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