1use super::Padding;
2use crate::{
3 geometry::{
4 rect::RectF,
5 vector::{vec2f, Vector2F},
6 },
7 platform::CursorStyle,
8 platform::MouseButton,
9 scene::{
10 CursorRegion, HandlerSet, MouseClick, MouseClickOut, MouseDown, MouseDownOut, MouseDrag,
11 MouseHover, MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut,
12 },
13 AnyElement, Element, EventContext, LayoutContext, MouseRegion, MouseState, PaintContext,
14 SceneBuilder, SizeConstraint, TypeTag, View, ViewContext,
15};
16use serde_json::json;
17use std::ops::Range;
18
19pub struct MouseEventHandler<V: View> {
20 child: AnyElement<V>,
21 region_id: usize,
22 cursor_style: Option<CursorStyle>,
23 handlers: HandlerSet,
24 hoverable: bool,
25 notify_on_hover: bool,
26 notify_on_click: bool,
27 above: bool,
28 padding: Padding,
29 tag: TypeTag,
30}
31
32/// Element which provides a render_child callback with a MouseState and paints a mouse
33/// region under (or above) it for easy mouse event handling.
34impl<V: View> MouseEventHandler<V> {
35 pub fn for_child<Tag: 'static>(child: impl Element<V>, region_id: usize) -> Self {
36 Self {
37 child: child.into_any(),
38 region_id,
39 cursor_style: None,
40 handlers: Default::default(),
41 notify_on_hover: false,
42 notify_on_click: false,
43 hoverable: false,
44 above: false,
45 padding: Default::default(),
46 tag: TypeTag::new::<Tag>(),
47 }
48 }
49
50 pub fn new<Tag: 'static, E>(
51 region_id: usize,
52 cx: &mut ViewContext<V>,
53 render_child: impl FnOnce(&mut MouseState, &mut ViewContext<V>) -> E,
54 ) -> Self
55 where
56 E: Element<V>,
57 {
58 let mut mouse_state = cx.mouse_state_dynamic(TypeTag::new::<Tag>(), region_id);
59 let child = render_child(&mut mouse_state, cx).into_any();
60 let notify_on_hover = mouse_state.accessed_hovered();
61 let notify_on_click = mouse_state.accessed_clicked();
62 Self {
63 child,
64 region_id,
65 cursor_style: None,
66 handlers: Default::default(),
67 notify_on_hover,
68 notify_on_click,
69 hoverable: true,
70 above: false,
71 padding: Default::default(),
72 tag: TypeTag::new::<Tag>(),
73 }
74 }
75
76 pub fn new_dynamic(
77 tag: TypeTag,
78 region_id: usize,
79 cx: &mut ViewContext<V>,
80 render_child: impl FnOnce(&mut MouseState, &mut ViewContext<V>) -> AnyElement<V>,
81 ) -> Self {
82 let mut mouse_state = cx.mouse_state_dynamic(tag, region_id);
83 let child = render_child(&mut mouse_state, cx);
84 let notify_on_hover = mouse_state.accessed_hovered();
85 let notify_on_click = mouse_state.accessed_clicked();
86 Self {
87 child,
88 region_id,
89 cursor_style: None,
90 handlers: Default::default(),
91 notify_on_hover,
92 notify_on_click,
93 hoverable: true,
94 above: false,
95 padding: Default::default(),
96 tag,
97 }
98 }
99
100 /// Modifies the MouseEventHandler to render the MouseRegion above the child element. Useful
101 /// for drag and drop handling and similar events which should be captured before the child
102 /// gets the opportunity
103 pub fn above<Tag: 'static, D>(
104 region_id: usize,
105 cx: &mut ViewContext<V>,
106 render_child: impl FnOnce(&mut MouseState, &mut ViewContext<V>) -> D,
107 ) -> Self
108 where
109 D: Element<V>,
110 {
111 let mut handler = Self::new::<Tag, _>(region_id, cx, render_child);
112 handler.above = true;
113 handler
114 }
115
116 pub fn with_cursor_style(mut self, cursor: CursorStyle) -> Self {
117 self.cursor_style = Some(cursor);
118 self
119 }
120
121 pub fn capture_all(mut self) -> Self {
122 self.handlers = HandlerSet::capture_all();
123 self
124 }
125
126 pub fn on_move(
127 mut self,
128 handler: impl Fn(MouseMove, &mut V, &mut EventContext<V>) + 'static,
129 ) -> Self {
130 self.handlers = self.handlers.on_move(handler);
131 self
132 }
133
134 pub fn on_move_out(
135 mut self,
136 handler: impl Fn(MouseMoveOut, &mut V, &mut EventContext<V>) + 'static,
137 ) -> Self {
138 self.handlers = self.handlers.on_move_out(handler);
139 self
140 }
141
142 pub fn on_down(
143 mut self,
144 button: MouseButton,
145 handler: impl Fn(MouseDown, &mut V, &mut EventContext<V>) + 'static,
146 ) -> Self {
147 self.handlers = self.handlers.on_down(button, handler);
148 self
149 }
150
151 pub fn on_up(
152 mut self,
153 button: MouseButton,
154 handler: impl Fn(MouseUp, &mut V, &mut EventContext<V>) + 'static,
155 ) -> Self {
156 self.handlers = self.handlers.on_up(button, handler);
157 self
158 }
159
160 pub fn on_click(
161 mut self,
162 button: MouseButton,
163 handler: impl Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
164 ) -> Self {
165 self.handlers = self.handlers.on_click(button, handler);
166 self
167 }
168
169 pub fn on_click_out(
170 mut self,
171 button: MouseButton,
172 handler: impl Fn(MouseClickOut, &mut V, &mut EventContext<V>) + 'static,
173 ) -> Self {
174 self.handlers = self.handlers.on_click_out(button, handler);
175 self
176 }
177
178 pub fn on_down_out(
179 mut self,
180 button: MouseButton,
181 handler: impl Fn(MouseDownOut, &mut V, &mut EventContext<V>) + 'static,
182 ) -> Self {
183 self.handlers = self.handlers.on_down_out(button, handler);
184 self
185 }
186
187 pub fn on_up_out(
188 mut self,
189 button: MouseButton,
190 handler: impl Fn(MouseUpOut, &mut V, &mut EventContext<V>) + 'static,
191 ) -> Self {
192 self.handlers = self.handlers.on_up_out(button, handler);
193 self
194 }
195
196 pub fn on_drag(
197 mut self,
198 button: MouseButton,
199 handler: impl Fn(MouseDrag, &mut V, &mut EventContext<V>) + 'static,
200 ) -> Self {
201 self.handlers = self.handlers.on_drag(button, handler);
202 self
203 }
204
205 pub fn on_hover(
206 mut self,
207 handler: impl Fn(MouseHover, &mut V, &mut EventContext<V>) + 'static,
208 ) -> Self {
209 self.handlers = self.handlers.on_hover(handler);
210 self
211 }
212
213 pub fn on_scroll(
214 mut self,
215 handler: impl Fn(MouseScrollWheel, &mut V, &mut EventContext<V>) + 'static,
216 ) -> Self {
217 self.handlers = self.handlers.on_scroll(handler);
218 self
219 }
220
221 pub fn with_hoverable(mut self, is_hoverable: bool) -> Self {
222 self.hoverable = is_hoverable;
223 self
224 }
225
226 pub fn with_padding(mut self, padding: Padding) -> Self {
227 self.padding = padding;
228 self
229 }
230
231 fn hit_bounds(&self, bounds: RectF) -> RectF {
232 RectF::from_points(
233 bounds.origin() - vec2f(self.padding.left, self.padding.top),
234 bounds.lower_right() + vec2f(self.padding.right, self.padding.bottom),
235 )
236 .round_out()
237 }
238
239 fn paint_regions(
240 &self,
241 scene: &mut SceneBuilder,
242 bounds: RectF,
243 visible_bounds: RectF,
244 cx: &mut ViewContext<V>,
245 ) {
246 let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
247 let hit_bounds = self.hit_bounds(visible_bounds);
248
249 if let Some(style) = self.cursor_style {
250 scene.push_cursor_region(CursorRegion {
251 bounds: hit_bounds,
252 style,
253 });
254 }
255 scene.push_mouse_region(
256 MouseRegion::from_handlers(
257 self.tag,
258 cx.view_id(),
259 self.region_id,
260 hit_bounds,
261 self.handlers.clone(),
262 )
263 .with_hoverable(self.hoverable)
264 .with_notify_on_hover(self.notify_on_hover)
265 .with_notify_on_click(self.notify_on_click),
266 );
267 }
268}
269
270impl<V: View> Element<V> for MouseEventHandler<V> {
271 type LayoutState = ();
272 type PaintState = ();
273
274 fn layout(
275 &mut self,
276 constraint: SizeConstraint,
277 view: &mut V,
278 cx: &mut LayoutContext<V>,
279 ) -> (Vector2F, Self::LayoutState) {
280 (self.child.layout(constraint, view, cx), ())
281 }
282
283 fn paint(
284 &mut self,
285 scene: &mut SceneBuilder,
286 bounds: RectF,
287 visible_bounds: RectF,
288 _: &mut Self::LayoutState,
289 view: &mut V,
290 cx: &mut PaintContext<V>,
291 ) -> Self::PaintState {
292 if self.above {
293 self.child
294 .paint(scene, bounds.origin(), visible_bounds, view, cx);
295
296 scene.paint_layer(None, |scene| {
297 self.paint_regions(scene, bounds, visible_bounds, cx);
298 });
299 } else {
300 self.paint_regions(scene, bounds, visible_bounds, cx);
301 self.child
302 .paint(scene, bounds.origin(), visible_bounds, view, cx);
303 }
304 }
305
306 fn rect_for_text_range(
307 &self,
308 range_utf16: Range<usize>,
309 _: RectF,
310 _: RectF,
311 _: &Self::LayoutState,
312 _: &Self::PaintState,
313 view: &V,
314 cx: &ViewContext<V>,
315 ) -> Option<RectF> {
316 self.child.rect_for_text_range(range_utf16, view, cx)
317 }
318
319 fn debug(
320 &self,
321 _: RectF,
322 _: &Self::LayoutState,
323 _: &Self::PaintState,
324 view: &V,
325 cx: &ViewContext<V>,
326 ) -> serde_json::Value {
327 json!({
328 "type": "MouseEventHandler",
329 "child": self.child.debug(view, cx),
330 })
331 }
332}