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