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 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 let stroke_width = 5.0;
218 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 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 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 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}