Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-ui-core/src/elements/event_handler.rs
StratoSDK / crates / strato-ui-core / src / elements / event_handler.rs
1use crate::{
2 event::{DispatchedEvent, EventDiscriminants, KeyState, ModifiersState},
3 keymap::Keystroke,
4 platform::keyboard::KeyCode,
5};
6 
7use super::{
8 AfterLayoutContext, AppContext, DispatchEventResult, Element, Event, EventContext,
9 LayoutContext, PaintContext, Point, SizeConstraint, ZIndex,
10};
11use pathfinder_geometry::vector::Vector2F;
12use std::cell::RefCell;
13 
14type Handler = Box<dyn FnMut(&mut EventContext, &AppContext, Vector2F) -> DispatchEventResult>;
15type KeyHandler = Box<dyn FnMut(&mut EventContext, &AppContext, &Keystroke) -> DispatchEventResult>;
16type ScrollHandler = Box<
17 dyn FnMut(&mut EventContext, &AppContext, &Vector2F, &ModifiersState) -> DispatchEventResult,
18>;
19type ModifierStateChangedHandler =
20 Box<dyn FnMut(&mut EventContext, &AppContext, &KeyCode, &KeyState) -> DispatchEventResult>;
21 
22#[derive(Debug, Clone, Copy)]
23pub struct MouseInBehavior {
24 /// Whether to fire the `mouse_in` event on synthetic events, which are events the UI
25 /// framework generates so in order to trigger hover effects when the underlying view has
26 /// changed even though the mouse hasn't actually moved. Typically elements should handle
27 /// sythetic hovers, but there are some cases where it's the incorrect behavior.
28 pub fire_on_synthetic_events: bool,
29 /// Whether to fire the `mouse_in` event when the element is covered by another element.
30 /// This is true by default, but some elements may want to configure this behavior.
31 pub fire_when_covered: bool,
32}
33 
34impl Default for MouseInBehavior {
35 fn default() -> Self {
36 Self {
37 fire_on_synthetic_events: true,
38 fire_when_covered: true,
39 }
40 }
41}
42 
43pub struct EventHandler {
44 child: Box<dyn Element>,
45 /// Allow this element to handle events even if a descendent already handled it.
46 always_handle: bool,
47 left_mouse_down: Option<RefCell<Handler>>,
48 left_mouse_up: Option<RefCell<Handler>>,
49 middle_mouse_down: Option<RefCell<Handler>>,
50 right_mouse_down: Option<RefCell<Handler>>,
51 forward_mouse_down: Option<RefCell<Handler>>,
52 back_mouse_down: Option<RefCell<Handler>>,
53 mouse_in: Option<RefCell<Handler>>,
54 mouse_in_behavior: MouseInBehavior,
55 mouse_out: Option<RefCell<Handler>>,
56 mouse_dragged: Option<RefCell<Handler>>,
57 scroll_wheel: Option<RefCell<ScrollHandler>>,
58 keydown: Option<RefCell<KeyHandler>>,
59 modifier_state_changed: Option<RefCell<ModifierStateChangedHandler>>,
60 origin: Option<Point>,
61 // This is a short-term solution for properly handling events on stacks. A stack will always
62 // put its children on higher z-indexes than its origin, so a hit test using the standard
63 // `z_index` method would always result in the event being covered (by the children of the
64 // stack). Instead, we track the upper-bound of z-indexes _contained by_ the child element.
65 // Then we use that upper bound to do the hit testing, which means a parent will always get
66 // events from its children, regardless of whether they are stacks or not.
67 child_max_z_index: Option<ZIndex>,
68}
69 
70impl EventHandler {
71 pub fn new(child: Box<dyn Element>) -> Self {
72 Self {
73 child,
74 always_handle: false,
75 left_mouse_down: None,
76 left_mouse_up: None,
77 middle_mouse_down: None,
78 right_mouse_down: None,
79 forward_mouse_down: None,
80 back_mouse_down: None,
81 mouse_in: None,
82 mouse_out: None,
83 mouse_dragged: None,
84 scroll_wheel: None,
85 keydown: None,
86 modifier_state_changed: None,
87 origin: None,
88 child_max_z_index: None,
89 mouse_in_behavior: Default::default(),
90 }
91 }
92 
93 pub fn with_always_handle(mut self) -> Self {
94 self.always_handle = true;
95 self
96 }
97 
98 pub fn on_keydown<F>(mut self, callback: F) -> Self
99 where
100 F: 'static + FnMut(&mut EventContext, &AppContext, &Keystroke) -> DispatchEventResult,
101 {
102 self.keydown = Some(RefCell::new(Box::new(callback)));
103 self
104 }
105 
106 pub fn on_modifier_state_changed<F>(mut self, callback: F) -> Self
107 where
108 F: 'static
109 + FnMut(&mut EventContext, &AppContext, &KeyCode, &KeyState) -> DispatchEventResult,
110 {
111 self.modifier_state_changed = Some(RefCell::new(Box::new(callback)));
112 self
113 }
114 
115 pub fn on_left_mouse_down<F>(mut self, callback: F) -> Self
116 where
117 F: 'static + FnMut(&mut EventContext, &AppContext, Vector2F) -> DispatchEventResult,
118 {
119 self.left_mouse_down = Some(RefCell::new(Box::new(callback)));
120 self
121 }
122 
123 pub fn on_left_mouse_up<F>(mut self, callback: F) -> Self
124 where
125 F: 'static + FnMut(&mut EventContext, &AppContext, Vector2F) -> DispatchEventResult,
126 {
127 self.left_mouse_up = Some(RefCell::new(Box::new(callback)));
128 self
129 }
130 
131 pub fn on_right_mouse_down<F>(mut self, callback: F) -> Self
132 where
133 F: 'static + FnMut(&mut EventContext, &AppContext, Vector2F) -> DispatchEventResult,
134 {
135 self.right_mouse_down = Some(RefCell::new(Box::new(callback)));
136 self
137 }
138 
139 pub fn on_middle_mouse_down<F>(mut self, callback: F) -> Self
140 where
141 F: 'static + FnMut(&mut EventContext, &AppContext, Vector2F) -> DispatchEventResult,
142 {
143 self.middle_mouse_down = Some(RefCell::new(Box::new(callback)));
144 self
145 }
146 
147 pub fn on_forward_mouse_down<F>(mut self, callback: F) -> Self
148 where
149 F: 'static + FnMut(&mut EventContext, &AppContext, Vector2F) -> DispatchEventResult,
150 {
151 self.forward_mouse_down = Some(RefCell::new(Box::new(callback)));
152 self
153 }
154 
155 pub fn on_back_mouse_down<F>(mut self, callback: F) -> Self
156 where
157 F: 'static + FnMut(&mut EventContext, &AppContext, Vector2F) -> DispatchEventResult,
158 {
159 self.back_mouse_down = Some(RefCell::new(Box::new(callback)));
160 self
161 }
162 
163 pub fn on_mouse_in<F>(mut self, callback: F, mouse_in_behavior: Option<MouseInBehavior>) -> Self
164 where
165 F: 'static + FnMut(&mut EventContext, &AppContext, Vector2F) -> DispatchEventResult,
166 {
167 self.mouse_in = Some(RefCell::new(Box::new(callback)));
168 self.mouse_in_behavior = mouse_in_behavior.unwrap_or_default();
169 self
170 }
171 
172 pub fn on_mouse_out<F>(mut self, callback: F) -> Self
173 where
174 F: 'static + FnMut(&mut EventContext, &AppContext, Vector2F) -> DispatchEventResult,
175 {
176 self.mouse_out = Some(RefCell::new(Box::new(callback)));
177 self
178 }
179 
180 pub fn on_mouse_dragged<F>(mut self, callback: F) -> Self
181 where
182 F: 'static + FnMut(&mut EventContext, &AppContext, Vector2F) -> DispatchEventResult,
183 {
184 self.mouse_dragged = Some(RefCell::new(Box::new(callback)));
185 self
186 }
187 
188 pub fn on_scroll_wheel<F>(mut self, callback: F) -> Self
189 where
190 F: 'static
191 + FnMut(&mut EventContext, &AppContext, &Vector2F, &ModifiersState) -> DispatchEventResult,
192 {
193 self.scroll_wheel = Some(RefCell::new(Box::new(callback)));
194 self
195 }
196 
197 fn dispatch_callback(
198 &self,
199 callback: Option<&RefCell<Handler>>,
200 ctx: &mut EventContext,
201 position: Vector2F,
202 app: &AppContext,
203 ) -> bool {
204 if let Some(callback) = callback.as_ref() {
205 if let Some(rect) = ctx.visible_rect(self.origin.unwrap(), self.size().unwrap()) {
206 if rect.contains_point(position) {
207 return match callback.borrow_mut()(ctx, app, position) {
208 DispatchEventResult::PropagateToParent => false,
209 DispatchEventResult::StopPropagation => true,
210 };
211 }
212 }
213 }
214 false
215 }
216}
217 
218impl Element for EventHandler {
219 fn layout(
220 &mut self,
221 constraint: SizeConstraint,
222 ctx: &mut LayoutContext,
223 app: &AppContext,
224 ) -> Vector2F {
225 self.child.layout(constraint, ctx, app)
226 }
227 
228 fn after_layout(&mut self, ctx: &mut AfterLayoutContext, app: &AppContext) {
229 self.child.after_layout(ctx, app);
230 }
231 
232 fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, app: &AppContext) {
233 self.origin = Some(Point::from_vec2f(origin, ctx.scene.z_index()));
234 self.child.paint(origin, ctx, app);
235 self.child_max_z_index = Some(ctx.scene.max_active_z_index());
236 }
237 
238 fn size(&self) -> Option<Vector2F> {
239 self.child.size()
240 }
241 
242 fn dispatch_event(
243 &mut self,
244 event: &DispatchedEvent,
245 ctx: &mut EventContext,
246 app: &AppContext,
247 ) -> bool {
248 let handled = self.child.dispatch_event(event, ctx, app);
249 if handled && !self.always_handle {
250 return true;
251 }
252 
253 let Some(z_index) = self.child_max_z_index else {
254 log::error!(
255 "Dispatching event {:?} on EventHandler element which was never painted.",
256 EventDiscriminants::from(event.raw_event())
257 );
258 return false;
259 };
260 match event.at_z_index(z_index, ctx) {
261 Some(Event::MouseMoved {
262 position,
263 is_synthetic,
264 ..
265 }) => {
266 let MouseInBehavior {
267 fire_on_synthetic_events,
268 fire_when_covered,
269 } = self.mouse_in_behavior;
270 let is_covered = ctx.is_covered(Point::from_vec2f(
271 *position,
272 self.child_max_z_index.expect("child max z index not set"),
273 ));
274 let should_fire = (!is_synthetic || fire_on_synthetic_events)
275 && (fire_when_covered || !is_covered);
276 if should_fire
277 && self.dispatch_callback(self.mouse_in.as_ref(), ctx, *position, app)
278 {
279 return true;
280 }
281 if self.dispatch_callback(self.mouse_out.as_ref(), ctx, *position, app) {
282 return true;
283 }
284 }
285 Some(Event::LeftMouseDragged { position, .. }) => {
286 if self.dispatch_callback(self.mouse_dragged.as_ref(), ctx, *position, app) {
287 return true;
288 }
289 if self.dispatch_callback(self.mouse_in.as_ref(), ctx, *position, app) {
290 return true;
291 }
292 if self.dispatch_callback(self.mouse_out.as_ref(), ctx, *position, app) {
293 return true;
294 }
295 }
296 Some(Event::LeftMouseDown { position, .. }) => {
297 if self.dispatch_callback(self.left_mouse_down.as_ref(), ctx, *position, app) {
298 return true;
299 }
300 }
301 Some(Event::LeftMouseUp { position, .. }) => {
302 if self.dispatch_callback(self.left_mouse_up.as_ref(), ctx, *position, app) {
303 return true;
304 }
305 }
306 Some(Event::MiddleMouseDown { position, .. }) => {
307 if self.dispatch_callback(self.middle_mouse_down.as_ref(), ctx, *position, app) {
308 return true;
309 }
310 }
311 Some(Event::RightMouseDown { position, .. }) => {
312 if self.dispatch_callback(self.right_mouse_down.as_ref(), ctx, *position, app) {
313 return true;
314 }
315 }
316 Some(Event::BackMouseDown { position, .. }) => {
317 if self.dispatch_callback(self.back_mouse_down.as_ref(), ctx, *position, app) {
318 return true;
319 }
320 }
321 Some(Event::ForwardMouseDown { position, .. }) => {
322 if self.dispatch_callback(self.forward_mouse_down.as_ref(), ctx, *position, app) {
323 return true;
324 }
325 }
326 Some(Event::KeyDown { keystroke, .. }) => {
327 if let Some(callback) = self.keydown.as_ref() {
328 return match callback.borrow_mut()(ctx, app, keystroke) {
329 DispatchEventResult::PropagateToParent => false,
330 DispatchEventResult::StopPropagation => true,
331 };
332 }
333 }
334 Some(Event::ModifierKeyChanged { key_code, state }) => {
335 if let Some(callback) = self.modifier_state_changed.as_ref() {
336 return match callback.borrow_mut()(ctx, app, key_code, state) {
337 DispatchEventResult::PropagateToParent => false,
338 DispatchEventResult::StopPropagation => true,
339 };
340 }
341 }
342 Some(Event::ScrollWheel {
343 position,
344 delta,
345 precise: _,
346 modifiers,
347 }) => {
348 if let Some(callback) = self.scroll_wheel.as_ref() {
349 if let Some(rect) = ctx.visible_rect(self.origin.unwrap(), self.size().unwrap())
350 {
351 if rect.contains_point(*position) {
352 return match callback.borrow_mut()(ctx, app, delta, modifiers) {
353 DispatchEventResult::PropagateToParent => false,
354 DispatchEventResult::StopPropagation => true,
355 };
356 }
357 }
358 }
359 }
360 _ => {}
361 }
362 handled
363 }
364 
365 fn origin(&self) -> Option<Point> {
366 self.child.origin()
367 }
368}
369 
370#[cfg(test)]
371#[path = "event_handler_test.rs"]
372mod tests;
373