|
| 1 | +--- |
| 2 | +title: Custom Components (Rust) |
| 3 | +--- |
| 4 | + |
| 5 | +import { Image } from 'astro:assets'; |
| 6 | +import initImg from "../../../../assets/hello-element/init.png"; |
| 7 | +import renderImg from "../../../../assets/hello-element/render.png"; |
| 8 | + |
| 9 | + |
| 10 | +Deft includes some commonly used basic components and allows you to create custom components to meet complex requirements. |
| 11 | + |
| 12 | +_Note: Custom components here refer to basic components like Label and Button. If you need to create custom React/Vue/Solid components, you should refer to the custom component tutorials for the respective frameworks; this tutorial does not apply._ |
| 13 | + |
| 14 | +### Creating a Custom Component |
| 15 | + |
| 16 | +Creating a custom component is very simple—you only need to implement the `ElementBackend` trait. |
| 17 | + |
| 18 | +The `ElementBackend` trait has several methods, but only the `create` method is mandatory. Other methods can be implemented optionally based on requirements. |
| 19 | + |
| 20 | +First, we need to create a struct to represent our custom component. Here, we use `HelloBackend` as an example. |
| 21 | + |
| 22 | +```rust |
| 23 | +pub struct HelloBackend { |
| 24 | + element_weak: ElementWeak, |
| 25 | +} |
| 26 | + |
| 27 | +impl ElementBackend for HelloBackend { |
| 28 | + fn create(element: &mut Element) -> Self where Self: Sized { |
| 29 | + Self { |
| 30 | + // Save a weak reference to the element, which will be used later |
| 31 | + element_weak: element.as_weak(), |
| 32 | + } |
| 33 | + } |
| 34 | +} |
| 35 | +``` |
| 36 | + |
| 37 | +### Registering the Custom Component |
| 38 | + |
| 39 | +After creating the custom component, it needs to be registered. This can be done during the initialization of the JS engine. |
| 40 | + |
| 41 | +```rust |
| 42 | +impl IApp for AppImpl { |
| 43 | + |
| 44 | + fn init_js_engine(&mut self, _js_engine: &mut JsEngine) { |
| 45 | + // Register our custom component with the tag "hello" |
| 46 | + register_component::<HelloBackend>("hello"); |
| 47 | + } |
| 48 | + ... |
| 49 | +} |
| 50 | +``` |
| 51 | + |
| 52 | +Here, we register this component as the `hello` component. |
| 53 | + |
| 54 | +### Creating JS Binding for the Custom Component |
| 55 | + |
| 56 | +To use this custom component in JS, we need to create a JS binding: `HelloElement` . |
| 57 | + |
| 58 | +```javascript |
| 59 | +class HelloElement extends Element { |
| 60 | + constructor() { |
| 61 | + // "hello" is the element's tag, which must match the tag declared during registration |
| 62 | + super("hello"); |
| 63 | + } |
| 64 | +} |
| 65 | +``` |
| 66 | + |
| 67 | +`HelloElement` inherits from the `Element` class, which is the base class for all components. |
| 68 | + |
| 69 | +### Using the Custom Component |
| 70 | + |
| 71 | +After completing the three steps of `creation`, `registration`, and `binding`, the component can be directly used in JS code. |
| 72 | + |
| 73 | +First, define some basic styles for it. |
| 74 | + |
| 75 | +```css |
| 76 | +body { |
| 77 | + justify-content: center; |
| 78 | + align-items: center; |
| 79 | +} |
| 80 | +hello { |
| 81 | + width: 100px; |
| 82 | + height: 100px; |
| 83 | + background: #ccc; |
| 84 | +} |
| 85 | +``` |
| 86 | + |
| 87 | +Then, instantiate a `hello` component and add it to the document flow. |
| 88 | + |
| 89 | +```javascript |
| 90 | +const helloElement = new HelloElement(); |
| 91 | +window.body.addChild(helloElement); |
| 92 | +``` |
| 93 | + |
| 94 | +The running effect is as follows: |
| 95 | + |
| 96 | +<Image src={initImg} alt="hello element" width="300" /> |
| 97 | + |
| 98 | +### Rendering the Component |
| 99 | + |
| 100 | +As shown, our custom component is displayed in the window, but it doesn't do anything yet. Now, let's enhance this component by adding custom rendering logic, which is typically required for custom components. |
| 101 | + |
| 102 | +To add custom rendering logic, implement the `ElementBackend::render` method. |
| 103 | + |
| 104 | +```rust |
| 105 | +impl ElementBackend for HelloBackend { |
| 106 | + ... |
| 107 | + fn render(&mut self) -> RenderFn { |
| 108 | + // Upgrade the weak reference to the element to a strong reference |
| 109 | + let element = self.element_weak.upgrade_mut().unwrap(); |
| 110 | + // Get the element's size information |
| 111 | + let bounds = element.get_bounds(); |
| 112 | + // Get the center coordinates of the element |
| 113 | + let center = (bounds.width / 2.0, bounds.height / 2.0); |
| 114 | + // Calculate the radius |
| 115 | + let radius = f32::min(center.0, center.1); |
| 116 | + RenderFn::new(move |painter| { |
| 117 | + let mut paint = Paint::default(); |
| 118 | + // Use fill style |
| 119 | + paint.set_style(PaintStyle::Fill); |
| 120 | + paint.set_color(Color::from_rgb(0, 80, 0)); |
| 121 | + // Draw a circle |
| 122 | + painter.canvas.draw_circle(center, radius, &paint); |
| 123 | + }) |
| 124 | + } |
| 125 | +} |
| 126 | +``` |
| 127 | + |
| 128 | +The `render` method returns a `RenderFn` object containing our component's rendering logic. In the example above, we draw a solid circle. Rerun the application to see the final effect: |
| 129 | + |
| 130 | +<Image src={renderImg} alt="custom element with renderer" width="300" /> |
| 131 | + |
| 132 | +### Related Code and Reference Examples |
| 133 | + |
| 134 | +The complete code for this article can be found in the Deft repository: https://github.com/deft-ui/deft/blob/main/examples/custom_element.rs |
| 135 | + |
| 136 | +More reference examples: |
| 137 | + |
| 138 | +1. Video playback (ffmpeg): https://github.com/deft-ui/deft-video |
| 139 | +2. Remote desktop (SPICE): https://github.com/kasonyang/tiny-spice |
| 140 | + |
| 141 | + |
0 commit comments