1use super::Padding;
2use crate::{
3 geometry::{
4 rect::RectF,
5 vector::{vec2f, Vector2F},
6 },
7 platform::CursorStyle,
8 DebugContext, Element, ElementBox, ElementStateContext, ElementStateHandle, Event,
9 EventContext, LayoutContext, PaintContext, SizeConstraint,
10};
11use serde_json::json;
12
13pub struct MouseEventHandler {
14 state: ElementStateHandle<MouseState>,
15 child: ElementBox,
16 cursor_style: Option<CursorStyle>,
17 mouse_down_handler: Option<Box<dyn FnMut(Vector2F, &mut EventContext)>>,
18 click_handler: Option<Box<dyn FnMut(Vector2F, usize, &mut EventContext)>>,
19 drag_handler: Option<Box<dyn FnMut(Vector2F, &mut EventContext)>>,
20 right_mouse_down_handler: Option<Box<dyn FnMut(Vector2F, &mut EventContext)>>,
21 right_click_handler: Option<Box<dyn FnMut(Vector2F, usize, &mut EventContext)>>,
22 padding: Padding,
23}
24
25#[derive(Default, Debug)]
26pub struct MouseState {
27 pub hovered: bool,
28 pub clicked: bool,
29 pub right_clicked: bool,
30 prev_drag_position: Option<Vector2F>,
31}
32
33impl MouseEventHandler {
34 pub fn new<Tag, C, F>(id: usize, cx: &mut C, render_child: F) -> Self
35 where
36 Tag: 'static,
37 C: ElementStateContext,
38 F: FnOnce(&MouseState, &mut C) -> ElementBox,
39 {
40 let state_handle = cx.element_state::<Tag, _>(id);
41 let child = state_handle.update(cx, |state, cx| render_child(state, cx));
42 Self {
43 state: state_handle,
44 child,
45 cursor_style: None,
46 mouse_down_handler: None,
47 click_handler: None,
48 drag_handler: None,
49 right_mouse_down_handler: None,
50 right_click_handler: None,
51 padding: Default::default(),
52 }
53 }
54
55 pub fn with_cursor_style(mut self, cursor: CursorStyle) -> Self {
56 self.cursor_style = Some(cursor);
57 self
58 }
59
60 pub fn on_mouse_down(
61 mut self,
62 handler: impl FnMut(Vector2F, &mut EventContext) + 'static,
63 ) -> Self {
64 self.mouse_down_handler = Some(Box::new(handler));
65 self
66 }
67
68 pub fn on_click(
69 mut self,
70 handler: impl FnMut(Vector2F, usize, &mut EventContext) + 'static,
71 ) -> Self {
72 self.click_handler = Some(Box::new(handler));
73 self
74 }
75
76 pub fn on_drag(mut self, handler: impl FnMut(Vector2F, &mut EventContext) + 'static) -> Self {
77 self.drag_handler = Some(Box::new(handler));
78 self
79 }
80
81 pub fn on_right_mouse_down(
82 mut self,
83 handler: impl FnMut(Vector2F, &mut EventContext) + 'static,
84 ) -> Self {
85 self.right_mouse_down_handler = Some(Box::new(handler));
86 self
87 }
88
89 pub fn on_right_click(
90 mut self,
91 handler: impl FnMut(Vector2F, usize, &mut EventContext) + 'static,
92 ) -> Self {
93 self.right_click_handler = Some(Box::new(handler));
94 self
95 }
96
97 pub fn with_padding(mut self, padding: Padding) -> Self {
98 self.padding = padding;
99 self
100 }
101
102 fn hit_bounds(&self, bounds: RectF) -> RectF {
103 RectF::from_points(
104 bounds.origin() - vec2f(self.padding.left, self.padding.top),
105 bounds.lower_right() + vec2f(self.padding.right, self.padding.bottom),
106 )
107 .round_out()
108 }
109}
110
111impl Element for MouseEventHandler {
112 type LayoutState = ();
113 type PaintState = ();
114
115 fn layout(
116 &mut self,
117 constraint: SizeConstraint,
118 cx: &mut LayoutContext,
119 ) -> (Vector2F, Self::LayoutState) {
120 (self.child.layout(constraint, cx), ())
121 }
122
123 fn paint(
124 &mut self,
125 bounds: RectF,
126 visible_bounds: RectF,
127 _: &mut Self::LayoutState,
128 cx: &mut PaintContext,
129 ) -> Self::PaintState {
130 if let Some(cursor_style) = self.cursor_style {
131 cx.scene
132 .push_cursor_style(self.hit_bounds(bounds), cursor_style);
133 }
134 self.child.paint(bounds.origin(), visible_bounds, cx);
135 }
136
137 fn dispatch_event(
138 &mut self,
139 event: &Event,
140 _: RectF,
141 visible_bounds: RectF,
142 _: &mut Self::LayoutState,
143 _: &mut Self::PaintState,
144 cx: &mut EventContext,
145 ) -> bool {
146 let hit_bounds = self.hit_bounds(visible_bounds);
147 let mouse_down_handler = self.mouse_down_handler.as_mut();
148 let click_handler = self.click_handler.as_mut();
149 let drag_handler = self.drag_handler.as_mut();
150 let right_mouse_down_handler = self.right_mouse_down_handler.as_mut();
151 let right_click_handler = self.right_click_handler.as_mut();
152
153 let handled_in_child = self.child.dispatch_event(event, cx);
154
155 self.state.update(cx, |state, cx| match event {
156 Event::MouseMoved {
157 position,
158 left_mouse_down,
159 } => {
160 if !left_mouse_down {
161 let mouse_in = hit_bounds.contains_point(*position);
162 if state.hovered != mouse_in {
163 state.hovered = mouse_in;
164 cx.notify();
165 return true;
166 }
167 }
168 handled_in_child
169 }
170 Event::LeftMouseDown { position, .. } => {
171 if !handled_in_child && hit_bounds.contains_point(*position) {
172 state.clicked = true;
173 state.prev_drag_position = Some(*position);
174 cx.notify();
175 if let Some(handler) = mouse_down_handler {
176 handler(*position, cx);
177 }
178 true
179 } else {
180 handled_in_child
181 }
182 }
183 Event::LeftMouseUp {
184 position,
185 click_count,
186 ..
187 } => {
188 state.prev_drag_position = None;
189 if !handled_in_child && state.clicked {
190 state.clicked = false;
191 cx.notify();
192 if let Some(handler) = click_handler {
193 if hit_bounds.contains_point(*position) {
194 handler(*position, *click_count, cx);
195 }
196 }
197 true
198 } else {
199 handled_in_child
200 }
201 }
202 Event::LeftMouseDragged { position, .. } => {
203 if !handled_in_child && state.clicked {
204 let prev_drag_position = state.prev_drag_position.replace(*position);
205 if let Some((handler, prev_position)) = drag_handler.zip(prev_drag_position) {
206 let delta = *position - prev_position;
207 if !delta.is_zero() {
208 (handler)(delta, cx);
209 }
210 }
211 true
212 } else {
213 handled_in_child
214 }
215 }
216 Event::RightMouseDown { position, .. } => {
217 if !handled_in_child && hit_bounds.contains_point(*position) {
218 state.right_clicked = true;
219 cx.notify();
220 if let Some(handler) = right_mouse_down_handler {
221 handler(*position, cx);
222 }
223 true
224 } else {
225 handled_in_child
226 }
227 }
228 Event::RightMouseUp {
229 position,
230 click_count,
231 ..
232 } => {
233 if !handled_in_child && state.right_clicked {
234 state.right_clicked = false;
235 cx.notify();
236 if let Some(handler) = right_click_handler {
237 if hit_bounds.contains_point(*position) {
238 handler(*position, *click_count, cx);
239 }
240 }
241 true
242 } else {
243 handled_in_child
244 }
245 }
246 _ => handled_in_child,
247 })
248 }
249
250 fn debug(
251 &self,
252 _: RectF,
253 _: &Self::LayoutState,
254 _: &Self::PaintState,
255 cx: &DebugContext,
256 ) -> serde_json::Value {
257 json!({
258 "type": "MouseEventHandler",
259 "child": self.child.debug(cx),
260 })
261 }
262}