druid_ui/widget/
button.rs

1use druid::widget::{ControllerHost,Click};
2use druid::widget::prelude::*;
3use druid::Color;
4use tracing::{instrument, trace};
5use druid::{theme, Affine, Data, Insets, LinearGradient, UnitPoint};
6use crate::widget::label::{Label,LabelText};
7
8
9const LABEL_INSETS: Insets = Insets::uniform_xy(8., 2.);
10
11pub struct Button<T> {
12    label: Label<T>,
13    label_size: Size,
14    style: ButtonStyle
15}
16
17
18pub enum ButtonStyle{
19    ORANGE,
20    RED,
21    YELLOW,
22    BLUE,
23    PURPLE,
24    GREEN,
25    NONE
26}
27
28#[derive(Clone)]
29struct ButtonStyleColor{
30    border_color:Color,
31    background_color:Color,
32    active_border_color:Color,
33    active_background_color:Color,
34    hot_border_color:Color,
35    hot_background_color:Color,
36    text_color:Color
37}
38
39impl ButtonStyleColor {
40
41    fn get_style(style: &ButtonStyle) -> Option<Self> {
42        match style {
43            ButtonStyle::RED => {
44                Some(Self{
45                    border_color:Color::rgb8(255,105,94),
46                    background_color:Color::rgb8(0,0,0),
47                    active_border_color:Color::rgb8(255,105,94),
48                    active_background_color:Color::rgb8(0,0,0),
49                    hot_border_color:Color::rgb8(255,105,94),
50                    hot_background_color:Color::rgb8(255,105,94),
51                    text_color:Color::rgb8(255,255,255)
52                })
53            }
54            ButtonStyle::ORANGE => {
55                Some(Self{
56                    border_color:Color::rgb8(255,133,27),
57                    background_color:Color::rgb8(0,0,0),
58                    active_border_color:Color::rgb8(255,133,27),
59                    active_background_color:Color::rgb8(0,0,0),
60                    hot_border_color:Color::rgb8(255,133,27),
61                    hot_background_color:Color::rgb8(255,133,27),
62                    text_color:Color::rgb8(255,255,255)
63                })
64            }
65            ButtonStyle::YELLOW => {
66                Some(Self{
67                    border_color:Color::rgb8(255,226,31),
68                    background_color:Color::rgb8(0,0,0),
69                    active_border_color:Color::rgb8(255,226,31),
70                    active_background_color:Color::rgb8(0,0,0),
71                    hot_border_color:Color::rgb8(255,226,31),
72                    hot_background_color:Color::rgb8(255,226,31),
73                    text_color:Color::rgb8(255,255,255)
74                })
75            }
76            ButtonStyle::BLUE => {
77                Some(Self{
78                    border_color:Color::rgb8(84,200,255),
79                    background_color:Color::rgb8(0,0,0),
80                    active_border_color:Color::rgb8(84,200,255),
81                    active_background_color:Color::rgb8(0,0,0),
82                    hot_border_color:Color::rgb8(84,200,255),
83                    hot_background_color:Color::rgb8(84,200,255),
84                    text_color:Color::rgb8(255,255,255)
85                })
86            }
87            ButtonStyle::PURPLE => {
88                Some(Self{
89                    border_color:Color::rgb8(220,115,255),
90                    background_color:Color::rgb8(0,0,0),
91                    active_border_color:Color::rgb8(220,115,255),
92                    active_background_color:Color::rgb8(0,0,0),
93                    hot_border_color:Color::rgb8(220,115,255),
94                    hot_background_color:Color::rgb8(220,115,255),
95                    text_color:Color::rgb8(255,255,255)
96                })
97            }
98            ButtonStyle::GREEN => {
99                Some(Self{
100                    border_color:Color::rgb8(46,204,64),
101                    background_color:Color::rgb8(0,0,0),
102                    active_border_color:Color::rgb8(46,204,64),
103                    active_background_color:Color::rgb8(0,0,0),
104                    hot_border_color:Color::rgb8(46,204,64),
105                    hot_background_color:Color::rgb8(46,204,64),
106                    text_color:Color::rgb8(255,255,255)
107                })
108            }
109            _ => {
110                None
111            }
112        }
113    }
114
115}
116
117
118impl<T: Data> Button<T> {
119
120    pub fn new(text: impl Into<LabelText<T>>,style:ButtonStyle) -> Button<T> {
121        let button_style = ButtonStyleColor::get_style(&style);
122        if let Some(color) = button_style{
123            Button::from_label(Label::with_and_color(text,color.text_color),style)
124        }else{
125            Button::from_label(Label::with_and_color(text,Color::rgb8(255,255,255)),style)
126        }
127    }
128
129
130    pub fn from_label(label: Label<T>,style:ButtonStyle) -> Button<T> {
131        Button {
132            label,
133            label_size: Size::ZERO,
134            style:style
135        }
136    }
137
138    pub fn dynamic(text: impl Fn(&T, &Env) -> String + 'static) -> Self {
139        let text: LabelText<T> = text.into();
140        Button::new(text,ButtonStyle::RED)
141    }
142
143    
144    pub fn on_click(
145        self,
146        f: impl Fn(&mut EventCtx, &mut T, &Env) + 'static,
147    ) -> ControllerHost<Self, Click<T>> {
148        ControllerHost::new(self, Click::new(f))
149    }
150
151}
152
153
154impl<T: Data> Widget<T> for Button<T> {
155    #[instrument(name = "Button", level = "trace", skip(self, ctx, event, _data, _env))]
156    fn event(&mut self, ctx: &mut EventCtx, event: &Event, _data: &mut T, _env: &Env) {
157        match event {
158            Event::MouseDown(_) => {
159                if !ctx.is_disabled() {
160                    ctx.set_active(true);
161                    ctx.request_paint();
162                    trace!("Button {:?} pressed", ctx.widget_id());
163                }
164            }
165            Event::MouseUp(_) => {
166                if ctx.is_active() && !ctx.is_disabled() {
167                    ctx.request_paint();
168                    trace!("Button {:?} released", ctx.widget_id());
169                }
170                ctx.set_active(false);
171            }
172            _ => (),
173        }
174    }
175
176    #[instrument(name = "Button", level = "trace", skip(self, ctx, event, data, env))]
177    fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &T, env: &Env) {
178        if let LifeCycle::HotChanged(_) | LifeCycle::DisabledChanged(_) = event {
179            ctx.request_paint();
180        }
181        self.label.lifecycle(ctx, event, data, env)
182    }
183
184    #[instrument(name = "Button", level = "trace", skip(self, ctx, old_data, data, env))]
185    fn update(&mut self, ctx: &mut UpdateCtx, old_data: &T, data: &T, env: &Env) {
186        self.label.update(ctx, old_data, data, env)
187    }
188
189    #[instrument(name = "Button", level = "trace", skip(self, ctx, bc, data, env))]
190    fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints, data: &T, env: &Env) -> Size {
191        bc.debug_check("Button");
192        let padding = Size::new(LABEL_INSETS.x_value(), LABEL_INSETS.y_value());
193        let label_bc = bc.shrink(padding).loosen();
194        self.label_size = self.label.layout(ctx, &label_bc, data, env);
195        // HACK: to make sure we look okay at default sizes when beside a textbox,
196        // we make sure we will have at least the same height as the default textbox.
197        let min_height = env.get(theme::BORDERED_WIDGET_HEIGHT);
198        let baseline = self.label.baseline_offset();
199        ctx.set_baseline_offset(baseline + LABEL_INSETS.y1);
200
201        let button_size = bc.constrain(Size::new(
202            self.label_size.width + padding.width+15.,
203            (self.label_size.height + padding.height+15.).max(min_height),
204        ));
205        trace!("Computed button size: {}", button_size);
206        button_size
207    }
208
209    #[instrument(name = "Button", level = "trace", skip(self, ctx, data, env))]
210    fn paint(&mut self, ctx: &mut PaintCtx, data: &T, env: &Env) {
211        let is_active = ctx.is_active() && !ctx.is_disabled();
212        
213        let is_hot = ctx.is_hot();
214        let size = ctx.size();
215    
216        //默认宽度
217        let stroke_width = 5.0;
218        //let stroke_width = env.get(theme::BUTTON_BORDER_WIDTH);
219
220        let button_style = ButtonStyleColor::get_style(&self.style);
221
222        let rounded_rect = size
223            .to_rect()
224            .inset(-stroke_width / 2.0)
225            .to_rounded_rect(env.get(theme::BUTTON_BORDER_RADIUS));
226
227        let bg_gradient = if ctx.is_disabled() {
228            //禁用状态
229            LinearGradient::new(
230                UnitPoint::TOP,
231                UnitPoint::BOTTOM,
232                (
233                    env.get(theme::DISABLED_BUTTON_LIGHT),
234                    env.get(theme::DISABLED_BUTTON_DARK),
235                ),
236            )
237        } else if is_active {
238            //点击
239            let style = button_style;
240            if let Some(color) = style {
241                let color_top = color.active_background_color.clone();
242                let color_bottom = color.active_background_color;
243                LinearGradient::new(
244                    UnitPoint::TOP,
245                    UnitPoint::BOTTOM,
246                    (color_top, color_bottom),
247                )
248            }else{
249                LinearGradient::new(
250                    UnitPoint::TOP,
251                    UnitPoint::BOTTOM,
252                    (env.get(theme::BUTTON_DARK), env.get(theme::BUTTON_LIGHT)),
253                )
254            }
255        } else if is_hot {
256            let style = button_style;
257            if let Some(color) = style {
258                let color_top = color.hot_background_color.clone();
259                let color_bottom = color.hot_background_color;
260                LinearGradient::new(
261                    UnitPoint::TOP,
262                    UnitPoint::BOTTOM,
263                    (color_top, color_bottom),
264                )
265            }else{
266                LinearGradient::new(
267                    UnitPoint::TOP,
268                    UnitPoint::BOTTOM,
269                    (env.get(theme::BUTTON_DARK), env.get(theme::BUTTON_LIGHT)),
270                )
271            }
272        } else {
273            let style = button_style;
274            if let Some(color) = style {
275                let color_top = color.background_color.clone();
276                let color_bottom = color.background_color;
277                LinearGradient::new(
278                    UnitPoint::TOP,
279                    UnitPoint::BOTTOM,
280                    (color_top, color_bottom),
281                )
282            }else{
283                LinearGradient::new(
284                    UnitPoint::TOP,
285                    UnitPoint::BOTTOM,
286                    (env.get(theme::BUTTON_DARK), env.get(theme::BUTTON_LIGHT)),
287                )
288            }
289        };
290
291        let button_style = ButtonStyleColor::get_style(&self.style);
292        //鼠标悬浮
293        let border_color = if is_hot && !ctx.is_disabled() {
294            let style = button_style;
295            if let Some(color) = style {
296                color.hot_border_color
297            }else{
298                env.get(theme::BORDER_LIGHT)
299            }
300        } else if is_active && !ctx.is_disabled(){
301            let style = button_style;
302            if let Some(color) = style {
303                color.active_border_color
304            }else{
305                env.get(theme::BORDER_LIGHT)
306            }
307        } else {
308            let style = button_style;
309            if let Some(color) = style {
310                color.border_color
311            }else{
312                env.get(theme::BORDER_DARK)
313            }
314        };
315
316        ctx.stroke(rounded_rect, &border_color, stroke_width);
317
318        ctx.fill(rounded_rect, &bg_gradient);
319
320        let label_offset = (size.to_vec2() - self.label_size.to_vec2()) / 2.0;
321
322        ctx.with_save(|ctx| {
323            ctx.transform(Affine::translate(label_offset));
324            self.label.paint(ctx, data, env);
325        });
326    }
327}