自定义 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] - 表示继承另一个组件的属性