自定义 Button Widget

    让我们创建一个自定义 Button 组件。

    完整代码如下,可查看代码中的注释:

    1use makepad_widgets::*;
    2
    3live_design! {
    4    use link::theme::*;
    5    use link::shaders::*;
    6    use link::widgets::*;
    7
    8    // 定义一个通用的按钮样式
    9    // 继承自 Button
    10    pub MyButton = {{MyButton}} <Button> {
    11        width: 200, // 按钮宽度
    12        height: 50, // 按钮高度
    13        margin: {left: 20, right: 20}, // 按钮左右外边距
    14
    15        text: "My Button", // 按钮文字
    16        draw_text: {
    17            color: #ffffff // 文字颜色为白色
    18        },
    19
    20        draw_bg: {
    21            // 这里最多定义 6 个 instance,否则报错 subtract with overflow
    22            instance background_color: #0000ff, // 背景色
    23            instance hover_color: #0055ff, // 鼠标悬停时的颜色
    24            instance pressed_color: #00008B, // 鼠标按下时的颜色
    25
    26            instance border_width: 1.0, // 边框宽度
    27            instance border_color: #00f3ff, // 边框颜色
    28
    29            instance glow: 0.0, // 发光效果控制
    30            instance hover: 0.0, // 控制鼠标悬停效果
    31            instance pressed: 0.0, // 控制鼠标按下效果
    32
    33            fn pixel(self) -> vec4 {
    34                let sdf = Sdf2d::viewport(self.pos * self.rect_size);
    35                // sdf.box(0.0, 0.0, self.rect_size.x, self.rect_size.y, 8.0);
    36
    37                // 计算缩放和位移
    38                let scale = 1.0 - self.pressed * 0.04; // 按下时稍微缩小
    39                let size = self.rect_size * scale;
    40                let offset = (self.rect_size - size) * 0.5; // 居中
    41
    42                // 绘制外层发光
    43                sdf.box(
    44                    offset.x ,  // 向外扩展4个像素
    45                    offset.y ,
    46                    size.x ,    // 两边各扩展4个像素
    47                    size.y ,
    48                    9.0            // 稍大的圆角
    49                );
    50
    51                // 发光效果 - 使用半透明的边框颜色
    52                let glow_alpha = self.glow * 0.5; // 控制发光强度
    53                sdf.fill_keep(vec4(self.border_color.xyz, glow_alpha));
    54
    55
    56                // 简化绘制,只保留主体
    57                sdf.box(
    58                    offset.x,
    59                    offset.y,
    60                    size.x,
    61                    size.y,
    62                    8.0
    63                );
    64
    65                // 未按下时显示阴影,按下时减弱阴影
    66                let shadow_alpha = (1.0 - self.pressed) * 0.2;
    67                sdf.fill_keep(vec4(0.,0.,0.,shadow_alpha));
    68
    69                // 基础颜色
    70                let base_color = self.background_color;
    71
    72                // hover效果通过降低透明度来实现,不直接修改颜色
    73                let hover_alpha = self.hover * 0.2;
    74                let color_with_hover = mix(
    75                    base_color,
    76                    vec4(1.0, 1.0, 1.0, 1.0),
    77                    hover_alpha
    78                );
    79
    80                // pressed效果
    81                let final_color = mix(
    82                    color_with_hover,
    83                    self.pressed_color,
    84                    self.pressed
    85                );
    86
    87
    88                // 先填充主体颜色
    89                sdf.fill_keep(final_color);
    90
    91                // 边框发光效果
    92                let border_glow = max(self.hover * 0.5, self.glow);
    93                let border_color = mix(
    94                    self.border_color,
    95                    vec4(1.0, 1.0, 1.0, 0.8),
    96                    border_glow
    97                );
    98                sdf.stroke(border_color, self.border_width);
    99
    100                return sdf.result
    101            }
    102        }
    103
    104    }
    105}
    106
    107
    108// 定义组件结构体
    109#[derive(Live,Widget)]
    110pub struct MyButton {
    111    // 继承 Button 的所有功能
    112    #[deref]
    113    button: Button,
    114    #[rust]
    115    initialized: bool, // 标记是否已初始化
    116}
    117
    118impl LiveHook for MyButton {
    119    fn after_new_from_doc(&mut self, cx: &mut Cx) {
    120        log!("MyButton: after_new_from_doc");
    121        self.initialized = true; // 在创建后就将其标记为已初始化
    122        self.button.after_new_from_doc(cx);
    123        log!("button text is empty? {:?}", self.button.text.as_ref())
    124    }
    125
    126    fn before_apply(&mut self, cx: &mut Cx, apply: &mut Apply, index: usize, nodes: &[LiveNode]) {
    127        log!("MyButton: before_apply");
    128        self.button.before_apply(cx, apply, index, nodes);
    129    }
    130
    131    fn after_apply(&mut self, cx: &mut Cx, apply: &mut Apply, index: usize, nodes: &[LiveNode]) {
    132        log!("MyButton: after_apply");
    133        self.button.after_apply(cx, apply, index, nodes);
    134    }
    135}
    136
    137
    138impl Widget for MyButton {
    139    fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
    140        log!("MyButton handle_event");
    141        log!("MyButton not initialized!");
    142        self.button.handle_event(cx, event, scope);
    143    }
    144
    145    fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep {
    146        log!("MyButton draw_walk");
    147        self.initialized = true;
    148        log!("MyButton initialized!");
    149        self.button.draw_walk(cx, scope, walk)
    150    }
    151}
    152
    153impl MyButtonRef {
    154    pub fn clicked(&self, actions: &Actions) -> bool {
    155        self.borrow().map(|button| button.button.clicked(actions)).unwrap_or(false)
    156    }
    157
    158    pub fn apply_over(&self, cx: &mut Cx, nodes: LiveNodeSlice) {
    159        if let Some(mut inner) = self.borrow_mut() {
    160            log!("Applying style to MyButton");
    161            // 应用样式到内部按钮
    162            inner.button.apply_over(cx, nodes);
    163            // 确保重绘
    164            inner.button.redraw(cx);
    165        } else {
    166            log!("Failed to borrow MyButton - this may indicate an initialization problem");
    167        }
    168    }
    169
    170    pub fn set_text_and_redraw(&self, cx: &mut Cx, text: &str) {
    171        if let Some(mut inner) = self.borrow_mut() {
    172            inner.button.set_text_and_redraw(cx, text);
    173            inner.button.redraw(cx);
    174        }
    175    }
    176
    177    // 添加检查方法
    178    pub fn is_some(&self) -> bool {
    179        self.borrow().is_some()
    180    }
    181}

    总的来说,这段代码定义了一个高度可定制的按钮组件 MyButton,具有丰富的视觉效果如发光、阴影、悬停变色等。它继承了 Button 的功能,还添加了初始化检查和一些辅助方法。通过 live_design! 宏可以方便地定义按钮的样式。

    • live_design! 宏中定义了按钮的样式和属性,包括:
      • 按钮的尺寸、外边距
      • 按钮的文字内容和颜色
      • 按钮的背景色,包括普通状态、悬停状态、按下状态的颜色
      • 按钮的边框宽度和颜色
      • 按钮的发光效果、悬停效果、按下效果的控制变量
      • 按钮的绘制函数 pixel(),详细定义了如何绘制按钮的各个部分,包括发光、阴影、主体颜色、边框等
    • MyButton 结构体定义了按钮组件,它继承了 Button 的所有功能,还添加了一个 initialized 字段来标记是否已初始化。
    • 实现了 LiveHook trait,定义了组件在不同生命周期的行为:
      • after_new_from_doc(): 创建后将 initialized 设为 true,表示已初始化
      • before_apply()after_apply(): 在应用属性前后执行一些操作
    • 实现了 Widget trait,定义了组件的事件处理和绘制行为:
      • handle_event(): 处理事件
      • draw_walk(): 绘制组件
    • MyButtonRef 实现了一些方法:
      • clicked(): 检查按钮是否被点击
      • apply_over(): 应用样式到按钮
      • set_text_and_redraw(): 设置按钮文字并重绘
      • is_some(): 检查按钮是否存在

    特别说明:定义 Widget 主要的 Live 属性标记如下。

    • #[live] - 表示此属性可在DSL中访问和修改
    • #[rust] - 表示此属性只在Rust代码中使用
    • #[calc] - 表示这是一个计算属性
    • #[live(default)] - 带默认值的属性
    • #[deref] - 表示继承另一个组件的属性