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::MouseDown(MouseButton::Right) => {
43 self.menu_visible.set(true);
44 }
45 WindowEvent::MouseOver => {
46 if !self.tooltip_visible.get().0 {
47 self.tooltip_visible.set((true, true));
48 }
49 }
50 WindowEvent::MouseOut => self.tooltip_visible.set((false, true)),
51 WindowEvent::FocusIn => {
52 if !self.tooltip_visible.get().0 {
53 self.tooltip_visible.set((true, false));
54 }
55 }
56 WindowEvent::FocusOut => self.tooltip_visible.set((false, false)),
57 WindowEvent::FocusVisibility(vis) if !(*vis) => {
58 self.tooltip_visible.set((false, false))
59 }
60 WindowEvent::KeyDown(code, _) if *code == Code::Escape => {
61 self.tooltip_visible.set((false, false));
62 self.menu_visible.set(false);
63 }
64 WindowEvent::PressDown { mouse: _ } => self.tooltip_visible.set((false, true)),
65 _ => {}
66 });
67 }
68}
69
70pub(crate) struct ActionsModel {
71 pub(crate) on_press: Option<Box<dyn Fn(&mut EventContext) + Send + Sync>>,
72 pub(crate) on_press_down: Option<Box<dyn Fn(&mut EventContext) + Send + Sync>>,
73 pub(crate) on_double_click: Option<Box<dyn Fn(&mut EventContext, MouseButton) + Send + Sync>>,
74 pub(crate) on_hover: Option<Box<dyn Fn(&mut EventContext) + Send + Sync>>,
75 pub(crate) on_hover_out: Option<Box<dyn Fn(&mut EventContext) + Send + Sync>>,
76 pub(crate) on_over: Option<Box<dyn Fn(&mut EventContext) + Send + Sync>>,
77 pub(crate) on_over_out: Option<Box<dyn Fn(&mut EventContext) + Send + Sync>>,
78 pub(crate) on_mouse_move: Option<Box<dyn Fn(&mut EventContext, f32, f32) + Send + Sync>>,
79 pub(crate) on_mouse_down: Option<Box<dyn Fn(&mut EventContext, MouseButton) + Send + Sync>>,
80 pub(crate) on_mouse_up: Option<Box<dyn Fn(&mut EventContext, MouseButton) + Send + Sync>>,
81 pub(crate) on_focus_in: Option<Box<dyn Fn(&mut EventContext) + Send + Sync>>,
82 pub(crate) on_focus_out: Option<Box<dyn Fn(&mut EventContext) + Send + Sync>>,
83 pub(crate) on_geo_changed: Option<Box<dyn Fn(&mut EventContext, GeoChanged) + Send + Sync>>,
84}
85
86impl ActionsModel {
87 pub(crate) fn new() -> Self {
88 Self {
89 on_press: None,
90 on_press_down: None,
91 on_double_click: None,
92 on_hover: None,
93 on_hover_out: None,
94 on_over: None,
95 on_over_out: None,
96 on_mouse_move: None,
97 on_mouse_down: None,
98 on_mouse_up: None,
99 on_focus_in: None,
100 on_focus_out: None,
101 on_geo_changed: None,
102 }
103 }
104}
105
106impl Model for ActionsModel {
107 fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
108 event.take(|actions_event, _| match actions_event {
109 ActionsEvent::OnPress(on_press) => {
110 self.on_press = Some(on_press);
111 }
112
113 ActionsEvent::OnPressDown(on_press_down) => {
114 self.on_press_down = Some(on_press_down);
115 }
116
117 ActionsEvent::OnDoubleClick(on_double_click) => {
118 self.on_double_click = Some(on_double_click);
119 }
120
121 ActionsEvent::OnHover(on_hover) => {
122 self.on_hover = Some(on_hover);
123 }
124
125 ActionsEvent::OnHoverOut(on_hover_out) => {
126 self.on_hover_out = Some(on_hover_out);
127 }
128
129 ActionsEvent::OnOver(on_over) => {
130 self.on_over = Some(on_over);
131 }
132
133 ActionsEvent::OnOverOut(on_over_out) => {
134 self.on_over_out = Some(on_over_out);
135 }
136
137 ActionsEvent::OnMouseMove(on_move) => {
138 self.on_mouse_move = Some(on_move);
139 }
140
141 ActionsEvent::OnMouseDown(on_mouse_down) => {
142 self.on_mouse_down = Some(on_mouse_down);
143 }
144
145 ActionsEvent::OnMouseUp(on_mouse_up) => {
146 self.on_mouse_up = Some(on_mouse_up);
147 }
148
149 ActionsEvent::OnFocusIn(on_focus_in) => {
150 self.on_focus_in = Some(on_focus_in);
151 }
152
153 ActionsEvent::OnFocusOut(on_focus_out) => {
154 self.on_focus_out = Some(on_focus_out);
155 }
156
157 ActionsEvent::OnGeoChanged(on_geo_changed) => {
158 self.on_geo_changed = Some(on_geo_changed);
159 cx.cache.set_bounds(cx.current, BoundingBox::default());
160 cx.needs_relayout();
161 }
162 });
163
164 event.map(|window_event, meta| match window_event {
165 WindowEvent::Press { mouse } => {
166 let over = if *mouse { cx.hovered() } else { cx.focused() };
167 if cx.current() != over && !over.is_descendant_of(cx.tree, cx.current()) {
168 return;
169 }
170
171 if !cx.is_disabled() && cx.current == meta.target {
172 let focusable = cx
181 .style
182 .abilities
183 .get(cx.current())
184 .is_some_and(|abilities| abilities.contains(Abilities::FOCUSABLE));
185 if focusable {
186 cx.focus();
187 }
188 if let Some(action) = &self.on_press {
189 (action)(cx);
190 }
191 }
192 }
193
194 WindowEvent::PressDown { mouse } => {
195 let over = if *mouse { cx.hovered() } else { cx.focused() };
196 if cx.current() != over && !over.is_descendant_of(cx.tree, cx.current()) {
197 return;
198 }
199 if !cx.is_disabled() && cx.current == meta.target {
200 if let Some(action) = &self.on_press_down {
201 (action)(cx);
202 }
203 }
204 }
205
206 WindowEvent::MouseDoubleClick(button) => {
207 if meta.target == cx.current && !cx.is_disabled() {
208 if let Some(action) = &self.on_double_click {
209 (action)(cx, *button);
210 }
211 }
212 }
213
214 WindowEvent::MouseEnter => {
215 if meta.target == cx.current() {
216 if let Some(action) = &self.on_hover {
217 (action)(cx);
218 }
219 }
220 }
221
222 WindowEvent::MouseLeave => {
223 if meta.target == cx.current() {
224 if let Some(action) = &self.on_hover_out {
225 (action)(cx);
226 }
227 }
228 }
229
230 WindowEvent::MouseOver => {
231 if let Some(action) = &self.on_over {
232 (action)(cx);
233 }
234 }
235
236 WindowEvent::MouseOut => {
237 if let Some(action) = &self.on_over_out {
239 (action)(cx);
240 }
241 }
243
244 WindowEvent::MouseMove(x, y) => {
245 if let Some(action) = &self.on_mouse_move {
246 (action)(cx, *x, *y);
247 }
248 }
249
250 WindowEvent::MouseDown(mouse_button) => {
251 if let Some(action) = &self.on_mouse_down {
252 (action)(cx, *mouse_button);
253 }
254 }
255
256 WindowEvent::MouseUp(mouse_button) => {
257 if let Some(action) = &self.on_mouse_up {
258 (action)(cx, *mouse_button);
259 }
260 }
261
262 WindowEvent::FocusIn => {
263 if let Some(action) = &self.on_focus_in {
264 (action)(cx);
265 }
266 }
267
268 WindowEvent::FocusOut => {
269 if let Some(action) = &self.on_focus_out {
270 (action)(cx);
271 }
272 }
273
274 WindowEvent::GeometryChanged(geo) => {
275 if meta.target == cx.current() {
276 if let Some(action) = &self.on_geo_changed {
277 (action)(cx, *geo);
278 }
279 }
280 }
281
282 _ => {}
283 });
284 }
285}
286
287pub(crate) enum ActionsEvent {
288 OnPress(Box<dyn Fn(&mut EventContext) + Send + Sync>),
289 OnPressDown(Box<dyn Fn(&mut EventContext) + Send + Sync>),
290 OnDoubleClick(Box<dyn Fn(&mut EventContext, MouseButton) + Send + Sync>),
291 OnHover(Box<dyn Fn(&mut EventContext) + Send + Sync>),
292 OnHoverOut(Box<dyn Fn(&mut EventContext) + Send + Sync>),
293 OnOver(Box<dyn Fn(&mut EventContext) + Send + Sync>),
294 OnOverOut(Box<dyn Fn(&mut EventContext) + Send + Sync>),
295 OnMouseMove(Box<dyn Fn(&mut EventContext, f32, f32) + Send + Sync>),
296 OnMouseDown(Box<dyn Fn(&mut EventContext, MouseButton) + Send + Sync>),
297 OnMouseUp(Box<dyn Fn(&mut EventContext, MouseButton) + Send + Sync>),
298 OnFocusIn(Box<dyn Fn(&mut EventContext) + Send + Sync>),
299 OnFocusOut(Box<dyn Fn(&mut EventContext) + Send + Sync>),
300 OnGeoChanged(Box<dyn Fn(&mut EventContext, GeoChanged) + Send + Sync>),
301}
302
303pub trait ActionModifiers<V> {
305 fn on_press<F>(self, action: F) -> Self
316 where
317 F: 'static + Fn(&mut EventContext) + Send + Sync;
318
319 fn on_press_down<F>(self, action: F) -> Self
330 where
331 F: 'static + Fn(&mut EventContext) + Send + Sync;
332
333 fn on_double_click<F>(self, action: F) -> Self
342 where
343 F: 'static + Fn(&mut EventContext, MouseButton) + Send + Sync;
344
345 fn on_hover<F>(self, action: F) -> Self
355 where
356 F: 'static + Fn(&mut EventContext) + Send + Sync;
357
358 fn on_hover_out<F>(self, action: F) -> Self
368 where
369 F: 'static + Fn(&mut EventContext) + Send + Sync;
370
371 fn on_over<F>(self, action: F) -> Self
381 where
382 F: 'static + Fn(&mut EventContext) + Send + Sync;
383
384 fn on_over_out<F>(self, action: F) -> Self
394 where
395 F: 'static + Fn(&mut EventContext) + Send + Sync;
396
397 fn on_mouse_move<F>(self, action: F) -> Self
406 where
407 F: 'static + Fn(&mut EventContext, f32, f32) + Send + Sync;
408
409 fn on_mouse_down<F>(self, action: F) -> Self
419 where
420 F: 'static + Fn(&mut EventContext, MouseButton) + Send + Sync;
421
422 fn on_mouse_up<F>(self, action: F) -> Self
432 where
433 F: 'static + Fn(&mut EventContext, MouseButton) + Send + Sync;
434
435 fn on_focus_in<F>(self, action: F) -> Self
444 where
445 F: 'static + Fn(&mut EventContext) + Send + Sync;
446
447 fn on_focus_out<F>(self, action: F) -> Self
456 where
457 F: 'static + Fn(&mut EventContext) + Send + Sync;
458
459 fn on_geo_changed<F>(self, action: F) -> Self
468 where
469 F: 'static + Fn(&mut EventContext, GeoChanged) + Send + Sync;
470
471 fn tooltip<C: Fn(&mut Context) -> Handle<'_, Tooltip> + 'static>(self, content: C) -> Self;
473
474 fn menu<C: Fn(&mut Context) -> Handle<'_, Popover> + 'static>(self, content: C) -> Self;
476}
477
478fn build_action_model(cx: &mut Context, entity: Entity) {
480 if cx.models.get(&entity).and_then(|models| models.get(&TypeId::of::<ActionsModel>())).is_none()
481 {
482 cx.with_current(entity, |cx| {
483 ActionsModel::new().build(cx);
484 });
485 }
486}
487
488fn build_modal_model(cx: &mut Context, entity: Entity) {
489 if cx.models.get(&entity).and_then(|models| models.get(&TypeId::of::<ModalModel>())).is_none() {
490 cx.with_current(entity, |cx| {
491 ModalModel {
492 tooltip_visible: Signal::new((false, true)),
493 menu_visible: Signal::new(false),
494 }
495 .build(cx);
496 });
497 }
498}
499
500impl<V: View> ActionModifiers<V> for Handle<'_, V> {
501 fn tooltip<C: Fn(&mut Context) -> Handle<'_, Tooltip> + 'static>(self, content: C) -> Self {
502 let entity = self.entity();
503
504 build_modal_model(self.cx, entity);
505
506 self.cx.with_current(entity, move |cx| {
507 let tooltip_visible = cx.data::<ModalModel>().tooltip_visible;
508 Binding::new(cx, tooltip_visible, move |cx| {
509 let tooltip_visible = tooltip_visible.get();
510 let tooltip_delay = cx.environment().tooltip_delay;
511 if tooltip_visible.0 {
512 (content)(cx)
513 .on_build(|cx| {
514 if tooltip_visible.1 {
515 cx.play_animation(
516 "tooltip_fade",
517 Duration::from_millis(100),
518 tooltip_delay,
519 )
520 }
521 })
522 .id(format!("{entity}"));
523 }
524 });
525 });
526
527 self.described_by(format!("{entity}"))
528 }
529
530 fn menu<C: Fn(&mut Context) -> Handle<'_, Popover> + 'static>(self, content: C) -> Self {
531 let entity = self.entity();
532
533 build_modal_model(self.cx, entity);
534
535 self.cx.with_current(entity, |cx| {
536 let menu_visible = cx.data::<ModalModel>().menu_visible;
537 Binding::new(cx, menu_visible, move |cx| {
538 let is_visible = menu_visible.get();
539 if is_visible {
540 (content)(cx).on_build(|cx| {
541 cx.add_listener(move |_: &mut Popover, cx, event| {
542 event.map(|window_event, _: &mut crate::events::EventMeta| {
543 match window_event {
544 WindowEvent::MouseDown(_) => {
545 if !cx.hovered.is_descendant_of(cx.tree, cx.current) {
548 cx.emit(ModalEvent::HideMenu);
549 }
550 }
552
553 _ => {}
554 }
555 });
556 });
557 });
558 }
559 });
560 });
561
562 self
563 }
564
565 fn on_press<F>(mut self, action: F) -> Self
566 where
567 F: 'static + Fn(&mut EventContext) + Send + Sync,
568 {
569 self = self.hoverable(true);
570
571 if let Some(view) = self
572 .cx
573 .views
574 .get_mut(&self.entity)
575 .and_then(|view_handler| view_handler.downcast_mut::<Button>())
576 {
577 view.action = Some(Box::new(action));
578 return self;
579 }
580
581 build_action_model(self.cx, self.entity);
582
583 self.cx.emit_custom(
584 Event::new(ActionsEvent::OnPress(Box::new(action)))
585 .target(self.entity)
586 .origin(self.entity),
587 );
588
589 self
590 }
591
592 fn on_press_down<F>(self, action: F) -> Self
593 where
594 F: 'static + Fn(&mut EventContext) + Send + Sync,
595 {
596 build_action_model(self.cx, self.entity);
597
598 self.cx.emit_custom(
599 Event::new(ActionsEvent::OnPressDown(Box::new(action)))
600 .target(self.entity)
601 .origin(self.entity),
602 );
603
604 self
605 }
606
607 fn on_double_click<F>(self, action: F) -> Self
608 where
609 F: 'static + Fn(&mut EventContext, MouseButton) + Send + Sync,
610 {
611 build_action_model(self.cx, self.entity);
612
613 self.cx.emit_custom(
614 Event::new(ActionsEvent::OnDoubleClick(Box::new(action)))
615 .target(self.entity)
616 .origin(self.entity),
617 );
618
619 self
620 }
621
622 fn on_hover<F>(self, action: F) -> Self
623 where
624 F: 'static + Fn(&mut EventContext) + Send + Sync,
625 {
626 build_action_model(self.cx, self.entity);
627
628 self.cx.emit_custom(
629 Event::new(ActionsEvent::OnHover(Box::new(action)))
630 .target(self.entity)
631 .origin(self.entity),
632 );
633
634 self
635 }
636
637 fn on_hover_out<F>(self, action: F) -> Self
638 where
639 F: 'static + Fn(&mut EventContext) + Send + Sync,
640 {
641 build_action_model(self.cx, self.entity);
642
643 self.cx.emit_custom(
644 Event::new(ActionsEvent::OnHoverOut(Box::new(action)))
645 .target(self.entity)
646 .origin(self.entity),
647 );
648
649 self
650 }
651
652 fn on_over<F>(self, action: F) -> Self
653 where
654 F: 'static + Fn(&mut EventContext) + Send + Sync,
655 {
656 build_action_model(self.cx, self.entity);
657
658 self.cx.emit_custom(
659 Event::new(ActionsEvent::OnOver(Box::new(action)))
660 .target(self.entity)
661 .origin(self.entity),
662 );
663
664 self
665 }
666
667 fn on_over_out<F>(self, action: F) -> Self
668 where
669 F: 'static + Fn(&mut EventContext) + Send + Sync,
670 {
671 build_action_model(self.cx, self.entity);
672
673 self.cx.emit_custom(
674 Event::new(ActionsEvent::OnOverOut(Box::new(action)))
675 .target(self.entity)
676 .origin(self.entity),
677 );
678
679 self
680 }
681
682 fn on_mouse_move<F>(self, action: F) -> Self
683 where
684 F: 'static + Fn(&mut EventContext, f32, f32) + Send + Sync,
685 {
686 build_action_model(self.cx, self.entity);
687
688 self.cx.emit_custom(
689 Event::new(ActionsEvent::OnMouseMove(Box::new(action)))
690 .target(self.entity)
691 .origin(self.entity),
692 );
693
694 self
695 }
696
697 fn on_mouse_down<F>(self, action: F) -> Self
698 where
699 F: 'static + Fn(&mut EventContext, MouseButton) + Send + Sync,
700 {
701 build_action_model(self.cx, self.entity);
702
703 self.cx.emit_custom(
704 Event::new(ActionsEvent::OnMouseDown(Box::new(action)))
705 .target(self.entity)
706 .origin(self.entity),
707 );
708
709 self
710 }
711
712 fn on_mouse_up<F>(self, action: F) -> Self
713 where
714 F: 'static + Fn(&mut EventContext, MouseButton) + Send + Sync,
715 {
716 build_action_model(self.cx, self.entity);
717
718 self.cx.emit_custom(
719 Event::new(ActionsEvent::OnMouseUp(Box::new(action)))
720 .target(self.entity)
721 .origin(self.entity),
722 );
723
724 self
725 }
726
727 fn on_focus_in<F>(self, action: F) -> Self
728 where
729 F: 'static + Fn(&mut EventContext) + Send + Sync,
730 {
731 build_action_model(self.cx, self.entity);
732
733 self.cx.emit_custom(
734 Event::new(ActionsEvent::OnFocusIn(Box::new(action)))
735 .target(self.entity)
736 .origin(self.entity),
737 );
738
739 self
740 }
741
742 fn on_focus_out<F>(self, action: F) -> Self
743 where
744 F: 'static + Fn(&mut EventContext) + Send + Sync,
745 {
746 build_action_model(self.cx, self.entity);
747
748 self.cx.emit_custom(
749 Event::new(ActionsEvent::OnFocusOut(Box::new(action)))
750 .target(self.entity)
751 .origin(self.entity),
752 );
753
754 self
755 }
756
757 fn on_geo_changed<F>(self, action: F) -> Self
758 where
759 F: 'static + Fn(&mut EventContext, GeoChanged) + Send + Sync,
760 {
761 build_action_model(self.cx, self.entity);
762
763 self.cx.emit_custom(
764 Event::new(ActionsEvent::OnGeoChanged(Box::new(action)))
765 .target(self.entity)
766 .origin(self.entity),
767 );
768
769 self
770 }
771}