1use super::Padding;
2use crate::{
3 geometry::{
4 rect::RectF,
5 vector::{vec2f, Vector2F},
6 },
7 platform::CursorStyle,
8 CursorStyleHandle, DebugContext, Element, ElementBox, ElementStateContext, ElementStateHandle,
9 Event, 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(&mut EventContext)>>,
18 click_handler: Option<Box<dyn FnMut(&mut EventContext)>>,
19 drag_handler: Option<Box<dyn FnMut(Vector2F, &mut EventContext)>>,
20 padding: Padding,
21}
22
23#[derive(Default)]
24pub struct MouseState {
25 pub hovered: bool,
26 pub clicked: bool,
27 prev_drag_position: Option<Vector2F>,
28 cursor_style_handle: Option<CursorStyleHandle>,
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(&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
77impl Element for MouseEventHandler {
78 type LayoutState = ();
79 type PaintState = ();
80
81 fn layout(
82 &mut self,
83 constraint: SizeConstraint,
84 cx: &mut LayoutContext,
85 ) -> (Vector2F, Self::LayoutState) {
86 (self.child.layout(constraint, cx), ())
87 }
88
89 fn paint(
90 &mut self,
91 bounds: RectF,
92 visible_bounds: RectF,
93 _: &mut Self::LayoutState,
94 cx: &mut PaintContext,
95 ) -> Self::PaintState {
96 self.child.paint(bounds.origin(), visible_bounds, cx);
97 }
98
99 fn dispatch_event(
100 &mut self,
101 event: &Event,
102 _: RectF,
103 visible_bounds: RectF,
104 _: &mut Self::LayoutState,
105 _: &mut Self::PaintState,
106 cx: &mut EventContext,
107 ) -> bool {
108 let cursor_style = self.cursor_style;
109 let mouse_down_handler = self.mouse_down_handler.as_mut();
110 let click_handler = self.click_handler.as_mut();
111 let drag_handler = self.drag_handler.as_mut();
112
113 let handled_in_child = self.child.dispatch_event(event, cx);
114
115 let hit_bounds = RectF::from_points(
116 visible_bounds.origin() - vec2f(self.padding.left, self.padding.top),
117 visible_bounds.lower_right() + vec2f(self.padding.right, self.padding.bottom),
118 )
119 .round_out();
120
121 self.state.update(cx, |state, cx| match event {
122 Event::MouseMoved {
123 position,
124 left_mouse_down,
125 } => {
126 if !left_mouse_down {
127 let mouse_in = hit_bounds.contains_point(*position);
128 if state.hovered != mouse_in {
129 state.hovered = mouse_in;
130 if let Some(cursor_style) = cursor_style {
131 if !state.clicked {
132 if state.hovered {
133 state.cursor_style_handle =
134 Some(cx.set_cursor_style(cursor_style));
135 } else {
136 state.cursor_style_handle = None;
137 }
138 }
139 }
140 cx.notify();
141 return true;
142 }
143 }
144 handled_in_child
145 }
146 Event::LeftMouseDown { position, .. } => {
147 if !handled_in_child && hit_bounds.contains_point(*position) {
148 state.clicked = true;
149 state.prev_drag_position = Some(*position);
150 cx.notify();
151 if let Some(handler) = mouse_down_handler {
152 handler(cx);
153 }
154 true
155 } else {
156 handled_in_child
157 }
158 }
159 Event::LeftMouseUp { position, .. } => {
160 state.prev_drag_position = None;
161 if !handled_in_child && state.clicked {
162 state.clicked = false;
163 if !state.hovered {
164 state.cursor_style_handle = None;
165 }
166 cx.notify();
167 if let Some(handler) = click_handler {
168 if hit_bounds.contains_point(*position) {
169 handler(cx);
170 }
171 }
172 true
173 } else {
174 handled_in_child
175 }
176 }
177 Event::LeftMouseDragged { position, .. } => {
178 if !handled_in_child && state.clicked {
179 let prev_drag_position = state.prev_drag_position.replace(*position);
180 if let Some((handler, prev_position)) = drag_handler.zip(prev_drag_position) {
181 let delta = *position - prev_position;
182 if !delta.is_zero() {
183 (handler)(delta, cx);
184 }
185 }
186 true
187 } else {
188 handled_in_child
189 }
190 }
191 _ => handled_in_child,
192 })
193 }
194
195 fn debug(
196 &self,
197 _: RectF,
198 _: &Self::LayoutState,
199 _: &Self::PaintState,
200 cx: &DebugContext,
201 ) -> serde_json::Value {
202 json!({
203 "type": "MouseEventHandler",
204 "child": self.child.debug(cx),
205 })
206 }
207}