1use crate::prelude::*;
2use std::any::TypeId;
3
4pub(crate) struct ModalModel {
5 pub tooltip_visible: Signal<(bool, bool)>,
6 pub menu_visible: Signal<bool>,
7}
8
9pub enum ModalEvent {
11 ShowTooltip,
13 HideTooltip,
15 ShowMenu,
17 HideMenu,
19}
20
21impl Model for ModalModel {
22 fn event(&mut self, _cx: &mut EventContext, event: &mut Event) {
23 event.map(|modal_event, _| match modal_event {
24 ModalEvent::ShowTooltip => {
25 self.tooltip_visible.set((true, true));
26 }
27
28 ModalEvent::HideTooltip => {
29 self.tooltip_visible.set((false, true));
30 }
31
32 ModalEvent::ShowMenu => {
33 self.menu_visible.set(true);
34 }
35
36 ModalEvent::HideMenu => {
37 self.menu_visible.set(false);
38 }
39 });
40
41 event.map(|window_event, _| match window_event {
42 WindowEvent::MouseOver => {
43 if !self.tooltip_visible.get().0 {
44 self.tooltip_visible.set((true, true));
45 }
46 }
47 WindowEvent::MouseOut => self.tooltip_visible.set((false, true)),
48 WindowEvent::FocusIn => {
49 if !self.tooltip_visible.get().0 {
50 self.tooltip_visible.set((true, false));
51 }
52 }
53 WindowEvent::FocusOut => self.tooltip_visible.set((false, false)),
54 WindowEvent::FocusVisibility(vis) if !(*vis) => {
55 self.tooltip_visible.set((false, false))
56 }
57 WindowEvent::KeyDown(code, _) if *code == Code::Escape => {
58 self.tooltip_visible.set((false, false));
59 }
60 WindowEvent::PressDown { mouse: _ } => self.tooltip_visible.set((false, true)),
61 _ => {}
62 });
63 }
64}
65
66pub(crate) struct ActionsModel {
67 pub(crate) on_press: Option<Box<dyn Fn(&mut EventContext) + Send + Sync>>,
68 pub(crate) on_press_down: Option<Box<dyn Fn(&mut EventContext) + Send + Sync>>,
69 pub(crate) on_double_click: Option<Box<dyn Fn(&mut EventContext, MouseButton) + Send + Sync>>,
70 pub(crate) on_hover: Option<Box<dyn Fn(&mut EventContext) + Send + Sync>>,
71 pub(crate) on_hover_out: Option<Box<dyn Fn(&mut EventContext) + Send + Sync>>,
72 pub(crate) on_over: Option<Box<dyn Fn(&mut EventContext) + Send + Sync>>,
73 pub(crate) on_over_out: Option<Box<dyn Fn(&mut EventContext) + Send + Sync>>,
74 pub(crate) on_mouse_move: Option<Box<dyn Fn(&mut EventContext, f32, f32) + Send + Sync>>,
75 pub(crate) on_mouse_down: Option<Box<dyn Fn(&mut EventContext, MouseButton) + Send + Sync>>,
76 pub(crate) on_mouse_up: Option<Box<dyn Fn(&mut EventContext, MouseButton) + Send + Sync>>,
77 pub(crate) on_focus_in: Option<Box<dyn Fn(&mut EventContext) + Send + Sync>>,
78 pub(crate) on_focus_out: Option<Box<dyn Fn(&mut EventContext) + Send + Sync>>,
79 pub(crate) on_geo_changed: Option<Box<dyn Fn(&mut EventContext, GeoChanged) + Send + Sync>>,
80}
81
82impl ActionsModel {
83 pub(crate) fn new() -> Self {
84 Self {
85 on_press: None,
86 on_press_down: None,
87 on_double_click: None,
88 on_hover: None,
89 on_hover_out: None,
90 on_over: None,
91 on_over_out: None,
92 on_mouse_move: None,
93 on_mouse_down: None,
94 on_mouse_up: None,
95 on_focus_in: None,
96 on_focus_out: None,
97 on_geo_changed: None,
98 }
99 }
100}
101
102impl Model for ActionsModel {
103 fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
104 event.take(|actions_event, _| match actions_event {
105 ActionsEvent::OnPress(on_press) => {
106 self.on_press = Some(on_press);
107 }
108
109 ActionsEvent::OnPressDown(on_press_down) => {
110 self.on_press_down = Some(on_press_down);
111 }
112
113 ActionsEvent::OnDoubleClick(on_double_click) => {
114 self.on_double_click = Some(on_double_click);
115 }
116
117 ActionsEvent::OnHover(on_hover) => {
118 self.on_hover = Some(on_hover);
119 }
120
121 ActionsEvent::OnHoverOut(on_hover_out) => {
122 self.on_hover_out = Some(on_hover_out);
123 }
124
125 ActionsEvent::OnOver(on_over) => {
126 self.on_over = Some(on_over);
127 }
128
129 ActionsEvent::OnOverOut(on_over_out) => {
130 self.on_over_out = Some(on_over_out);
131 }
132
133 ActionsEvent::OnMouseMove(on_move) => {
134 self.on_mouse_move = Some(on_move);
135 }
136
137 ActionsEvent::OnMouseDown(on_mouse_down) => {
138 self.on_mouse_down = Some(on_mouse_down);
139 }
140
141 ActionsEvent::OnMouseUp(on_mouse_up) => {
142 self.on_mouse_up = Some(on_mouse_up);
143 }
144
145 ActionsEvent::OnFocusIn(on_focus_in) => {
146 self.on_focus_in = Some(on_focus_in);
147 }
148
149 ActionsEvent::OnFocusOut(on_focus_out) => {
150 self.on_focus_out = Some(on_focus_out);
151 }
152
153 ActionsEvent::OnGeoChanged(on_geo_changed) => {
154 self.on_geo_changed = Some(on_geo_changed);
155 cx.cache.set_bounds(cx.current, BoundingBox::default());
156 cx.needs_relayout();
157 }
158 });
159
160 event.map(|window_event, meta| match window_event {
161 WindowEvent::Press { mouse } => {
162 let over = if *mouse { cx.hovered() } else { cx.focused() };
163 if cx.current() != over && !over.is_descendant_of(cx.tree, cx.current()) {
164 return;
165 }
166
167 if !cx.is_disabled() && cx.current == meta.target {
168 let focusable = cx
177 .style
178 .abilities
179 .get(cx.current())
180 .is_some_and(|abilities| abilities.contains(Abilities::FOCUSABLE));
181 if focusable {
182 cx.focus();
183 }
184 if let Some(action) = &self.on_press {
185 (action)(cx);
186 }
187 }
188 }
189
190 WindowEvent::PressDown { mouse } => {
191 let over = if *mouse { cx.hovered() } else { cx.focused() };
192 if cx.current() != over && !over.is_descendant_of(cx.tree, cx.current()) {
193 return;
194 }
195 if !cx.is_disabled() && cx.current == meta.target {
196 if let Some(action) = &self.on_press_down {
197 (action)(cx);
198 }
199 }
200 }
201
202 WindowEvent::MouseDoubleClick(button) => {
203 if meta.target == cx.current && !cx.is_disabled() {
204 if let Some(action) = &self.on_double_click {
205 (action)(cx, *button);
206 }
207 }
208 }
209
210 WindowEvent::MouseEnter => {
211 if meta.target == cx.current() {
212 if let Some(action) = &self.on_hover {
213 (action)(cx);
214 }
215 }
216 }
217
218 WindowEvent::MouseLeave => {
219 if meta.target == cx.current() {
220 if let Some(action) = &self.on_hover_out {
221 (action)(cx);
222 }
223 }
224 }
225
226 WindowEvent::MouseOver => {
227 if let Some(action) = &self.on_over {
228 (action)(cx);
229 }
230 }
231
232 WindowEvent::MouseOut => {
233 if let Some(action) = &self.on_over_out {
235 (action)(cx);
236 }
237 }
239
240 WindowEvent::MouseMove(x, y) => {
241 if let Some(action) = &self.on_mouse_move {
242 (action)(cx, *x, *y);
243 }
244 }
245
246 WindowEvent::MouseDown(mouse_button) => {
247 if let Some(action) = &self.on_mouse_down {
248 (action)(cx, *mouse_button);
249 }
250 }
251
252 WindowEvent::MouseUp(mouse_button) => {
253 if let Some(action) = &self.on_mouse_up {
254 (action)(cx, *mouse_button);
255 }
256 }
257
258 WindowEvent::FocusIn => {
259 if let Some(action) = &self.on_focus_in {
260 (action)(cx);
261 }
262 }
263
264 WindowEvent::FocusOut => {
265 if let Some(action) = &self.on_focus_out {
266 (action)(cx);
267 }
268 }
269
270 WindowEvent::GeometryChanged(geo) => {
271 if meta.target == cx.current() {
272 if let Some(action) = &self.on_geo_changed {
273 (action)(cx, *geo);
274 }
275 }
276 }
277
278 _ => {}
279 });
280 }
281}
282
283pub(crate) enum ActionsEvent {
284 OnPress(Box<dyn Fn(&mut EventContext) + Send + Sync>),
285 OnPressDown(Box<dyn Fn(&mut EventContext) + Send + Sync>),
286 OnDoubleClick(Box<dyn Fn(&mut EventContext, MouseButton) + Send + Sync>),
287 OnHover(Box<dyn Fn(&mut EventContext) + Send + Sync>),
288 OnHoverOut(Box<dyn Fn(&mut EventContext) + Send + Sync>),
289 OnOver(Box<dyn Fn(&mut EventContext) + Send + Sync>),
290 OnOverOut(Box<dyn Fn(&mut EventContext) + Send + Sync>),
291 OnMouseMove(Box<dyn Fn(&mut EventContext, f32, f32) + Send + Sync>),
292 OnMouseDown(Box<dyn Fn(&mut EventContext, MouseButton) + Send + Sync>),
293 OnMouseUp(Box<dyn Fn(&mut EventContext, MouseButton) + Send + Sync>),
294 OnFocusIn(Box<dyn Fn(&mut EventContext) + Send + Sync>),
295 OnFocusOut(Box<dyn Fn(&mut EventContext) + Send + Sync>),
296 OnGeoChanged(Box<dyn Fn(&mut EventContext, GeoChanged) + Send + Sync>),
297}
298
299pub trait ActionModifiers<V> {
301 fn on_press<F>(self, action: F) -> Self
312 where
313 F: 'static + Fn(&mut EventContext) + Send + Sync;
314
315 fn on_press_down<F>(self, action: F) -> Self
326 where
327 F: 'static + Fn(&mut EventContext) + Send + Sync;
328
329 fn on_double_click<F>(self, action: F) -> Self
338 where
339 F: 'static + Fn(&mut EventContext, MouseButton) + Send + Sync;
340
341 fn on_hover<F>(self, action: F) -> Self
351 where
352 F: 'static + Fn(&mut EventContext) + Send + Sync;
353
354 fn on_hover_out<F>(self, action: F) -> Self
364 where
365 F: 'static + Fn(&mut EventContext) + Send + Sync;
366
367 fn on_over<F>(self, action: F) -> Self
377 where
378 F: 'static + Fn(&mut EventContext) + Send + Sync;
379
380 fn on_over_out<F>(self, action: F) -> Self
390 where
391 F: 'static + Fn(&mut EventContext) + Send + Sync;
392
393 fn on_mouse_move<F>(self, action: F) -> Self
402 where
403 F: 'static + Fn(&mut EventContext, f32, f32) + Send + Sync;
404
405 fn on_mouse_down<F>(self, action: F) -> Self
415 where
416 F: 'static + Fn(&mut EventContext, MouseButton) + Send + Sync;
417
418 fn on_mouse_up<F>(self, action: F) -> Self
428 where
429 F: 'static + Fn(&mut EventContext, MouseButton) + Send + Sync;
430
431 fn on_focus_in<F>(self, action: F) -> Self
440 where
441 F: 'static + Fn(&mut EventContext) + Send + Sync;
442
443 fn on_focus_out<F>(self, action: F) -> Self
452 where
453 F: 'static + Fn(&mut EventContext) + Send + Sync;
454
455 fn on_geo_changed<F>(self, action: F) -> Self
464 where
465 F: 'static + Fn(&mut EventContext, GeoChanged) + Send + Sync;
466
467 fn tooltip<C: Fn(&mut Context) -> Handle<'_, Tooltip> + 'static>(self, content: C) -> Self;
469
470 fn menu<C: FnOnce(&mut Context) -> Handle<'_, T>, T: View>(self, content: C) -> Self;
472}
473
474fn build_action_model(cx: &mut Context, entity: Entity) {
476 if cx.models.get(&entity).and_then(|models| models.get(&TypeId::of::<ActionsModel>())).is_none()
477 {
478 cx.with_current(entity, |cx| {
479 ActionsModel::new().build(cx);
480 });
481 }
482}
483
484fn build_modal_model(cx: &mut Context, entity: Entity) {
485 if cx.models.get(&entity).and_then(|models| models.get(&TypeId::of::<ModalModel>())).is_none() {
486 cx.with_current(entity, |cx| {
487 ModalModel {
488 tooltip_visible: Signal::new((false, true)),
489 menu_visible: Signal::new(false),
490 }
491 .build(cx);
492 });
493 }
494}
495
496impl<V: View> ActionModifiers<V> for Handle<'_, V> {
497 fn tooltip<C: Fn(&mut Context) -> Handle<'_, Tooltip> + 'static>(self, content: C) -> Self {
498 let entity = self.entity();
499
500 build_modal_model(self.cx, entity);
501
502 self.cx.with_current(entity, move |cx| {
503 let tooltip_visible = cx.data::<ModalModel>().tooltip_visible;
504 Binding::new(cx, tooltip_visible, move |cx| {
505 let tooltip_visible = tooltip_visible.get();
506 let tooltip_delay = cx.environment().tooltip_delay;
507 if tooltip_visible.0 {
508 (content)(cx)
509 .on_build(|cx| {
510 if tooltip_visible.1 {
511 cx.play_animation(
512 "tooltip_fade",
513 Duration::from_millis(100),
514 tooltip_delay,
515 )
516 }
517 })
518 .id(format!("{entity}"));
519 }
520 });
521 });
522
523 self.described_by(format!("{entity}"))
524 }
525
526 fn menu<C: FnOnce(&mut Context) -> Handle<'_, T>, T: View>(self, content: C) -> Self {
527 let entity = self.entity();
528
529 build_modal_model(self.cx, entity);
530
531 self.cx.with_current(entity, |cx| {
532 let menu_visible = cx.data::<ModalModel>().menu_visible;
533 (content)(cx).bind(menu_visible, move |mut handle| {
534 let is_visible = menu_visible.get();
535 handle = handle.toggle_class("vis", is_visible);
536
537 if is_visible {
538 handle.context().emit(WindowEvent::GeometryChanged(GeoChanged::empty()));
539 }
540 });
541 });
542
543 self
544 }
545
546 fn on_press<F>(mut self, action: F) -> Self
547 where
548 F: 'static + Fn(&mut EventContext) + Send + Sync,
549 {
550 self = self.hoverable(true);
551
552 if let Some(view) = self
553 .cx
554 .views
555 .get_mut(&self.entity)
556 .and_then(|view_handler| view_handler.downcast_mut::<Button>())
557 {
558 view.action = Some(Box::new(action));
559 return self;
560 }
561
562 build_action_model(self.cx, self.entity);
563
564 self.cx.emit_custom(
565 Event::new(ActionsEvent::OnPress(Box::new(action)))
566 .target(self.entity)
567 .origin(self.entity),
568 );
569
570 self
571 }
572
573 fn on_press_down<F>(self, action: F) -> Self
574 where
575 F: 'static + Fn(&mut EventContext) + Send + Sync,
576 {
577 build_action_model(self.cx, self.entity);
578
579 self.cx.emit_custom(
580 Event::new(ActionsEvent::OnPressDown(Box::new(action)))
581 .target(self.entity)
582 .origin(self.entity),
583 );
584
585 self
586 }
587
588 fn on_double_click<F>(self, action: F) -> Self
589 where
590 F: 'static + Fn(&mut EventContext, MouseButton) + Send + Sync,
591 {
592 build_action_model(self.cx, self.entity);
593
594 self.cx.emit_custom(
595 Event::new(ActionsEvent::OnDoubleClick(Box::new(action)))
596 .target(self.entity)
597 .origin(self.entity),
598 );
599
600 self
601 }
602
603 fn on_hover<F>(self, action: F) -> Self
604 where
605 F: 'static + Fn(&mut EventContext) + Send + Sync,
606 {
607 build_action_model(self.cx, self.entity);
608
609 self.cx.emit_custom(
610 Event::new(ActionsEvent::OnHover(Box::new(action)))
611 .target(self.entity)
612 .origin(self.entity),
613 );
614
615 self
616 }
617
618 fn on_hover_out<F>(self, action: F) -> Self
619 where
620 F: 'static + Fn(&mut EventContext) + Send + Sync,
621 {
622 build_action_model(self.cx, self.entity);
623
624 self.cx.emit_custom(
625 Event::new(ActionsEvent::OnHoverOut(Box::new(action)))
626 .target(self.entity)
627 .origin(self.entity),
628 );
629
630 self
631 }
632
633 fn on_over<F>(self, action: F) -> Self
634 where
635 F: 'static + Fn(&mut EventContext) + Send + Sync,
636 {
637 build_action_model(self.cx, self.entity);
638
639 self.cx.emit_custom(
640 Event::new(ActionsEvent::OnOver(Box::new(action)))
641 .target(self.entity)
642 .origin(self.entity),
643 );
644
645 self
646 }
647
648 fn on_over_out<F>(self, action: F) -> Self
649 where
650 F: 'static + Fn(&mut EventContext) + Send + Sync,
651 {
652 build_action_model(self.cx, self.entity);
653
654 self.cx.emit_custom(
655 Event::new(ActionsEvent::OnOverOut(Box::new(action)))
656 .target(self.entity)
657 .origin(self.entity),
658 );
659
660 self
661 }
662
663 fn on_mouse_move<F>(self, action: F) -> Self
664 where
665 F: 'static + Fn(&mut EventContext, f32, f32) + Send + Sync,
666 {
667 build_action_model(self.cx, self.entity);
668
669 self.cx.emit_custom(
670 Event::new(ActionsEvent::OnMouseMove(Box::new(action)))
671 .target(self.entity)
672 .origin(self.entity),
673 );
674
675 self
676 }
677
678 fn on_mouse_down<F>(self, action: F) -> Self
679 where
680 F: 'static + Fn(&mut EventContext, MouseButton) + Send + Sync,
681 {
682 build_action_model(self.cx, self.entity);
683
684 self.cx.emit_custom(
685 Event::new(ActionsEvent::OnMouseDown(Box::new(action)))
686 .target(self.entity)
687 .origin(self.entity),
688 );
689
690 self
691 }
692
693 fn on_mouse_up<F>(self, action: F) -> Self
694 where
695 F: 'static + Fn(&mut EventContext, MouseButton) + Send + Sync,
696 {
697 build_action_model(self.cx, self.entity);
698
699 self.cx.emit_custom(
700 Event::new(ActionsEvent::OnMouseUp(Box::new(action)))
701 .target(self.entity)
702 .origin(self.entity),
703 );
704
705 self
706 }
707
708 fn on_focus_in<F>(self, action: F) -> Self
709 where
710 F: 'static + Fn(&mut EventContext) + Send + Sync,
711 {
712 build_action_model(self.cx, self.entity);
713
714 self.cx.emit_custom(
715 Event::new(ActionsEvent::OnFocusIn(Box::new(action)))
716 .target(self.entity)
717 .origin(self.entity),
718 );
719
720 self
721 }
722
723 fn on_focus_out<F>(self, action: F) -> Self
724 where
725 F: 'static + Fn(&mut EventContext) + Send + Sync,
726 {
727 build_action_model(self.cx, self.entity);
728
729 self.cx.emit_custom(
730 Event::new(ActionsEvent::OnFocusOut(Box::new(action)))
731 .target(self.entity)
732 .origin(self.entity),
733 );
734
735 self
736 }
737
738 fn on_geo_changed<F>(self, action: F) -> Self
739 where
740 F: 'static + Fn(&mut EventContext, GeoChanged) + Send + Sync,
741 {
742 build_action_model(self.cx, self.entity);
743
744 self.cx.emit_custom(
745 Event::new(ActionsEvent::OnGeoChanged(Box::new(action)))
746 .target(self.entity)
747 .origin(self.entity),
748 );
749
750 self
751 }
752}