1use crate::{
2 geometry::{rect::RectF, vector::Vector2F},
3 AfterLayoutContext, AppContext, DebugContext, Element, ElementBox, Event, EventContext,
4 LayoutContext, PaintContext, SizeConstraint, ValueHandle,
5};
6use serde_json::json;
7
8pub struct MouseEventHandler {
9 state: ValueHandle<MouseState>,
10 child: ElementBox,
11 click_handler: Option<Box<dyn FnMut(&mut EventContext)>>,
12}
13
14#[derive(Clone, Copy, Debug, Default)]
15pub struct MouseState {
16 pub hovered: bool,
17 pub clicked: bool,
18}
19
20impl MouseEventHandler {
21 pub fn new<Tag, F>(id: usize, cx: &AppContext, render_child: F) -> Self
22 where
23 Tag: 'static,
24 F: FnOnce(MouseState) -> ElementBox,
25 {
26 let state_handle = cx.value::<Tag, _>(id);
27 let state = state_handle.read(cx, |state| *state);
28 let child = render_child(state);
29 Self {
30 state: state_handle,
31 child,
32 click_handler: None,
33 }
34 }
35
36 pub fn on_click(mut self, handler: impl FnMut(&mut EventContext) + 'static) -> Self {
37 self.click_handler = Some(Box::new(handler));
38 self
39 }
40}
41
42impl Element for MouseEventHandler {
43 type LayoutState = ();
44 type PaintState = ();
45
46 fn layout(
47 &mut self,
48 constraint: SizeConstraint,
49 cx: &mut LayoutContext,
50 ) -> (Vector2F, Self::LayoutState) {
51 (self.child.layout(constraint, cx), ())
52 }
53
54 fn after_layout(
55 &mut self,
56 _: Vector2F,
57 _: &mut Self::LayoutState,
58 cx: &mut AfterLayoutContext,
59 ) {
60 self.child.after_layout(cx);
61 }
62
63 fn paint(
64 &mut self,
65 bounds: RectF,
66 _: &mut Self::LayoutState,
67 cx: &mut PaintContext,
68 ) -> Self::PaintState {
69 self.child.paint(bounds.origin(), cx);
70 }
71
72 fn dispatch_event(
73 &mut self,
74 event: &Event,
75 bounds: RectF,
76 _: &mut Self::LayoutState,
77 _: &mut Self::PaintState,
78 cx: &mut EventContext,
79 ) -> bool {
80 let click_handler = self.click_handler.as_mut();
81
82 let handled_in_child = self.child.dispatch_event(event, cx);
83
84 self.state.update(cx.app, |state| match event {
85 Event::MouseMoved { position } => {
86 let mouse_in = bounds.contains_point(*position);
87 if state.hovered != mouse_in {
88 state.hovered = mouse_in;
89 cx.notify();
90 true
91 } else {
92 handled_in_child
93 }
94 }
95 Event::LeftMouseDown { position, .. } => {
96 if !handled_in_child && bounds.contains_point(*position) {
97 state.clicked = true;
98 cx.notify();
99 true
100 } else {
101 handled_in_child
102 }
103 }
104 Event::LeftMouseUp { position, .. } => {
105 if !handled_in_child && state.clicked {
106 state.clicked = false;
107 cx.notify();
108 if let Some(handler) = click_handler {
109 if bounds.contains_point(*position) {
110 handler(cx);
111 }
112 }
113 true
114 } else {
115 handled_in_child
116 }
117 }
118 _ => handled_in_child,
119 })
120 }
121
122 fn debug(
123 &self,
124 _: RectF,
125 _: &Self::LayoutState,
126 _: &Self::PaintState,
127 cx: &DebugContext,
128 ) -> serde_json::Value {
129 json!({
130 "type": "MouseEventHandler",
131 "child": self.child.debug(cx),
132 })
133 }
134}