1# Custom Render Node (RenderNode)
2
3## Overview
4
5For third-party frameworks that lack an inherent rendering environment, leveraging the system's basic rendering and animation capabilities is crucial. Such frameworks often have their own mechanisms for front-end parsing, layout management, and event handling. As a result, the universal attributes and events associated with [FrameNode](./arkts-user-defined-arktsNode-frameNode.md) may be redundant for these frameworks, potentially causing duplicate work in managing layout and event logic.
6
7This is where [RenderNode](../reference/apis-arkui/js-apis-arkui-renderNode.md) becomes beneficial. **RenderNode** is a streamlined render node designed to offer rendering-specific features. It allows for setting of basic rendering properties and provides the capability to dynamically add and remove nodes and to implement custom drawing. These can be used to provide third-party frameworks with essential rendering and animation capabilities.
8
9## Creating and Removing Nodes
10
11You can create and remove nodes with **RenderNode**. You can create a custom instance of **RenderNode** using its constructor, and the instance thereby created corresponds to an entity node. You can use the [dispose](../reference/apis-arkui/js-apis-arkui-renderNode.md#dispose12) API in **RenderNode** to break the binding with the entity node.
12
13## Operating the Node Tree
14
15With **RenderNode**, you can add, delete, query, and modify nodes, thereby changing the subtree structure of nodes; you can also query the parent-child relationships to obtain the results.
16
17> **NOTE**
18>
19> - The subtree structure obtained through queries in **RenderNode** is constructed based on the parameters passed through the APIs of **RenderNode**.
20>
21> - To display a RenderNode in conjunction with built-in components, you need to mount the RenderNode obtained from a FrameNode onto the component tree.
22
23```ts
24import { FrameNode, NodeController, RenderNode } from '@kit.ArkUI';
25
26const renderNode = new RenderNode();
27renderNode.frame = { x: 0, y: 0, width: 200, height: 350 };
28renderNode.backgroundColor = 0xffff0000;
29for (let i = 0; i < 5; i++) {
30  const node = new RenderNode();
31  // Set the frame size of the node.
32  node.frame = { x: 10, y: 10 + 60 * i, width: 50, height: 50 };
33  // Set the background color of the node.
34  node.backgroundColor = 0xff00ff00;
35  // Mount the new node to the RenderNode.
36  renderNode.appendChild(node);
37}
38
39class MyNodeController extends NodeController {
40  private rootNode: FrameNode | null = null;
41
42  makeNode(uiContext: UIContext): FrameNode | null {
43    this.rootNode = new FrameNode(uiContext);
44
45    const rootRenderNode = this.rootNode?.getRenderNode();
46    if (rootRenderNode) {
47      rootRenderNode.appendChild(renderNode);
48    }
49    return this.rootNode;
50  }
51}
52
53@Entry
54@Component
55struct Index {
56  private myNodeController: MyNodeController = new MyNodeController();
57
58  build() {
59    Row() {
60      NodeContainer(this.myNodeController)
61        .width(200)
62        .height(350)
63      Button('getNextSibling')
64        .onClick(() => {
65          const child = renderNode.getChild(1);
66          const nextSibling = child!.getNextSibling()
67          if (child === null || nextSibling === null) {
68            console.log('the child or nextChild is null');
69          } else {
70            // Obtain the position of the child node.
71            console.log(`the position of child is x: ${child.position.x}, y: ${child.position.y}, ` +
72            `the position of nextSibling is x: ${nextSibling.position.x}, y: ${nextSibling.position.y}`);
73          }
74        })
75    }
76  }
77}
78```
79
80## Setting and Obtaining Rendering-related Properties
81
82In **RenderNode**, you can set rendering-related properties, including the following: [backgroundColor](../reference/apis-arkui/js-apis-arkui-renderNode.md#backgroundcolor), [clipToFrame](../reference/apis-arkui/js-apis-arkui-renderNode.md#cliptoframe), [opacity](../reference/apis-arkui/js-apis-arkui-renderNode.md#opacity), [size](../reference/apis-arkui/js-apis-arkui-renderNode.md#size), [position](../reference/apis-arkui/js-apis-arkui-renderNode.md#position), [frame](../reference/apis-arkui/js-apis-arkui-renderNode.md#frame), [pivot](../reference/apis-arkui/js-apis-arkui-renderNode.md#pivot), [scale](../reference/apis-arkui/js-apis-arkui-renderNode.md#scale), [translation](../reference/apis-arkui/js-apis-arkui-renderNode.md#translation), [rotation](../reference/apis-arkui/js-apis-arkui-renderNode.md#rotation), [transform](../reference/apis-arkui/js-apis-arkui-renderNode.md#transform), [shadowColor](../reference/apis-arkui/js-apis-arkui-renderNode.md#shadowcolor), [shadowOffset](../reference/apis-arkui/js-apis-arkui-renderNode.md#shadowoffset), [shadowAlpha](../reference/apis-arkui/js-apis-arkui-renderNode.md#shadowalpha), [shadowElevation](../reference/apis-arkui/js-apis-arkui-renderNode.md#shadowelevation), [shadowRadius](../reference/apis-arkui/js-apis-arkui-renderNode.md#shadowradius), [borderStyle](../reference/apis-arkui/js-apis-arkui-renderNode.md#borderstyle12), [borderWidth](../reference/apis-arkui/js-apis-arkui-renderNode.md#borderwidth12), [borderColor](../reference/apis-arkui/js-apis-arkui-renderNode.md#bordercolor12), [borderRadius](../reference/apis-arkui/js-apis-arkui-renderNode.md#borderradius12), [shapeMask](../reference/apis-arkui/js-apis-arkui-renderNode.md#shapemask12), [shapeClip](../reference/apis-arkui/js-apis-arkui-renderNode.md#shapeclip12), [markNodeGroup](../reference/apis-arkui/js-apis-arkui-renderNode.md#marknodegroup12). For details about the supported attributes, see [RenderNode](../reference/apis-arkui/js-apis-arkui-renderNode.md#rendernode).
83
84> **NOTE**
85>
86> - The properties obtained from a query in **RenderNode** are the values that have been explicitly set.
87>
88> - If no parameters are provided or if the provided parameters are invalid, the query will return the default values.
89>
90> - Avoid modifying RenderNodes in a BuilderNode. In **BuilderNode**, how properties are applied and updated is governed by the state management system, independently of manual intervention. Be aware that setting the same **RenderNode** property in both BuilderNode and FrameNode could lead to unexpected behavior.
91
92```ts
93import {  RenderNode, FrameNode, NodeController, ShapeMask, ShapeClip } from '@kit.ArkUI';
94
95const mask = new ShapeMask();
96mask.setRectShape({ left: 0, right: 150, top: 0, bottom: 150 });
97mask.fillColor = 0X55FF0000;
98mask.strokeColor = 0XFFFF0000;
99mask.strokeWidth = 24;
100
101const clip = new ShapeClip();
102clip.setCommandPath({ commands: "M100 0 L0 100 L50 200 L150 200 L200 100 Z" });
103
104const renderNode = new RenderNode();
105renderNode.backgroundColor = 0xffff0000;
106renderNode.size = { width: 100, height: 100 };
107
108class MyNodeController extends NodeController {
109  private rootNode: FrameNode | null = null;
110
111  makeNode(uiContext: UIContext): FrameNode | null {
112    this.rootNode = new FrameNode(uiContext);
113
114    const rootRenderNode = this.rootNode.getRenderNode();
115    if (rootRenderNode !== null) {
116      rootRenderNode.appendChild(renderNode);
117    }
118
119    return this.rootNode;
120  }
121}
122
123@Entry
124@Component
125struct Index {
126  private myNodeController: MyNodeController = new MyNodeController();
127
128  build() {
129    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) {
130      Column() {
131        NodeContainer(this.myNodeController)
132      }
133      Button("position")
134        .width(300)
135        .onClick(() => {
136          renderNode.position = { x: 10, y: 10 };
137          console.log("renderNode position:" + JSON.stringify(renderNode.position));
138        })
139      Button("pivot")
140        .width(300)
141        .onClick(() => {
142          renderNode.pivot = { x: 0.5, y: 0.6 };
143          console.log("renderNode pivot:" + JSON.stringify(renderNode.pivot));
144        })
145      Button("scale")
146        .width(300)
147        .onClick(() => {
148          renderNode.scale = { x: 0.5, y: 1 };
149          console.log("renderNode scale:" + JSON.stringify(renderNode.scale));
150        })
151      Button("translation")
152        .width(300)
153        .onClick(() => {
154          renderNode.translation = { x: 100, y: 0 };
155          console.log("renderNode translation:" + JSON.stringify(renderNode.translation));
156        })
157      Button("rotation")
158        .width(300)
159        .onClick(() => {
160          renderNode.rotation = { x: 45, y: 0, z: 0 };
161          console.log("renderNode rotation:" + JSON.stringify(renderNode.rotation));
162        })
163      Button("transform")
164        .width(300)
165        .onClick(() => {
166          renderNode.transform = [
167            1, 0, 0, 0,
168            0, 2, 0, 0,
169            0, 0, 1, 0,
170            0, 0, 0, 1
171          ];
172          console.log("renderNode transform:" + JSON.stringify(renderNode.transform));
173        })
174      Button("shadow")
175        .width(300)
176        .onClick(() => {
177          renderNode.shadowElevation = 10;
178          renderNode.shadowColor = 0XFF00FF00;
179          renderNode.shadowOffset = { x: 10, y: 10 };
180          renderNode.shadowAlpha = 0.1;
181          console.log("renderNode shadowElevation:" + JSON.stringify(renderNode.shadowElevation));
182          console.log("renderNode shadowColor:" + JSON.stringify(renderNode.shadowColor));
183          console.log("renderNode shadowOffset:" + JSON.stringify(renderNode.shadowOffset));
184          console.log("renderNode shadowAlpha:" + JSON.stringify(renderNode.shadowAlpha));
185        })
186      Button("shadowRadius")
187        .width(300)
188        .onClick(() => {
189          renderNode.shadowOffset = { x: 10, y: 10 };
190          renderNode.shadowAlpha = 0.7
191          renderNode.shadowRadius = 30;
192          console.log("renderNode shadowOffset:" + JSON.stringify(renderNode.shadowOffset));
193          console.log("renderNode shadowAlpha:" + JSON.stringify(renderNode.shadowAlpha));
194          console.log("renderNode shadowRadius:" + JSON.stringify(renderNode.shadowRadius));
195        })
196      Button("border")
197        .width(300)
198        .onClick(() => {
199          renderNode.borderWidth = { left: 8, top: 8, right: 8, bottom: 8 };
200          renderNode.borderStyle = {
201            left: BorderStyle.Solid,
202            top: BorderStyle.Dotted,
203            right: BorderStyle.Dashed,
204            bottom: BorderStyle.Solid
205          }
206          renderNode.borderColor = { left: 0xFF0000FF, top: 0xFF0000FF, right: 0xFF0000FF, bottom: 0xFF0000FF };
207          renderNode.borderRadius = { topLeft: 32, topRight: 32, bottomLeft: 32, bottomRight: 32 };
208          console.log("renderNode borderWidth:" + JSON.stringify(renderNode.borderWidth));
209          console.log("renderNode borderStyle:" + JSON.stringify(renderNode.borderStyle));
210          console.log("renderNode borderColor:" + JSON.stringify(renderNode.borderColor));
211          console.log("renderNode borderRadius:" + JSON.stringify(renderNode.borderRadius));
212        })
213      Button("shapeMask")
214        .width(300)
215        .onClick(() => {
216          renderNode.shapeMask = mask;
217          console.log("renderNode shapeMask:" + JSON.stringify(renderNode.shapeMask));
218        })
219      Button("shapeClip")
220        .width(300)
221        .onClick(() => {
222          renderNode.shapeClip = clip;
223          console.log("renderNode shapeMask:" + JSON.stringify(renderNode.shapeMask));
224        })
225    }
226    .padding({ left: 35, right: 35, top: 35, bottom: 35 })
227    .width("100%")
228    .height("100%")
229  }
230}
231```
232
233## Using Custom Drawing
234
235Override the [draw](../reference/apis-arkui/js-apis-arkui-renderNode.md#draw) API in **RenderNode** to customize the drawing content and use the [invalidate](../reference/apis-arkui/js-apis-arkui-renderNode.md#invalidate) API to manually trigger a redraw of the node.
236
237> **NOTE**
238>
239> - Triggering multiple **invalidate** calls at once will only result in a single redraw.
240>
241> - Custom drawing can be implemented by calling ArkTS APIs or Node-APIs.
242
243**ArkTS API sample code**
244
245```ts
246import { FrameNode, NodeController, RenderNode } from '@kit.ArkUI';
247import { drawing } from '@kit.ArkGraphics2D';
248
249class MyRenderNode extends RenderNode {
250  draw(context: DrawContext) {
251    // Obtain the canvas object.
252    const canvas = context.canvas;
253    // Create a brush.
254    const brush = new drawing.Brush();
255    // Set the brush color.
256    brush.setColor({ alpha: 255, red: 255, green: 0, blue: 0 });
257    canvas.attachBrush(brush);
258    // Draw a rectangle.
259    canvas.drawRect({ left: 0, right: 200, top: 0, bottom: 200 });
260    canvas.detachBrush();
261  }
262}
263
264const renderNode = new MyRenderNode();
265renderNode.frame = { x: 0, y: 0, width: 300, height: 300 };
266renderNode.backgroundColor = 0xff0000ff;
267renderNode.opacity = 0.5;
268
269class MyNodeController extends NodeController {
270  private rootNode: FrameNode | null = null;
271
272  makeNode(uiContext: UIContext): FrameNode | null {
273    this.rootNode = new FrameNode(uiContext);
274
275    const rootRenderNode = this.rootNode?.getRenderNode();
276    if (rootRenderNode !== null) {
277      rootRenderNode.frame = { x: 0, y: 0, width: 500, height: 500 }
278      rootRenderNode.appendChild(renderNode);
279    }
280
281    return this.rootNode;
282  }
283}
284
285@Entry
286@Component
287struct Index {
288  private myNodeController: MyNodeController = new MyNodeController();
289
290  build() {
291    Column() {
292      NodeContainer(this.myNodeController)
293        .width('100%')
294      Button('Invalidate')
295        .onClick(() => {
296          // Triggering multiple invalidate calls at once will only result in a single redraw.
297          renderNode.invalidate();
298          renderNode.invalidate();
299        })
300    }
301  }
302}
303```
304
305**Node-API sample code**
306
307The C++ side can obtain the canvas through the Node-API and perform subsequent custom drawing operations.
308
309```c++
310// native_bridge.cpp
311#include "napi/native_api.h"
312#include <native_drawing/drawing_canvas.h>
313#include <native_drawing/drawing_color.h>
314#include <native_drawing/drawing_path.h>
315#include <native_drawing/drawing_pen.h>
316
317static napi_value OnDraw(napi_env env, napi_callback_info info)
318{
319    size_t argc = 4;
320    napi_value args[4] = { nullptr };
321    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
322
323    int32_t id;
324    napi_get_value_int32(env, args[0], &id);
325
326    // Obtain the pointer to the canvas.
327    void* temp = nullptr;
328    napi_unwrap(env, args[1], &temp);
329    OH_Drawing_Canvas *canvas = reinterpret_cast<OH_Drawing_Canvas*>(temp);
330
331    // Obtain the canvas width.
332    int32_t width;
333    napi_get_value_int32(env, args[2], &width);
334
335    // Obtain the canvas height.
336    int32_t height;
337    napi_get_value_int32(env, args[3], &height);
338
339    // Pass in information such as the canvas, height, and width to the drawing API for custom drawing.
340    auto path = OH_Drawing_PathCreate();
341    OH_Drawing_PathMoveTo(path, width / 4, height / 4);
342    OH_Drawing_PathLineTo(path, width * 3 / 4, height / 4);
343    OH_Drawing_PathLineTo(path, width * 3 / 4, height * 3 / 4);
344    OH_Drawing_PathLineTo(path, width / 4, height * 3 / 4);
345    OH_Drawing_PathLineTo(path, width / 4, height / 4);
346    OH_Drawing_PathClose(path);
347
348    auto pen = OH_Drawing_PenCreate();
349    OH_Drawing_PenSetWidth(pen, 10);
350    OH_Drawing_PenSetColor(pen, OH_Drawing_ColorSetArgb(0xFF, 0xFF, 0x00, 0x00));
351    OH_Drawing_CanvasAttachPen(canvas, pen);
352
353    OH_Drawing_CanvasDrawPath(canvas, path);
354
355    return nullptr;
356}
357
358EXTERN_C_START
359static napi_value Init(napi_env env, napi_value exports)
360{
361    napi_property_descriptor desc[] = {
362        { "nativeOnDraw", nullptr, OnDraw, nullptr, nullptr, nullptr, napi_default, nullptr }
363    };
364    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
365    return exports;
366}
367EXTERN_C_END
368
369static napi_module demoModule = {
370    .nm_version =1,
371    .nm_flags = 0,
372    .nm_filename = nullptr,
373    .nm_register_func = Init,
374    .nm_modname = "entry",
375    .nm_priv = ((void*)0),
376    .reserved = { 0 },
377};
378
379extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
380{
381    napi_module_register(&demoModule);
382}
383```
384
385Add the following content to the **src/main/cpp/CMakeLists.txt** file of the project:
386```cmake
387# the minimum version of CMake.
388cmake_minimum_required(VERSION 3.4.1)
389project(NapiTest)
390
391set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
392
393include_directories(${NATIVERENDER_ROOT_PATH}
394                    ${NATIVERENDER_ROOT_PATH}/include)
395
396add_library(entry SHARED native_bridge.cpp)
397target_link_libraries(entry PUBLIC libace_napi.z.so)
398target_link_libraries(entry PUBLIC libace_ndk.z.so)
399target_link_libraries(entry PUBLIC libnative_drawing.so)
400```
401
402Add the definition of the custom drawing API on the ArkTS side to the **src/main/cpp/types/libentry/index.d.ts** file of the project. The following is an example:
403```ts
404import { DrawContext } from '@kit.ArkUI'
405
406export const nativeOnDraw: (id: number, context: DrawContext, width: number, height: number) => number;
407```
408
409Code in ArkTS:
410
411```ts
412// Index.ets
413import bridge from "libentry.so" // This .so file is written and generated by Node-API.
414import { DrawContext, FrameNode, NodeController, RenderNode } from '@kit.ArkUI'
415
416class MyRenderNode extends RenderNode {
417  draw(context: DrawContext) {
418    // The width and height in the context need to be converted from vp to px.
419    bridge.nativeOnDraw(0, context, vp2px(context.size.height), vp2px(context.size.width));
420  }
421}
422
423class MyNodeController extends NodeController {
424  private rootNode: FrameNode | null = null;
425
426  makeNode(uiContext: UIContext): FrameNode | null {
427    this.rootNode = new FrameNode(uiContext);
428
429    const rootRenderNode = this.rootNode.getRenderNode();
430    if (rootRenderNode !== null) {
431      const renderNode = new MyRenderNode();
432      renderNode.size = { width: 100, height: 100 }
433      rootRenderNode.appendChild(renderNode);
434    }
435    return this.rootNode;
436  }
437}
438
439@Entry
440@Component
441struct Index {
442  private myNodeController: MyNodeController = new MyNodeController();
443
444  build() {
445    Row() {
446      NodeContainer(this.myNodeController)
447    }
448  }
449}
450```
451
452## Setting the Label
453
454You can use the [label](../reference/apis-arkui/js-apis-arkui-renderNode.md#label12) API to assign labels for RenderNodes. This makes it easier to distinguish between nodes under node **Inspector**.
455
456```ts
457import {  RenderNode, FrameNode, NodeController, UIContext } from '@kit.ArkUI';
458
459class MyNodeController extends NodeController {
460  private rootNode: FrameNode | null = null;
461
462  makeNode(uiContext: UIContext): FrameNode | null {
463    this.rootNode = new FrameNode(uiContext);
464    const renderNode: RenderNode | null = this.rootNode.getRenderNode();
465    if (renderNode !== null) {
466      const renderChildNode: RenderNode = new RenderNode();
467      renderChildNode.frame = { x: 0, y: 0, width: 100, height: 100 };
468      renderChildNode.backgroundColor = 0xffff0000;
469      renderChildNode.label = 'customRenderChildNode';
470      console.log('label:', renderChildNode.label);
471      renderNode.appendChild(renderChildNode);
472    }
473
474    return this.rootNode;
475  }
476}
477
478@Entry
479@Component
480struct Index {
481  private myNodeController: MyNodeController = new MyNodeController();
482
483  build() {
484    Column() {
485      NodeContainer(this.myNodeController)
486        .width(300)
487        .height(700)
488        .backgroundColor(Color.Gray)
489    }
490  }
491}
492```
493