1use crate::scene::*;
2
3#[derive(Debug, Clone)]
4pub enum AppEvent {
5 Click(f32, f32),
6 MouseMove(f32, f32),
7 KeyPress(String),
8 Scroll(f32, f32, f32, f32), }
10
11#[derive(Debug, Clone)]
12pub struct EventResult {
13 pub target: NodeId,
14 pub event_type: String,
15 pub handler_code: String,
16}
17
18pub struct EventSystem {
19 pub hover_node: Option<NodeId>,
20 pub focus_node: Option<NodeId>,
21}
22
23impl Default for EventSystem {
24 fn default() -> Self {
25 Self::new()
26 }
27}
28
29impl EventSystem {
30 pub fn new() -> Self {
31 Self {
32 hover_node: None,
33 focus_node: None,
34 }
35 }
36
37 pub fn handle(&mut self, event: &AppEvent, scene: &mut SceneGraph) -> Vec<EventResult> {
38 match event {
39 AppEvent::Click(x, y) => self.handle_click(*x, *y, scene),
40 AppEvent::MouseMove(x, y) => self.handle_mouse_move(*x, *y, scene),
41 AppEvent::Scroll(x, y, dx, dy) => {
42 self.handle_scroll(*x, *y, *dx, *dy, scene);
43 Vec::new()
44 }
45 AppEvent::KeyPress(key) => self.handle_key(key, scene),
46 }
47 }
48
49 fn handle_click(&mut self, x: f32, y: f32, scene: &SceneGraph) -> Vec<EventResult> {
50 let mut results = Vec::new();
51
52 if let Some(hit) = self.hit_test(x, y, scene) {
53 let mut current = Some(hit);
55 while let Some(node_id) = current {
56 let node = scene.get(node_id);
57 if let Some(handler) = node.event_handlers.get("click") {
58 results.push(EventResult {
59 target: hit,
60 event_type: "click".to_string(),
61 handler_code: handler.clone(),
62 });
63 }
64 current = node.parent;
65 }
66 }
67
68 results
69 }
70
71 fn handle_mouse_move(&mut self, x: f32, y: f32, scene: &mut SceneGraph) -> Vec<EventResult> {
72 let mut results = Vec::new();
73 let new_hover = self.hit_test(x, y, scene);
74
75 if new_hover != self.hover_node {
76 if let Some(old) = self.hover_node {
78 let node = scene.get(old);
79 let handler = node.event_handlers.get("mouseleave").cloned();
80 let has_base = node.base_style.is_some();
81 let has_transition = node.style.transition_duration > 0.0;
82 if let Some(handler) = handler {
83 results.push(EventResult {
84 target: old,
85 event_type: "mouseleave".to_string(),
86 handler_code: handler,
87 });
88 }
89 if has_base {
90 let base = scene.get(old).base_style.clone().unwrap();
91 if has_transition {
92 let current = scene.get(old).style.clone();
94 let node = scene.get_mut(old);
95 node.transition_from = Some(current);
96 node.transition_to = Some(base);
97 node.transition_start = Some(std::time::Instant::now());
98 } else {
99 scene.get_mut(old).style = base;
100 }
101 scene.get_mut(old).is_hovered = false;
102 scene.get_mut(old).dirty = true;
103 }
104 }
105 if let Some(new) = new_hover {
107 let node = scene.get(new);
108 let handler = node.event_handlers.get("mouseenter").cloned();
109 let has_hover = node.hover_style.is_some();
110 let has_transition = node.style.transition_duration > 0.0
111 || node
112 .hover_style
113 .as_ref()
114 .map(|h| h.transition_duration > 0.0)
115 .unwrap_or(false);
116 if let Some(handler) = handler {
117 results.push(EventResult {
118 target: new,
119 event_type: "mouseenter".to_string(),
120 handler_code: handler,
121 });
122 }
123 if has_hover {
124 let hover = scene.get(new).hover_style.clone().unwrap();
125 if has_transition {
126 let current = scene.get(new).style.clone();
128 let node = scene.get_mut(new);
129 node.transition_from = Some(current);
130 node.transition_to = Some(hover);
131 node.transition_start = Some(std::time::Instant::now());
132 } else {
133 scene.get_mut(new).style = hover;
134 }
135 scene.get_mut(new).is_hovered = true;
136 scene.get_mut(new).dirty = true;
137 }
138 }
139 self.hover_node = new_hover;
140 }
141
142 results
143 }
144
145 fn handle_scroll(&mut self, x: f32, y: f32, _dx: f32, dy: f32, scene: &mut SceneGraph) {
146 if let Some(root) = scene.root {
148 if let Some(scroll_target) = self.find_scrollable(x, y, scene, root) {
149 let node = scene.get_mut(scroll_target);
150 let max_scroll = (node.content_height - node.layout.height).max(0.0);
151 node.scroll_offset.1 = (node.scroll_offset.1 + dy * 30.0).clamp(0.0, max_scroll);
152 }
153 }
154 }
155
156 fn handle_key(&self, _key: &str, scene: &SceneGraph) -> Vec<EventResult> {
157 let mut results = Vec::new();
158
159 if let Some(focus) = self.focus_node {
160 let node = scene.get(focus);
161 if let Some(handler) = node.event_handlers.get("keypress") {
162 results.push(EventResult {
163 target: focus,
164 event_type: "keypress".to_string(),
165 handler_code: handler.clone(),
166 });
167 }
168 }
169
170 results
171 }
172
173 fn hit_test(&self, x: f32, y: f32, scene: &SceneGraph) -> Option<NodeId> {
174 if let Some(root) = scene.root {
175 self.hit_test_recursive(x, y, scene, root)
176 } else {
177 None
178 }
179 }
180
181 fn hit_test_recursive(
182 &self,
183 x: f32,
184 y: f32,
185 scene: &SceneGraph,
186 node_id: NodeId,
187 ) -> Option<NodeId> {
188 let node = scene.get(node_id);
189
190 if node.style.display == Display::None {
191 return None;
192 }
193
194 let layout = &node.layout;
195 let scroll_y = node.scroll_offset.1;
197 let in_bounds = x >= layout.x
198 && x <= layout.x + layout.width
199 && y >= layout.y
200 && y <= layout.y + layout.height;
201
202 if !in_bounds && node.style.overflow != Overflow::Visible {
203 return None;
204 }
205
206 let child_y = y + scroll_y;
208
209 let children: Vec<NodeId> = node.children.clone();
211 for &child_id in children.iter().rev() {
212 if let Some(hit) = self.hit_test_recursive(x, child_y, scene, child_id) {
213 return Some(hit);
214 }
215 }
216
217 if in_bounds && !node.event_handlers.is_empty() {
218 return Some(node_id);
219 }
220
221 if in_bounds {
222 return Some(node_id);
223 }
224
225 None
226 }
227
228 fn find_scrollable(
229 &self,
230 x: f32,
231 y: f32,
232 scene: &SceneGraph,
233 node_id: NodeId,
234 ) -> Option<NodeId> {
235 let node = scene.get(node_id);
236 let layout = &node.layout;
237
238 let in_bounds = x >= layout.x
239 && x <= layout.x + layout.width
240 && y >= layout.y
241 && y <= layout.y + layout.height;
242
243 if !in_bounds {
244 return None;
245 }
246
247 let children: Vec<NodeId> = node.children.clone();
249 for &child_id in children.iter().rev() {
250 if let Some(found) = self.find_scrollable(x, y, scene, child_id) {
251 return Some(found);
252 }
253 }
254
255 if node.style.overflow == Overflow::Scroll {
256 return Some(node_id);
257 }
258
259 None
260 }
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266
267 fn node(scene: &mut SceneGraph, tag: &str, rect: (f32, f32, f32, f32)) -> NodeId {
268 let id = scene.add_node(ElementKind::from_tag(tag), tag.to_string());
269 scene.get_mut(id).layout = LayoutRect {
270 x: rect.0,
271 y: rect.1,
272 width: rect.2,
273 height: rect.3,
274 };
275 id
276 }
277
278 fn on(scene: &mut SceneGraph, id: NodeId, event: &str, code: &str) {
279 scene
280 .get_mut(id)
281 .event_handlers
282 .insert(event.to_string(), code.to_string());
283 }
284
285 fn simple_scene() -> (SceneGraph, NodeId, NodeId) {
287 let mut scene = SceneGraph::new();
288 let root = node(&mut scene, "div", (0.0, 0.0, 200.0, 200.0));
289 let inner = node(&mut scene, "div", (50.0, 50.0, 100.0, 100.0));
290 scene.add_child(root, inner);
291 (scene, root, inner)
292 }
293
294 #[test]
295 fn click_inside_a_child_hits_the_child() {
296 let (mut scene, _root, inner) = simple_scene();
297 on(&mut scene, inner, "click", "inner()");
298
299 let results = EventSystem::new().handle(&AppEvent::Click(100.0, 100.0), &mut scene);
300
301 assert_eq!(results.len(), 1);
302 assert_eq!(results[0].target, inner);
303 assert_eq!(results[0].event_type, "click");
304 assert_eq!(results[0].handler_code, "inner()");
305 }
306
307 #[test]
308 fn click_outside_the_child_falls_through_to_the_parent() {
309 let (mut scene, root, inner) = simple_scene();
310 on(&mut scene, inner, "click", "inner()");
311 on(&mut scene, root, "click", "root()");
312
313 let results = EventSystem::new().handle(&AppEvent::Click(10.0, 10.0), &mut scene);
314
315 assert_eq!(results.len(), 1);
316 assert_eq!(results[0].target, root);
317 assert_eq!(results[0].handler_code, "root()");
318 }
319
320 #[test]
321 fn click_bubbles_from_the_target_up_through_its_ancestors() {
322 let mut scene = SceneGraph::new();
323 let root = node(&mut scene, "div", (0.0, 0.0, 300.0, 300.0));
324 let mid = node(&mut scene, "div", (0.0, 0.0, 200.0, 200.0));
325 let leaf = node(&mut scene, "button", (0.0, 0.0, 100.0, 100.0));
326 scene.add_child(root, mid);
327 scene.add_child(mid, leaf);
328 on(&mut scene, root, "click", "root()");
329 on(&mut scene, mid, "click", "mid()");
330 on(&mut scene, leaf, "click", "leaf()");
331
332 let results = EventSystem::new().handle(&AppEvent::Click(10.0, 10.0), &mut scene);
333
334 let codes: Vec<&str> = results.iter().map(|r| r.handler_code.as_str()).collect();
336 assert_eq!(codes, vec!["leaf()", "mid()", "root()"]);
337 assert!(results.iter().all(|r| r.target == leaf));
339 }
340
341 #[test]
342 fn a_click_that_misses_everything_produces_no_results() {
343 let (mut scene, root, inner) = simple_scene();
344 on(&mut scene, inner, "click", "inner()");
345 on(&mut scene, root, "click", "root()");
346
347 let results = EventSystem::new().handle(&AppEvent::Click(999.0, 999.0), &mut scene);
348
349 assert!(results.is_empty());
350 }
351
352 #[test]
353 fn the_last_of_two_overlapping_siblings_wins() {
354 let mut scene = SceneGraph::new();
355 let root = node(&mut scene, "div", (0.0, 0.0, 200.0, 200.0));
356 let under = node(&mut scene, "div", (0.0, 0.0, 100.0, 100.0));
357 let over = node(&mut scene, "div", (0.0, 0.0, 100.0, 100.0));
358 scene.add_child(root, under);
359 scene.add_child(root, over);
360 on(&mut scene, under, "click", "under()");
361 on(&mut scene, over, "click", "over()");
362
363 let results = EventSystem::new().handle(&AppEvent::Click(50.0, 50.0), &mut scene);
364
365 assert_eq!(results[0].target, over, "later siblings paint on top");
366 assert_eq!(results[0].handler_code, "over()");
367 }
368
369 #[test]
370 fn display_none_subtrees_are_not_hit_testable() {
371 let (mut scene, root, inner) = simple_scene();
372 scene.get_mut(inner).style.display = Display::None;
373 on(&mut scene, inner, "click", "inner()");
374 on(&mut scene, root, "click", "root()");
375
376 let results = EventSystem::new().handle(&AppEvent::Click(100.0, 100.0), &mut scene);
377
378 assert_eq!(results.len(), 1);
379 assert_eq!(results[0].target, root);
380 }
381
382 #[test]
383 fn hit_testing_is_inclusive_of_the_box_edges() {
384 let (mut scene, _root, inner) = simple_scene();
385 on(&mut scene, inner, "click", "inner()");
386
387 let mut sys = EventSystem::new();
388 assert_eq!(
390 sys.handle(&AppEvent::Click(50.0, 50.0), &mut scene).len(),
391 1
392 );
393 assert_eq!(
394 sys.handle(&AppEvent::Click(150.0, 150.0), &mut scene).len(),
395 1
396 );
397 assert!(sys
399 .handle(&AppEvent::Click(151.0, 150.0), &mut scene)
400 .is_empty());
401 }
402
403 #[test]
404 fn mouse_move_tracks_the_hovered_node() {
405 let (mut scene, root, inner) = simple_scene();
406 let mut sys = EventSystem::new();
407
408 sys.handle(&AppEvent::MouseMove(100.0, 100.0), &mut scene);
409 assert_eq!(sys.hover_node, Some(inner));
410
411 sys.handle(&AppEvent::MouseMove(5.0, 5.0), &mut scene);
412 assert_eq!(sys.hover_node, Some(root));
413
414 sys.handle(&AppEvent::MouseMove(-10.0, -10.0), &mut scene);
415 assert_eq!(sys.hover_node, None);
416 }
417
418 #[test]
419 fn hover_style_is_applied_on_enter_and_restored_on_leave() {
420 let (mut scene, _root, inner) = simple_scene();
421 let base = scene.get(inner).style.clone();
422 let mut hover = base.clone();
423 hover.background_color = Color::from_rgba(255, 0, 0, 1.0);
424 scene.get_mut(inner).base_style = Some(base.clone());
425 scene.get_mut(inner).hover_style = Some(hover);
426
427 let mut sys = EventSystem::new();
428 sys.handle(&AppEvent::MouseMove(100.0, 100.0), &mut scene);
429 assert!(scene.get(inner).is_hovered);
430 assert_eq!(scene.get(inner).style.background_color.r, 1.0);
431
432 sys.handle(&AppEvent::MouseMove(5.0, 5.0), &mut scene);
433 assert!(!scene.get(inner).is_hovered);
434 assert_eq!(
435 scene.get(inner).style.background_color,
436 base.background_color
437 );
438 }
439
440 #[test]
441 fn a_transition_defers_the_hover_style_instead_of_snapping() {
442 let (mut scene, _root, inner) = simple_scene();
443 let base = scene.get(inner).style.clone();
444 let mut hover = base.clone();
445 hover.background_color = Color::from_rgba(255, 0, 0, 1.0);
446 hover.transition_duration = 0.3;
447 scene.get_mut(inner).base_style = Some(base.clone());
448 scene.get_mut(inner).hover_style = Some(hover);
449
450 EventSystem::new().handle(&AppEvent::MouseMove(100.0, 100.0), &mut scene);
451
452 let node = scene.get(inner);
453 assert!(node.transition_start.is_some());
454 assert!(node.transition_to.is_some());
455 assert_eq!(node.style.background_color, base.background_color);
457 }
458
459 #[test]
460 fn mouseenter_and_mouseleave_handlers_fire_once_per_crossing() {
461 let (mut scene, _root, inner) = simple_scene();
462 on(&mut scene, inner, "mouseenter", "enter()");
463 on(&mut scene, inner, "mouseleave", "leave()");
464 let mut sys = EventSystem::new();
465
466 let enter = sys.handle(&AppEvent::MouseMove(100.0, 100.0), &mut scene);
467 assert_eq!(enter.len(), 1);
468 assert_eq!(enter[0].event_type, "mouseenter");
469
470 assert!(sys
472 .handle(&AppEvent::MouseMove(110.0, 110.0), &mut scene)
473 .is_empty());
474
475 let leave = sys.handle(&AppEvent::MouseMove(5.0, 5.0), &mut scene);
476 assert_eq!(leave.len(), 1);
477 assert_eq!(leave[0].event_type, "mouseleave");
478 assert_eq!(leave[0].target, inner);
479 }
480
481 #[test]
482 fn scrolling_moves_a_scroll_container_and_clamps_at_both_ends() {
483 let mut scene = SceneGraph::new();
484 let root = node(&mut scene, "div", (0.0, 0.0, 200.0, 100.0));
485 scene.get_mut(root).style.overflow = Overflow::Scroll;
486 scene.get_mut(root).content_height = 400.0;
487
488 let mut sys = EventSystem::new();
489 sys.handle(&AppEvent::Scroll(50.0, 50.0, 0.0, 2.0), &mut scene);
490 assert_eq!(scene.get(root).scroll_offset.1, 60.0); sys.handle(&AppEvent::Scroll(50.0, 50.0, 0.0, -10.0), &mut scene);
494 assert_eq!(scene.get(root).scroll_offset.1, 0.0);
495
496 sys.handle(&AppEvent::Scroll(50.0, 50.0, 0.0, 100.0), &mut scene);
498 assert_eq!(scene.get(root).scroll_offset.1, 300.0);
499 }
500
501 #[test]
502 fn scrolling_targets_the_innermost_scrollable_ancestor() {
503 let mut scene = SceneGraph::new();
504 let outer = node(&mut scene, "div", (0.0, 0.0, 200.0, 200.0));
505 scene.get_mut(outer).style.overflow = Overflow::Scroll;
506 scene.get_mut(outer).content_height = 1000.0;
507 let inner = node(&mut scene, "div", (0.0, 0.0, 100.0, 100.0));
508 scene.get_mut(inner).style.overflow = Overflow::Scroll;
509 scene.get_mut(inner).content_height = 1000.0;
510 scene.add_child(outer, inner);
511
512 let mut sys = EventSystem::new();
513 sys.handle(&AppEvent::Scroll(50.0, 50.0, 0.0, 1.0), &mut scene);
514
515 assert_eq!(scene.get(inner).scroll_offset.1, 30.0);
516 assert_eq!(scene.get(outer).scroll_offset.1, 0.0);
517
518 sys.handle(&AppEvent::Scroll(150.0, 150.0, 0.0, 1.0), &mut scene);
520 assert_eq!(scene.get(outer).scroll_offset.1, 30.0);
521 }
522
523 #[test]
524 fn a_non_scrollable_container_ignores_the_wheel() {
525 let mut scene = SceneGraph::new();
526 let root = node(&mut scene, "div", (0.0, 0.0, 200.0, 100.0));
527 scene.get_mut(root).content_height = 400.0;
528
529 EventSystem::new().handle(&AppEvent::Scroll(50.0, 50.0, 0.0, 3.0), &mut scene);
530
531 assert_eq!(scene.get(root).scroll_offset.1, 0.0);
532 }
533
534 #[test]
535 fn keypress_only_dispatches_to_the_focused_node() {
536 let (mut scene, _root, inner) = simple_scene();
537 on(&mut scene, inner, "keypress", "key()");
538 let mut sys = EventSystem::new();
539
540 assert!(sys
541 .handle(&AppEvent::KeyPress("a".into()), &mut scene)
542 .is_empty());
543
544 sys.focus_node = Some(inner);
545 let results = sys.handle(&AppEvent::KeyPress("a".into()), &mut scene);
546 assert_eq!(results.len(), 1);
547 assert_eq!(results[0].event_type, "keypress");
548 assert_eq!(results[0].target, inner);
549 }
550
551 #[test]
552 fn hit_testing_compensates_for_the_scroll_offset_of_a_container() {
553 let mut scene = SceneGraph::new();
554 let root = node(&mut scene, "div", (0.0, 0.0, 200.0, 100.0));
555 scene.get_mut(root).style.overflow = Overflow::Scroll;
556 let item = node(&mut scene, "div", (0.0, 120.0, 200.0, 40.0));
558 scene.add_child(root, item);
559 on(&mut scene, item, "click", "item()");
560
561 let mut sys = EventSystem::new();
562 assert!(sys
564 .handle(&AppEvent::Click(10.0, 30.0), &mut scene)
565 .is_empty());
566
567 scene.get_mut(root).scroll_offset.1 = 100.0;
569 let results = sys.handle(&AppEvent::Click(10.0, 30.0), &mut scene);
570 assert_eq!(results.len(), 1);
571 assert_eq!(results[0].target, item);
572 }
573}