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