Custom Widget

Let's create a custom Button component.

Complete code with comments is as follows:

1use makepad_widgets::*;
2
3live_design! {
4    use link::theme::*;
5    use link::shaders::*;
6    use link::widgets::*;
7
8    // Define a common button style
9    // Inherit from Button
10    MyButton = {{MyButton}} <Button> {
11        width: 200, // Button width
12        height: 50, // Button height
13        margin: {left: 20, right: 20}, // Button left and right margins
14
15        text: "My Button", // Button text
16        draw_text: {
17            color: #ffffff // Text color is white
18        },
19
20        draw_bg: {
21            // Here define at most 6 instances, otherwise will report subtract with overflow
22            instance background_color: #0000ff, // Background color
23            instance hover_color: #0055ff, // Mouse hover color
24            instance pressed_color: #00008B, // Mouse pressed color
25
26            instance border_width: 1.0, // Border width
27            instance border_color: #00f3ff, // Border color
28
29            instance glow: 0.0, // Glow effect control
30            instance hover: 0.0, // Control mouse hover effect
31            instance pressed: 0.0, // Control mouse pressed effect
32
33            fn pixel(self) -> vec4 {
34                let sdf = Sdf2d::viewport(self.pos * self.rect_size);
35
36                // Calculate scale and offset
37                let scale = 1.0 - self.pressed * 0.04; // Slightly shrink when pressed
38                let size = self.rect_size * scale; 
39                let offset = (self.rect_size - size) * 0.5; // Center
40
41                // Draw outer glow
42                sdf.box(
43                    offset.x,
44                    offset.y,
45                    size.x,
46                    size.y,
47                    9.0            // Slightly larger corner radius
48                );
49
50                // Glow effect - use semi-transparent border color
51                let glow_alpha = self.glow * 0.5; // Control glow intensity
52                sdf.fill_keep(vec4(self.border_color.xyz, glow_alpha)); 
53
54
55                // Simplify drawing, keep only the main body
56                sdf.box(
57                    offset.x,
58                    offset.y,
59                    size.x,
60                    size.y,
61                    8.0
62                );
63
64                // Show shadow when not pressed, reduce shadow when pressed
65                let shadow_alpha = (1.0 - self.pressed) * 0.2; 
66                sdf.fill_keep(vec4(0.,0.,0.,shadow_alpha));
67
68                // Base color
69                let base_color = self.background_color;
70
71                // Hover effect achieved by reducing opacity, not directly modifying color
72                let hover_alpha = self.hover * 0.2;
73                let color_with_hover = mix(
74                    base_color, 
75                    vec4(1.0, 1.0, 1.0, 1.0),
76                    hover_alpha
77                );
78
79                // Pressed effect
80                let final_color = mix(
81                    color_with_hover,
82                    self.pressed_color,
83                    self.pressed
84                );
85
86                // Fill the main body color first
87                sdf.fill_keep(final_color);
88
89                // Border glow effect  
90                let border_glow = max(self.hover * 0.5, self.glow);
91                let border_color = mix(
92                    self.border_color,
93                    vec4(1.0, 1.0, 1.0, 0.8), 
94                    border_glow
95                );
96                sdf.stroke(border_color, self.border_width);
97
98                return sdf.result
99            }
100        }
101    }
102}
103
104
105// Define component structure
106#[derive(Live,Widget)]
107pub struct MyButton {
108    // Inherit all Button functionality
109    #[deref]
110    button: Button,
111    #[rust]
112    initialized: bool, // Mark whether initialized
113}
114
115impl LiveHook for MyButton {
116    fn after_new_from_doc(&mut self, cx: &mut Cx) {
117        log!("MyButton: after_new_from_doc");
118        self.initialized = true; // Mark as initialized after creation
119        self.button.after_new_from_doc(cx);
120        log!("button text is empty? {:?}", self.button.text.as_ref())
121    }
122
123    fn before_apply(&mut self, cx: &mut Cx, apply: &mut Apply, index: usize, nodes: &[LiveNode]) {
124        log!("MyButton: before_apply");
125        self.button.before_apply(cx, apply, index, nodes);
126    }
127
128    fn after_apply(&mut self, cx: &mut Cx, apply: &mut Apply, index: usize, nodes: &[LiveNode]) {
129        log!("MyButton: after_apply");
130        self.button.after_apply(cx, apply, index, nodes);
131    }
132}
133
134
135impl Widget for MyButton {
136    fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
137        log!("MyButton handle_event");
138        log!("MyButton not initialized!");
139        self.button.handle_event(cx, event, scope);
140    }
141
142    fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep {
143        log!("MyButton draw_walk");
144        self.initialized = true; 
145        log!("MyButton initialized!");
146        self.button.draw_walk(cx, scope, walk)
147    }
148}
149
150impl MyButtonRef {
151    pub fn clicked(&self, actions: &Actions) -> bool {
152        self.borrow().map(|button| button.button.clicked(actions)).unwrap_or(false)
153    }
154
155    pub fn apply_over(&self, cx: &mut Cx, nodes: LiveNodeSlice) {
156        if let Some(mut inner) = self.borrow_mut() {
157            log!("Applying style to MyButton");
158            // Apply style to inner button
159            inner.button.apply_over(cx, nodes);
160            // Ensure redraw
161            inner.button.redraw(cx);
162        } else {
163            log!("Failed to borrow MyButton - this may indicate an initialization problem");
164        }
165    }
166
167    pub fn set_text_and_redraw(&self, cx: &mut Cx, text: &str) {
168        if let Some(mut inner) = self.borrow_mut() {
169            inner.button.set_text_and_redraw(cx, text);
170            inner.button.redraw(cx);
171        }
172    }
173
174    // Add check method
175    pub fn is_some(&self) -> bool {
176        self.borrow().is_some()
177    }
178}

In summary, this code defines a highly customizable button component MyButton with rich visual effects like glow, shadows, hover state changes, etc.

It inherits from Button's functionality and adds initialization checks and helper methods. The button's style can be easily defined through the live_design! macro.

  • The live_design! macro defines the style and properties of the button, including.
    • Button size, margins
    • the text content and color of the button
    • The background color of the button, including the color of the normal, hover, and pressed states.
    • The width and color of the button's border
    • Button's light effect, hover effect, press the effect of the control variables
    • button drawing function pixel (), a detailed definition of how to draw the various parts of the button, including light, shadow, body color, border and so on.
  • The MyButton structure defines the button component, which inherits all the functionality of the Button and adds an initialized field to mark it as initialized or not.
  • Implements the LiveHook trait, which defines the behavior of the component during different lifecycles.
    • after_new_from_doc(): sets initialized to true after creation to indicate initialization.
    • before_apply() and after_apply(): perform actions before and after applying attributes.
  • Widget traits are implemented that define the event handling and drawing behavior of the component.
    • handle_event(): Handles events.
    • draw_walk(): draws the component.
  • Implemented methods for MyButtonRef.
    • clicked(): Checks if the button was clicked.
    • apply_over(): applies styles to the button
    • set_text_and_redraw(): sets the button text and redraws it.
    • is_some(): Checks if the button exists.

Special note: The main Live attribute markers for defining Widgets are as follows:

  • #[live] - Indicates this property can be accessed and modified in DSL
  • #[rust] - Indicates this property is only used in Rust code
  • #[calc] - Indicates this is a calculated property
  • #[live(default)] - Property with default value
  • #[deref] - Indicates inheritance of another component's properties