1use super::internal::button_presenter::{
2 ButtonPresenter, ButtonTemplate, ButtonVisualState, DEFAULT_BUTTON_TEMPLATE,
3};
4use super::*;
5use crate::ffi::CursorStyle;
6use crate::node::{TextNode, WeakFlexBox};
7use crate::theme::subscribe;
8use crate::{focus_adorner, focus_visibility};
9use std::rc::Rc;
10
11#[derive(Clone)]
12pub struct Button {
13 root: FlexBox,
14 presenter: Rc<RefCell<Rc<dyn ButtonPresenter>>>,
15 label: Rc<RefCell<TextNode>>,
16 template: Rc<RefCell<Option<Rc<dyn ButtonTemplate>>>>,
17 label_value: Rc<RefCell<String>>,
18 click: Rc<RefCell<Option<ClickCallback>>>,
19 hovered_state: Rc<Cell<bool>>,
20 pressed_state: Rc<Cell<bool>>,
21 focused_state: Rc<Cell<bool>>,
22 keyboard_armed_key: Rc<RefCell<Option<String>>>,
23 font_family_override: Rc<RefCell<Option<crate::FontFamily>>>,
24 font_size_override: Rc<Cell<Option<f32>>>,
25 text_color_override: Rc<Cell<Option<u32>>>,
26 colors_value: Rc<Cell<Option<ButtonColors>>>,
27}
28
29impl Button {
30 pub fn new(label: impl Into<String>) -> Self {
31 let label = label.into();
32 let root = row();
33 let presenter = create_button_presenter(None);
34 let label_node = presenter.label_node();
35 label_node.text(label.clone());
36 root.interactive(true)
37 .focusable(true, 0)
38 .semantic_role(SemanticRole::Button)
39 .semantic_label(label.clone())
40 .reflect_semantic_disabled_from_enabled()
41 .child(&presenter.content_root());
42
43 let control = Self {
44 root,
45 presenter: Rc::new(RefCell::new(presenter)),
46 label: Rc::new(RefCell::new(label_node)),
47 template: Rc::new(RefCell::new(None)),
48 label_value: Rc::new(RefCell::new(label.clone())),
49 click: Rc::new(RefCell::new(None)),
50 hovered_state: Rc::new(Cell::new(false)),
51 pressed_state: Rc::new(Cell::new(false)),
52 focused_state: Rc::new(Cell::new(false)),
53 keyboard_armed_key: Rc::new(RefCell::new(None)),
54 font_family_override: Rc::new(RefCell::new(None)),
55 font_size_override: Rc::new(Cell::new(None)),
56 text_color_override: Rc::new(Cell::new(None)),
57 colors_value: Rc::new(Cell::new(None)),
58 };
59 control.install_visual_subscriptions();
60 control.sync_visual_state();
61 control.sync_focus_chrome();
62 control.bind_events();
63 control
64 }
65
66 fn bind_events(&self) {
67 let event_target = self.event_target();
68 self.root.on_pointer_enter(move |_event| {
69 event_target.set_hovered(true);
70 });
71 let event_target = self.event_target();
72 self.root.on_pointer_leave(move |_event| {
73 event_target.set_hovered(false);
74 if event_target.keyboard_armed_key.borrow().is_none() {
75 event_target.cancel_press();
76 }
77 });
78 let event_target = self.event_target();
79 self.root.on_pointer_down(move |_event| {
80 event_target.set_hovered(true);
81 event_target.begin_press();
82 });
83 let event_target = self.event_target();
84 self.root.on_pointer_up(move |_event| {
85 if event_target.pressed_state.get()
86 && event_target.keyboard_armed_key.borrow().is_none()
87 {
88 event_target.end_press();
89 }
90 });
91
92 let event_target = self.event_target();
93 self.root.on_pointer_click(move |event| {
94 fire_click_callback(&event_target.click);
95 event.handled = true;
96 });
97 let event_target = self.event_target();
98 self.root.on_key_down(move |event| {
99 event_target.sync_focus_chrome();
100 if !event_target.is_enabled() || event.modifiers != 0 || !is_activation_key(event) {
101 return;
102 }
103 if event_target.keyboard_armed_key.borrow().is_some() {
104 event.handled = true;
105 return;
106 }
107 *event_target.keyboard_armed_key.borrow_mut() = Some(event.key.clone());
108 event_target.begin_press();
109 event.handled = true;
110 });
111 let event_target = self.event_target();
112 self.root.on_key_up(move |event| {
113 if !event_target.is_enabled() || event.modifiers != 0 || !is_activation_key(event) {
114 return;
115 }
116 let armed = event_target.keyboard_armed_key.borrow().clone();
117 if armed.as_deref() == Some(event.key.as_str()) {
118 *event_target.keyboard_armed_key.borrow_mut() = None;
119 event_target.end_press();
120 fire_click_callback(&event_target.click);
121 event.handled = true;
122 }
123 });
124 let event_target = self.event_target();
125 self.root.on_focus_changed(move |event| {
126 if !event.focused && event_target.keyboard_armed_key.borrow().is_some() {
127 *event_target.keyboard_armed_key.borrow_mut() = None;
128 event_target.cancel_press();
129 }
130 if event_target.focused_state.get() != event.focused {
131 event_target.focused_state.set(event.focused);
132 event_target.sync_visual_state();
133 event_target.sync_focus_chrome();
134 }
135 });
136 }
137
138 pub fn text(&self, value: impl Into<String>) -> &Self {
139 let value = value.into();
140 self.label_value.replace(value.clone());
141 self.label.borrow().text(value.clone());
142 self.root.semantic_label(value);
143 self
144 }
145
146 pub fn template(&self, template: Rc<dyn ButtonTemplate>) -> &Self {
147 self.set_template(Some(template))
148 }
149
150 pub fn clear_template(&self) -> &Self {
151 self.set_template(None)
152 }
153
154 fn set_template(&self, template: Option<Rc<dyn ButtonTemplate>>) -> &Self {
155 self.template.replace(template.clone());
156 self.replace_presenter(create_button_presenter(template));
157 self
158 }
159
160 pub fn colors(&self, colors: ButtonColors) -> &Self {
161 self.set_colors(Some(colors))
162 }
163
164 pub fn clear_colors(&self) -> &Self {
165 self.set_colors(None)
166 }
167
168 fn set_colors(&self, colors: Option<ButtonColors>) -> &Self {
169 self.colors_value.set(colors);
170 self.sync_visual_state();
171 self
172 }
173
174 pub fn enabled(&self, enabled: bool) -> &Self {
175 self.root.enabled(enabled);
176 if !enabled {
177 self.hovered_state.set(false);
178 self.focused_state.set(false);
179 *self.keyboard_armed_key.borrow_mut() = None;
180 self.cancel_press();
181 }
182 self.sync_visual_state();
183 self.sync_focus_chrome();
184 self
185 }
186
187 fn set_explicit_text_color(&self, color: u32) {
188 self.text_color_override.set(Some(color));
189 self.sync_visual_state();
190 }
191
192 fn set_explicit_font_family(&self, family: crate::FontFamily) {
193 self.font_family_override.replace(Some(family.clone()));
194 self.label.borrow().font_family(family);
195 }
196
197 fn set_explicit_font_size(&self, size: f32) {
198 self.font_size_override.set(Some(size));
199 self.label.borrow().font_size(size);
200 }
201
202 fn install_visual_subscriptions(&self) {
203 let event_target = self.event_target();
204 let theme_guard = subscribe(move |_theme| {
205 event_target.sync_visual_state();
206 event_target.sync_focus_chrome();
207 });
208 self.root
209 .retained_node_ref()
210 .retain_attachment(Rc::new(theme_guard));
211
212 let event_target = self.event_target();
213 let focus_guard = focus_visibility::subscribe(move |_visible| {
214 event_target.sync_focus_chrome();
215 });
216 self.root
217 .retained_node_ref()
218 .retain_attachment(Rc::new(focus_guard));
219 }
220
221 fn event_target(&self) -> ButtonEventTarget {
222 ButtonEventTarget {
223 weak_root: self.root.downgrade(),
224 presenter: self.presenter.clone(),
225 label: self.label.clone(),
226 template: self.template.clone(),
227 label_value: self.label_value.clone(),
228 click: self.click.clone(),
229 hovered_state: self.hovered_state.clone(),
230 pressed_state: self.pressed_state.clone(),
231 focused_state: self.focused_state.clone(),
232 keyboard_armed_key: self.keyboard_armed_key.clone(),
233 font_family_override: self.font_family_override.clone(),
234 font_size_override: self.font_size_override.clone(),
235 text_color_override: self.text_color_override.clone(),
236 colors_value: self.colors_value.clone(),
237 }
238 }
239
240 pub(crate) fn is_enabled(&self) -> bool {
241 self.root.retained_node_ref().is_enabled_for_routing()
242 }
243
244 pub(crate) fn begin_press(&self) {
245 self.pressed_state.set(true);
246 self.sync_visual_state();
247 self.sync_focus_chrome();
248 }
249
250 pub(crate) fn end_press(&self, activate: bool) {
251 self.pressed_state.set(false);
252 self.sync_visual_state();
253 self.sync_focus_chrome();
254 if activate {
255 fire_click_callback(&self.click);
256 }
257 }
258
259 pub(crate) fn cancel_press(&self) {
260 if self.pressed_state.replace(false) {
261 self.sync_visual_state();
262 }
263 self.sync_focus_chrome();
264 }
265
266 fn sync_visual_state(&self) {
267 sync_button_visual_state(
268 &self.root,
269 &self.presenter,
270 self.hovered_state.get(),
271 self.pressed_state.get(),
272 self.focused_state.get(),
273 self.is_enabled(),
274 self.font_family_override.borrow().clone(),
275 self.font_size_override.get(),
276 self.text_color_override.get(),
277 self.colors_value.get(),
278 );
279 }
280
281 fn sync_focus_chrome(&self) {
282 sync_button_focus_chrome(&self.root, self.focused_state.get(), self.is_enabled());
283 }
284
285 fn replace_presenter(&self, next_presenter: Rc<dyn ButtonPresenter>) {
286 let previous_presenter = self.presenter.borrow().clone();
287 if Rc::ptr_eq(&previous_presenter, &next_presenter) {
288 return;
289 }
290 next_presenter
291 .label_node()
292 .text(self.label_value.borrow().clone());
293 self.root.child(&next_presenter.content_root());
294 self.root.remove_child(&previous_presenter.content_root());
295 previous_presenter.content_root().dispose();
296 self.label.replace(next_presenter.label_node());
297 self.presenter.replace(next_presenter);
298 self.sync_visual_state();
299 self.sync_focus_chrome();
300 }
301}
302
303impl Clickable for Button {
304 fn on_click(&self, handler: impl Fn(ClickEventArgs) + 'static) -> &Self {
305 *self.click.borrow_mut() = Some(Rc::new(handler));
306 self
307 }
308}
309
310impl Node for Button {
311 fn retained_node_ref(&self) -> NodeRef {
312 self.root.retained_node_ref()
313 }
314
315 fn build_self(&self) {
316 self.sync_visual_state();
317 self.sync_focus_chrome();
318 self.root.build_self();
319 }
320}
321
322impl HasFlexBoxRoot for Button {
323 fn flex_box_root(&self) -> &FlexBox {
324 &self.root
325 }
326}
327
328impl ThemeBindable for Button {
329 fn theme_binding_node(&self) -> NodeRef {
330 self.root.retained_node_ref()
331 }
332
333 fn weak_theme_target(&self) -> Box<dyn Fn() -> Option<Self>> {
334 let target = self.event_target();
335 Box::new(move || target.upgrade())
336 }
337}
338
339impl LabeledControlTextStyle for Button {
340 fn set_label_font_family(&self, family: crate::FontFamily) {
341 self.set_explicit_font_family(family);
342 }
343
344 fn set_label_font_size(&self, size: f32) {
345 self.set_explicit_font_size(size);
346 }
347
348 fn set_label_text_color(&self, color: u32) {
349 self.set_explicit_text_color(color);
350 }
351}
352
353#[allow(clippy::too_many_arguments)]
354fn sync_button_visual_state(
355 root: &FlexBox,
356 presenter: &Rc<RefCell<Rc<dyn ButtonPresenter>>>,
357 hovered: bool,
358 pressed: bool,
359 focused: bool,
360 enabled: bool,
361 font_family_override: Option<crate::FontFamily>,
362 font_size_override: Option<f32>,
363 text_color_override: Option<u32>,
364 colors: Option<ButtonColors>,
365) {
366 let presenter = presenter.borrow().clone();
367 let theme = current_theme();
368 let state = ButtonVisualState {
369 hovered,
370 pressed,
371 focused,
372 enabled,
373 };
374 let mut host_style = presenter.present(&theme, &state, colors.as_ref());
375 host_style.cursor = Some(if enabled {
376 CursorStyle::Pointer
377 } else {
378 CursorStyle::Default
379 });
380 host_style.opacity = Some(if enabled { 1.0 } else { 0.38 });
381 root.apply_presenter_style(host_style);
382 if let Some(family) = font_family_override {
383 presenter.label_node().font_family(family);
384 }
385 if let Some(size) = font_size_override {
386 presenter.label_node().font_size(size);
387 }
388 if let Some(color) = text_color_override {
389 presenter.label_node().text_color(color);
390 }
391}
392
393fn sync_button_focus_chrome(root: &FlexBox, focused: bool, enabled: bool) {
394 if focused && enabled && focus_visibility::keyboard_focus_visible() {
395 let corners = root
396 .resolved_host_style()
397 .corners
398 .unwrap_or_else(|| crate::Corners::all(current_theme().spacing.sm));
399 focus_adorner::show_standard_corners(
400 root,
401 corners.top_left,
402 corners.top_right,
403 corners.bottom_right,
404 corners.bottom_left,
405 );
406 return;
407 }
408 focus_adorner::hide_owner(root);
409}
410
411fn create_button_presenter(template: Option<Rc<dyn ButtonTemplate>>) -> Rc<dyn ButtonPresenter> {
412 if let Some(template) = template {
413 return template.create();
414 }
415 if let Some(template_set) = get_control_templates() {
416 if let Some(template) = template_set.button {
417 return template.create();
418 }
419 }
420 DEFAULT_BUTTON_TEMPLATE.create()
421}
422
423#[derive(Clone)]
424struct ButtonEventTarget {
425 weak_root: WeakFlexBox,
426 presenter: Rc<RefCell<Rc<dyn ButtonPresenter>>>,
427 label: Rc<RefCell<TextNode>>,
428 template: Rc<RefCell<Option<Rc<dyn ButtonTemplate>>>>,
429 label_value: Rc<RefCell<String>>,
430 click: Rc<RefCell<Option<ClickCallback>>>,
431 hovered_state: Rc<Cell<bool>>,
432 pressed_state: Rc<Cell<bool>>,
433 focused_state: Rc<Cell<bool>>,
434 keyboard_armed_key: Rc<RefCell<Option<String>>>,
435 font_family_override: Rc<RefCell<Option<crate::FontFamily>>>,
436 font_size_override: Rc<Cell<Option<f32>>>,
437 text_color_override: Rc<Cell<Option<u32>>>,
438 colors_value: Rc<Cell<Option<ButtonColors>>>,
439}
440
441impl ButtonEventTarget {
442 fn upgrade(&self) -> Option<Button> {
443 Some(Button {
444 root: self.weak_root.upgrade()?,
445 presenter: self.presenter.clone(),
446 label: self.label.clone(),
447 template: self.template.clone(),
448 label_value: self.label_value.clone(),
449 click: self.click.clone(),
450 hovered_state: self.hovered_state.clone(),
451 pressed_state: self.pressed_state.clone(),
452 focused_state: self.focused_state.clone(),
453 keyboard_armed_key: self.keyboard_armed_key.clone(),
454 font_family_override: self.font_family_override.clone(),
455 font_size_override: self.font_size_override.clone(),
456 text_color_override: self.text_color_override.clone(),
457 colors_value: self.colors_value.clone(),
458 })
459 }
460
461 fn is_enabled(&self) -> bool {
462 self.weak_root
463 .upgrade()
464 .is_some_and(|root| root.retained_node_ref().is_enabled_for_routing())
465 }
466
467 fn sync_visual_state(&self) {
468 let Some(root) = self.weak_root.upgrade() else {
469 return;
470 };
471 sync_button_visual_state(
472 &root,
473 &self.presenter,
474 self.hovered_state.get(),
475 self.pressed_state.get(),
476 self.focused_state.get(),
477 root.retained_node_ref().is_enabled_for_routing(),
478 self.font_family_override.borrow().clone(),
479 self.font_size_override.get(),
480 self.text_color_override.get(),
481 self.colors_value.get(),
482 );
483 }
484
485 fn sync_focus_chrome(&self) {
486 let Some(root) = self.weak_root.upgrade() else {
487 return;
488 };
489 sync_button_focus_chrome(
490 &root,
491 self.focused_state.get(),
492 root.retained_node_ref().is_enabled_for_routing(),
493 );
494 }
495
496 fn begin_press(&self) {
497 self.pressed_state.set(true);
498 self.sync_visual_state();
499 self.sync_focus_chrome();
500 }
501
502 fn end_press(&self) {
503 self.pressed_state.set(false);
504 self.sync_visual_state();
505 self.sync_focus_chrome();
506 }
507
508 fn cancel_press(&self) {
509 if self.pressed_state.replace(false) {
510 self.sync_visual_state();
511 }
512 self.sync_focus_chrome();
513 }
514
515 fn set_hovered(&self, hovered: bool) {
516 if self.hovered_state.replace(hovered) != hovered {
517 self.sync_visual_state();
518 }
519 self.sync_focus_chrome();
520 }
521}