1use glyphon::Metrics;
2use glyphon::cosmic_text::Align;
3use taffy::prelude::*;
4use winit::event::{ElementState, MouseButton, WindowEvent};
5
6use crate::framework::{DrawContext, EventContext, Widget};
7
8pub struct Button {
9 label: String,
10 metrics: Metrics,
11 padding: f32,
12 border_radius: f32,
13 bg_color: [f32; 3],
14 hover_color: [f32; 3],
15 active_color: [f32; 3],
16 focus_color: [f32; 3],
17 text_color: [u8; 3],
18 hover_text_color: [u8; 3],
19 active_text_color: [u8; 3],
20 hover: bool,
21 active: bool,
22 focus: bool,
23 on_click: Option<Box<dyn FnMut()>>,
24}
25
26impl Button {
27 pub fn new(label: impl Into<String>, metrics: Metrics) -> Self {
28 Self {
29 label: label.into(),
30 metrics,
31 padding: 16.0,
32 border_radius: 0.0,
33 bg_color: [0.20, 0.65, 0.85],
34 hover_color: [0.35, 0.75, 0.92],
35 active_color: [0.15, 0.55, 0.75],
36 focus_color: [0.18, 0.60, 0.82],
37 text_color: [25, 25, 25],
38 hover_text_color: [15, 15, 15],
39 active_text_color: [250, 250, 250],
40 hover: false,
41 active: false,
42 focus: false,
43 on_click: None,
44 }
45 }
46
47 pub fn with_colors(
48 mut self,
49 bg: [f32; 3],
50 hover: [f32; 3],
51 active: [f32; 3],
52 focus: [f32; 3],
53 ) -> Self {
54 self.bg_color = bg;
55 self.hover_color = hover;
56 self.active_color = active;
57 self.focus_color = focus;
58 self
59 }
60
61 pub fn with_text_colors(mut self, normal: [u8; 3], hover: [u8; 3], active: [u8; 3]) -> Self {
62 self.text_color = normal;
63 self.hover_text_color = hover;
64 self.active_text_color = active;
65 self
66 }
67
68 pub fn with_padding(mut self, padding: f32) -> Self {
69 self.padding = padding;
70 self
71 }
72
73 pub fn with_border_radius(mut self, radius: f32) -> Self {
74 self.border_radius = radius;
75 self
76 }
77
78 pub fn set_on_click(&mut self, handler: impl FnMut() + 'static) {
79 self.on_click = Some(Box::new(handler));
80 }
81
82 fn current_color(&self) -> [f32; 3] {
83 if self.active {
84 self.active_color
85 } else if self.hover {
86 self.hover_color
87 } else if self.focus {
88 self.focus_color
89 } else {
90 self.bg_color
91 }
92 }
93
94 fn current_text_color(&self) -> [u8; 3] {
95 if self.active {
96 self.active_text_color
97 } else if self.hover {
98 self.hover_text_color
99 } else {
100 self.text_color
101 }
102 }
103
104 fn hit_test(&self, layout: &Layout, x: f32, y: f32) -> bool {
105 x >= layout.location.x
106 && x <= layout.location.x + layout.size.width
107 && y >= layout.location.y
108 && y <= layout.location.y + layout.size.height
109 }
110
111 fn click(&mut self) {
112 if let Some(handler) = self.on_click.as_mut() {
113 handler();
114 }
115 }
116}
117
118impl Widget for Button {
119 fn style(&self) -> Style {
120 Style {
121 flex_grow: 1.0,
122 ..Default::default()
123 }
124 }
125
126 fn draw(&self, ctx: &mut DrawContext) {
127 let layout = ctx.layout;
128 let color = self.current_color();
129 ctx.renderer.fill_rect_rounded(
130 (
131 layout.location.x,
132 layout.location.y,
133 layout.size.width,
134 layout.size.height,
135 ),
136 [color[0], color[1], color[2], 1.0],
137 self.border_radius,
138 );
139
140 let inner_width = (layout.size.width - self.padding * 2.0).max(0.0);
141 let inner_height = (layout.size.height - self.padding * 2.0).max(0.0);
142 let baseline_nudge = 2.0;
143 let vertical_offset = ((inner_height - self.metrics.font_size).max(0.0)) * 0.5 + baseline_nudge;
144 let text_left = layout.location.x + self.padding;
145 let text_top = layout.location.y + self.padding + vertical_offset;
146
147 ctx.renderer.draw_text(
148 &self.label,
149 (text_left, text_top),
150 self.current_text_color(),
151 (inner_width, inner_height),
152 self.metrics,
153 Align::Center,
154 );
155 }
156
157 fn handle_event(&mut self, ctx: &mut EventContext) -> bool {
158 let layout = ctx.layout;
159 let mut changed = false;
160 match ctx.event {
161 WindowEvent::CursorMoved { position, .. } => {
162 let over = self.hit_test(layout, position.x as f32, position.y as f32);
163 if over != self.hover {
164 self.hover = over;
165 changed = true;
166 }
167 }
168 WindowEvent::MouseInput {
169 state: ElementState::Pressed,
170 button: MouseButton::Left,
171 ..
172 } => {
173 if self.hover {
174 self.active = !self.active;
175 self.click();
176 changed = true;
177 }
178 }
179 _ => {}
180 }
181
182 changed
183 }
184
185 fn is_focusable(&self) -> bool {
186 true
187 }
188
189 fn set_focus(&mut self, focused: bool) {
190 self.focus = focused;
191 }
192
193 fn activate(&mut self) {
194 self.active = !self.active;
195 self.click();
196 }
197
198 fn clear_active(&mut self) {
199 self.active = false;
200 }
201}