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