1use super::{FixedSizeWidget, Widget};
2use crate::{
3 app::App,
4 asset::{ButtonKind, IconId},
5 canvas_ext::CanvasExt,
6 event::{Event, MouseAction},
7};
8use pagurus::image::{Canvas, Sprite};
9use pagurus::{
10 Result,
11 spatial::{Contains, Position, Region, Size},
12};
13
14const DISABLED_ALPHA: u8 = 100;
15
16pub struct ButtonWidget {
17 region: Region,
18 kind: ButtonKind,
19 icon: IconId,
20 state: ButtonState,
21 disabled: Option<fn(&App) -> bool>,
22 number: Option<fn(&App) -> u32>,
23 number_margin: u32,
24 prev_state: ButtonState,
25 prev_disabled: bool,
26 prev_number: u32,
27}
28
29impl ButtonWidget {
30 pub fn new(kind: ButtonKind, icon: IconId) -> Self {
31 Self {
32 region: Region::default(),
33 kind,
34 icon,
35 state: ButtonState::default(),
36 disabled: None,
37 number: None,
38 number_margin: 0,
39 prev_state: ButtonState::default(),
40 prev_disabled: false,
41 prev_number: 0,
42 }
43 }
44
45 pub fn icon(&self) -> IconId {
46 self.icon
47 }
48
49 pub fn set_icon(&mut self, app: &mut App, icon: IconId) {
50 self.icon = icon;
51 app.request_redraw(self.region);
52 }
53
54 pub fn kind(&self) -> ButtonKind {
55 self.kind
56 }
57
58 pub fn state(&self) -> ButtonState {
59 self.state
60 }
61
62 pub fn set_kind(&mut self, kind: ButtonKind) {
63 self.kind = kind;
64 }
65
66 pub fn is_clicked(&self) -> bool {
67 self.state == ButtonState::Clicked
68 }
69
70 pub fn take_clicked(&mut self, app: &mut App) -> bool {
72 if self.state == ButtonState::Clicked {
73 app.request_redraw(self.region);
74 self.state = ButtonState::Focused;
75 true
76 } else {
77 false
78 }
79 }
80
81 pub fn with_disabled_callback(mut self, f: fn(&App) -> bool) -> Self {
82 self.set_disabled_callback(f);
83 self
84 }
85
86 pub fn set_disabled_callback(&mut self, f: fn(&App) -> bool) {
87 self.disabled = Some(f);
88 }
89
90 pub fn set_number_callback(&mut self, margin: u32, f: fn(&App) -> u32) {
91 self.number = Some(f);
92 self.number_margin = margin;
93 }
94
95 pub fn is_disabled(&self, app: &App) -> bool {
96 self.disabled.is_some_and(|f| f(app))
97 }
98
99 pub fn number(&self, app: &App) -> u32 {
100 self.number.map_or(0, |f| f(app))
101 }
102
103 fn render_number(&self, app: &App, canvas: &mut Canvas) {
104 let disabled = self.is_disabled(app);
105 let mut number = self.number(app);
106 let mut offset = Position::from_xy(
107 self.region.size.width as i32 - 18 - self.number_margin as i32,
108 self.region.size.height as i32 - 28,
109 );
110 let margin = 2;
111 loop {
112 let digit = (number % 10) as usize;
113 let sprite = &app.assets().digits_10x14[digit];
114 if disabled {
115 canvas
116 .offset(offset)
117 .draw_sprite_with_alpha(sprite, DISABLED_ALPHA);
118 } else {
119 canvas.offset(offset).draw_sprite(sprite);
120 }
121 offset.x -= sprite.size().width as i32 + margin;
122 number /= 10;
123 if number == 0 {
124 break;
125 }
126 }
127 }
128}
129
130impl std::fmt::Debug for ButtonWidget {
131 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132 write!(f, "ButtonWidget {{ .. }}")
133 }
134}
135
136impl Widget for ButtonWidget {
137 fn region(&self) -> Region {
138 self.region
139 }
140
141 fn render(&self, app: &App, canvas: &mut Canvas) {
142 let mut canvas = canvas.offset(self.region.position);
143 let disabled = self.is_disabled(app);
144
145 let button = self.state.get_sprite(app, self.kind);
146 if disabled {
147 canvas.draw_sprite_with_alpha(button, DISABLED_ALPHA);
148 } else {
149 canvas.draw_sprite(button);
150 }
151
152 let mut canvas = canvas.offset(self.state.offset(self.kind));
153 let icon = app.assets().get_icon(self.icon);
154 if disabled {
155 canvas.draw_sprite_with_alpha(icon, DISABLED_ALPHA);
156 } else {
157 canvas.draw_sprite(icon);
158 }
159
160 if self.number.is_some() {
161 self.render_number(app, &mut canvas);
162 }
163 }
164
165 fn handle_event_before(&mut self, app: &mut App) -> Result<()> {
166 self.prev_disabled = self.is_disabled(app);
167 self.prev_state = self.state;
168 self.prev_number = self.number(app);
169 Ok(())
170 }
171
172 fn handle_event_after(&mut self, app: &mut App) -> Result<()> {
173 let disabled = self.is_disabled(app);
174 if disabled {
175 self.state = ButtonState::Neutral;
176 }
177 let number = self.number(app);
178 if self.prev_disabled != disabled
179 || self.prev_state != self.state
180 || self.prev_number != number
181 {
182 self.prev_disabled = disabled;
183 self.prev_state = self.state;
184 self.prev_number = number;
185 app.request_redraw(self.region);
186 }
187
188 Ok(())
189 }
190
191 fn handle_event(&mut self, app: &mut App, event: &mut Event) -> Result<()> {
192 match event {
193 Event::Mouse {
194 consumed: false,
195 action,
196 position,
197 ..
198 } => {
199 let disabled = self.is_disabled(app);
200 if !disabled && self.region.contains(position) {
201 match action {
202 MouseAction::Move if self.state == ButtonState::Neutral => {
203 self.state = ButtonState::Focused;
204 }
205 MouseAction::Down => {
206 self.state = ButtonState::Pressed;
207 }
208 MouseAction::Up if self.state == ButtonState::Pressed => {
209 self.state = ButtonState::Clicked;
210 }
211 _ => {}
212 }
213 event.consume();
214 } else {
215 self.state = ButtonState::Neutral;
216 }
217 }
218 Event::Mouse { position, .. } => {
219 if !self.region.contains(position) {
220 self.state = ButtonState::Neutral;
221 }
222 }
223 _ => {}
224 }
225
226 Ok(())
227 }
228
229 fn children(&mut self) -> Vec<&mut dyn Widget> {
230 Vec::new()
231 }
232}
233
234impl FixedSizeWidget for ButtonWidget {
235 fn requiring_size(&self, _app: &App) -> Size {
236 self.kind.size()
237 }
238
239 fn set_position(&mut self, app: &App, position: Position) {
240 self.region = Region::new(position, self.requiring_size(app));
241 }
242}
243
244#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
245pub enum ButtonState {
246 #[default]
247 Neutral,
248 Focused,
249 Pressed,
250 Clicked,
251}
252
253impl ButtonState {
254 fn get_sprite(self, app: &App, kind: ButtonKind) -> &Sprite {
255 let button = app.assets().get_button(kind);
256 match self {
257 ButtonState::Neutral => &button.neutral,
258 ButtonState::Focused => &button.focused,
259 ButtonState::Pressed => &button.pressed,
260 ButtonState::Clicked => &button.pressed,
261 }
262 }
263
264 pub fn offset(self, kind: ButtonKind) -> Position {
265 let offset = Position::ORIGIN;
266 match kind {
267 ButtonKind::Basic => match self {
268 ButtonState::Neutral => offset,
269 ButtonState::Focused => offset.move_y(4),
270 ButtonState::Pressed => offset.move_y(8),
271 ButtonState::Clicked => offset.move_y(8),
272 },
273 ButtonKind::BasicPressed => offset.move_y(8),
274 ButtonKind::SliderLeft => match self {
275 ButtonState::Neutral => offset,
276 ButtonState::Focused => offset.move_y(2),
277 ButtonState::Pressed => offset.move_y(4),
278 ButtonState::Clicked => offset.move_y(4),
279 },
280 ButtonKind::SliderRight => match self {
281 ButtonState::Neutral => offset,
282 ButtonState::Focused => offset.move_y(2),
283 ButtonState::Pressed => offset.move_y(4),
284 ButtonState::Clicked => offset.move_y(4),
285 },
286 ButtonKind::Middle => match self {
287 ButtonState::Neutral => offset,
288 ButtonState::Focused => offset.move_y(2),
289 ButtonState::Pressed => offset.move_y(4),
290 ButtonState::Clicked => offset.move_y(4),
291 },
292 }
293 }
294}