Skip to main content

foundry_runtime/
events.rs

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), // x, y, dx, dy
9}
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            // Walk up from hit node, collecting click handlers (bubble)
54            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            // Remove hover style from old node
77            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                        // Start transition back to base
93                        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            // Apply hover style to new node
106            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                        // Start transition to hover style
127                        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        // Find the deepest scrollable container at (x, y)
147        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        // Account for scroll offset
196        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        // Adjust child hit coords for this node's scroll offset
207        let child_y = y + scroll_y;
208
209        // Check children in reverse order (last drawn = on top)
210        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        // Check children first (deepest scrollable wins)
248        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}