1use crate::{
2 geometry::{rect::RectF, vector::Vector2F},
3 AppContext, DebugContext, Element, ElementBox, Event, EventContext, LayoutContext,
4 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 drag_handler: Option<Box<dyn FnMut(Vector2F, &mut EventContext)>>,
13}
14
15#[derive(Clone, Copy, Debug, Default)]
16pub struct MouseState {
17 pub hovered: bool,
18 pub clicked: bool,
19 prev_drag_position: Option<Vector2F>,
20}
21
22impl MouseEventHandler {
23 pub fn new<Tag, F>(id: usize, cx: &AppContext, render_child: F) -> Self
24 where
25 Tag: 'static,
26 F: FnOnce(MouseState) -> ElementBox,
27 {
28 let state_handle = cx.value::<Tag, _>(id);
29 let state = state_handle.read(cx.as_ref(), |state| *state);
30 let child = render_child(state);
31 Self {
32 state: state_handle,
33 child,
34 click_handler: None,
35 drag_handler: None,
36 }
37 }
38
39 pub fn on_click(mut self, handler: impl FnMut(&mut EventContext) + 'static) -> Self {
40 self.click_handler = Some(Box::new(handler));
41 self
42 }
43
44 pub fn on_drag(mut self, handler: impl FnMut(Vector2F, &mut EventContext) + 'static) -> Self {
45 self.drag_handler = Some(Box::new(handler));
46 self
47 }
48}
49
50impl Element for MouseEventHandler {
51 type LayoutState = ();
52 type PaintState = ();
53
54 fn layout(
55 &mut self,
56 constraint: SizeConstraint,
57 cx: &mut LayoutContext,
58 ) -> (Vector2F, Self::LayoutState) {
59 (self.child.layout(constraint, cx), ())
60 }
61
62 fn paint(
63 &mut self,
64 bounds: RectF,
65 _: &mut Self::LayoutState,
66 cx: &mut PaintContext,
67 ) -> Self::PaintState {
68 self.child.paint(bounds.origin(), cx);
69 }
70
71 fn dispatch_event(
72 &mut self,
73 event: &Event,
74 bounds: RectF,
75 _: &mut Self::LayoutState,
76 _: &mut Self::PaintState,
77 cx: &mut EventContext,
78 ) -> bool {
79 let click_handler = self.click_handler.as_mut();
80 let drag_handler = self.drag_handler.as_mut();
81
82 let handled_in_child = self.child.dispatch_event(event, cx);
83
84 self.state.update(cx, |state, cx| 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 state.prev_drag_position = Some(*position);
99 cx.notify();
100 true
101 } else {
102 handled_in_child
103 }
104 }
105 Event::LeftMouseUp { position, .. } => {
106 state.prev_drag_position = None;
107 if !handled_in_child && state.clicked {
108 state.clicked = false;
109 cx.notify();
110 if let Some(handler) = click_handler {
111 if bounds.contains_point(*position) {
112 handler(cx);
113 }
114 }
115 true
116 } else {
117 handled_in_child
118 }
119 }
120 Event::LeftMouseDragged { position, .. } => {
121 if !handled_in_child && state.clicked {
122 let prev_drag_position = state.prev_drag_position.replace(*position);
123 if let Some((handler, prev_position)) = drag_handler.zip(prev_drag_position) {
124 let delta = *position - prev_position;
125 if !delta.is_zero() {
126 (handler)(delta, cx);
127 }
128 }
129 true
130 } else {
131 handled_in_child
132 }
133 }
134 _ => handled_in_child,
135 })
136 }
137
138 fn debug(
139 &self,
140 _: RectF,
141 _: &Self::LayoutState,
142 _: &Self::PaintState,
143 cx: &DebugContext,
144 ) -> serde_json::Value {
145 json!({
146 "type": "MouseEventHandler",
147 "child": self.child.debug(cx),
148 })
149 }
150}