yakui_core/input/
input_state.rs

1use std::cell::{Cell, RefCell};
2use std::collections::HashMap;
3
4use glam::Vec2;
5use smallvec::SmallVec;
6
7use crate::dom::{Dom, DomNode};
8use crate::event::{Event, EventInterest, EventResponse, WidgetEvent};
9use crate::id::WidgetId;
10use crate::layout::LayoutDom;
11use crate::widget::EventContext;
12
13use super::mouse::MouseButton;
14use super::{KeyCode, Modifiers};
15
16/// Holds yakui's input state, like cursor position, hovered, and selected
17/// widgets.
18#[derive(Debug)]
19pub struct InputState {
20    /// State for the mouse, like buttons and position.
21    mouse: RefCell<Mouse>,
22
23    /// State of the keyboard modifier keys
24    modifiers: Cell<Modifiers>,
25
26    /// Details about widgets and their mouse intersections.
27    intersections: RefCell<Intersections>,
28
29    /// The widget that is currently selected.
30    selection: Cell<Option<WidgetId>>,
31
32    /// The widget that was selected last frame.
33    last_selection: Cell<Option<WidgetId>>,
34}
35
36#[derive(Debug)]
37struct Mouse {
38    /// The current mouse position, or `None` if it's outside the window.
39    position: Option<Vec2>,
40
41    /// The state of each mouse button. If missing from the map, the button is
42    /// up and has not yet been pressed.
43    buttons: HashMap<MouseButton, ButtonState>,
44}
45
46#[derive(Debug)]
47struct Intersections {
48    /// All of the widgets with mouse interest that the current mouse position
49    /// intersects with.
50    ///
51    /// All lists like this are stored with the deepest widgets first.
52    mouse_hit: Vec<WidgetId>,
53
54    /// All of the widgets that have had a mouse enter event sent to them
55    /// without a corresponding mouse leave event yet. This is different from
56    /// mouse_hit because hover events can be sunk by event handlers.
57    mouse_entered: Vec<WidgetId>,
58
59    /// All of the widgets that had a mouse enter event sent to them and then
60    /// sunk it that are still being hovered. This helps us ensure that a widget
61    /// that sunk a hover event will continue to occupy that space even if we
62    /// don't send it more events.
63    mouse_entered_and_sunk: Vec<WidgetId>,
64
65    /// All widgets that had the corresponding mouse button pressed while the
66    /// mouse cursor was over them.
67    #[allow(unused)]
68    mouse_down_in: HashMap<MouseButton, Vec<WidgetId>>,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub(crate) enum ButtonState {
73    JustDown,
74    Down,
75    JustUp,
76    Up,
77}
78
79impl ButtonState {
80    pub fn is_down(&self) -> bool {
81        matches!(self, Self::JustDown | Self::Down)
82    }
83
84    pub fn settle(&mut self) {
85        match self {
86            Self::JustDown => {
87                *self = Self::Down;
88            }
89            Self::JustUp => {
90                *self = Self::Up;
91            }
92            _ => (),
93        }
94    }
95}
96
97impl InputState {
98    /// Create a new, empty `InputState`.
99    pub fn new() -> Self {
100        Self {
101            mouse: RefCell::new(Mouse {
102                position: None,
103                buttons: HashMap::new(),
104            }),
105            modifiers: Cell::new(Modifiers::default()),
106            intersections: RefCell::new(Intersections {
107                mouse_hit: Vec::new(),
108                mouse_entered: Vec::new(),
109                mouse_entered_and_sunk: Vec::new(),
110                mouse_down_in: HashMap::new(),
111            }),
112            last_selection: Cell::new(None),
113            selection: Cell::new(None),
114        }
115    }
116
117    /// Begin a new frame for input handling.
118    pub fn start(&self, dom: &Dom, layout: &LayoutDom) {
119        self.notify_selection(dom, layout);
120    }
121
122    /// Finish applying input events for this frame.
123    pub fn finish(&self) {
124        self.settle_buttons();
125    }
126
127    /// Return the currently selected widget, if there is one.
128    pub fn selection(&self) -> Option<WidgetId> {
129        self.selection.get()
130    }
131
132    /// Set the currently selected widget.
133    pub fn set_selection(&self, id: Option<WidgetId>) {
134        self.selection.set(id);
135    }
136
137    pub(crate) fn handle_event(
138        &self,
139        dom: &Dom,
140        layout: &LayoutDom,
141        event: &Event,
142    ) -> EventResponse {
143        match event {
144            Event::CursorMoved(pos) => {
145                self.mouse_moved(dom, layout, *pos);
146                EventResponse::Bubble
147            }
148            Event::MouseButtonChanged { button, down } => {
149                let response = self.mouse_button_changed(dom, layout, *button, *down);
150
151                // If no widgets elected to handle mouse button one going down,
152                // we can should clear our selection.
153                //
154                // FIXME: Currently, this gets sunk by widgets that sink events
155                // but don't do anything to the selection state with them. We
156                // should figure out how to detect that case, like clicking an
157                // Opaque widget.
158                if response == EventResponse::Bubble {
159                    if *button == MouseButton::One && *down {
160                        self.set_selection(None);
161                        self.notify_selection(dom, layout);
162                    }
163                }
164
165                response
166            }
167            Event::MouseScroll { delta } => self.send_mouse_scroll(dom, layout, *delta),
168            Event::KeyChanged { key, down } => self.keyboard_key_changed(dom, layout, *key, *down),
169            Event::ModifiersChanged(modifiers) => self.modifiers_changed(modifiers),
170            Event::TextInput(c) => self.text_input(dom, layout, *c),
171            _ => EventResponse::Bubble,
172        }
173    }
174
175    fn notify_selection(&self, dom: &Dom, layout: &LayoutDom) {
176        let mut current = self.selection.get();
177        let last = self.last_selection.get();
178
179        if current == last {
180            return;
181        }
182
183        if let Some(entered) = current {
184            if let Some(mut node) = dom.get_mut(entered) {
185                self.fire_event(
186                    dom,
187                    layout,
188                    entered,
189                    &mut node,
190                    &WidgetEvent::FocusChanged(true),
191                );
192            } else {
193                self.selection.set(None);
194                current = None;
195            }
196        }
197
198        if let Some(left) = last {
199            if let Some(mut node) = dom.get_mut(left) {
200                self.fire_event(
201                    dom,
202                    layout,
203                    left,
204                    &mut node,
205                    &WidgetEvent::FocusChanged(false),
206                );
207            }
208        }
209
210        self.last_selection.set(current);
211    }
212
213    /// Signal that the mouse has moved.
214    fn mouse_moved(&self, dom: &Dom, layout: &LayoutDom, pos: Option<Vec2>) {
215        let pos = pos.map(|pos| pos - layout.unscaled_viewport().pos());
216
217        {
218            let mut mouse = self.mouse.borrow_mut();
219            mouse.position = pos;
220        }
221
222        self.send_mouse_move(dom, layout);
223        self.mouse_hit_test(dom, layout);
224        self.send_mouse_enter(dom, layout);
225        self.send_mouse_leave(dom, layout);
226    }
227
228    /// Signal that a mouse button's state has changed.
229    fn mouse_button_changed(
230        &self,
231        dom: &Dom,
232        layout: &LayoutDom,
233        button: MouseButton,
234        down: bool,
235    ) -> EventResponse {
236        {
237            let mut mouse = self.mouse.borrow_mut();
238            let state = mouse.buttons.entry(button).or_insert(ButtonState::Up);
239
240            match (state.is_down(), down) {
241                // If the state didn't actually change, leave the current value
242                // alone.
243                (true, true) | (false, false) => (),
244
245                (false, true) => {
246                    *state = ButtonState::JustDown;
247                }
248
249                (true, false) => {
250                    *state = ButtonState::JustUp;
251                }
252            }
253        }
254
255        self.send_button_change(dom, layout, button, down)
256    }
257
258    fn keyboard_key_changed(
259        &self,
260        dom: &Dom,
261        layout: &LayoutDom,
262        key: KeyCode,
263        down: bool,
264    ) -> EventResponse {
265        let selected = self.selection.get();
266        if let Some(id) = selected {
267            let Some(layout_node) = layout.get(id) else {
268                return EventResponse::Bubble;
269            };
270
271            if layout_node
272                .event_interest
273                .contains(EventInterest::FOCUSED_KEYBOARD)
274            {
275                // Panic safety: if this node is in the layout DOM, it must be
276                // in the DOM.
277                let mut node = dom.get_mut(id).unwrap();
278                let event = WidgetEvent::KeyChanged {
279                    key,
280                    down,
281                    modifiers: self.modifiers.get(),
282                };
283                return self.fire_event(dom, layout, id, &mut node, &event);
284            }
285        }
286
287        EventResponse::Bubble
288    }
289
290    fn modifiers_changed(&self, modifiers: &Modifiers) -> EventResponse {
291        self.modifiers.set(*modifiers);
292        EventResponse::Bubble
293    }
294
295    fn text_input(&self, dom: &Dom, layout: &LayoutDom, c: char) -> EventResponse {
296        let selected = self.selection.get();
297        if let Some(id) = selected {
298            let Some(layout_node) = layout.get(id) else {
299                return EventResponse::Bubble;
300            };
301
302            if layout_node
303                .event_interest
304                .contains(EventInterest::FOCUSED_KEYBOARD)
305            {
306                // Panic safety: if this node is in the layout DOM, it must be
307                // in the DOM.
308                let mut node = dom.get_mut(id).unwrap();
309                let event = WidgetEvent::TextInput(c);
310                return self.fire_event(dom, layout, id, &mut node, &event);
311            }
312        }
313
314        EventResponse::Bubble
315    }
316
317    fn send_button_change(
318        &self,
319        dom: &Dom,
320        layout: &LayoutDom,
321        button: MouseButton,
322        down: bool,
323    ) -> EventResponse {
324        let mouse = self.mouse.borrow();
325        let intersections = self.intersections.borrow();
326        let mut overall_response = EventResponse::Bubble;
327
328        for &id in &intersections.mouse_hit {
329            if let Some(mut node) = dom.get_mut(id) {
330                let event = WidgetEvent::MouseButtonChanged {
331                    button,
332                    down,
333                    inside: true,
334                    position: mouse.position.unwrap_or(Vec2::ZERO) / layout.scale_factor(),
335                    modifiers: self.modifiers.get(),
336                };
337                let response = self.fire_event(dom, layout, id, &mut node, &event);
338
339                if response == EventResponse::Sink {
340                    overall_response = response;
341                    break;
342                }
343            }
344        }
345
346        for (id, interest) in layout.interest_mouse.iter() {
347            if interest.contains(EventInterest::MOUSE_OUTSIDE)
348                && !intersections.mouse_hit.contains(&id)
349            {
350                if let Some(mut node) = dom.get_mut(id) {
351                    let event = WidgetEvent::MouseButtonChanged {
352                        button,
353                        down,
354                        inside: false,
355                        position: mouse.position.unwrap_or(Vec2::ZERO) / layout.scale_factor(),
356                        modifiers: self.modifiers.get(),
357                    };
358                    self.fire_event(dom, layout, id, &mut node, &event);
359                }
360            }
361        }
362
363        overall_response
364    }
365
366    fn send_mouse_scroll(&self, dom: &Dom, layout: &LayoutDom, delta: Vec2) -> EventResponse {
367        let intersections = self.intersections.borrow();
368
369        let mut overall_response = EventResponse::Bubble;
370
371        for &id in &intersections.mouse_hit {
372            if let Some(mut node) = dom.get_mut(id) {
373                let event = WidgetEvent::MouseScroll { delta };
374                let response = self.fire_event(dom, layout, id, &mut node, &event);
375
376                if response == EventResponse::Sink {
377                    overall_response = response;
378                    break;
379                }
380            }
381        }
382
383        overall_response
384    }
385
386    fn send_mouse_move(&self, dom: &Dom, layout: &LayoutDom) {
387        let mouse = self.mouse.borrow();
388        let pos = mouse.position.map(|pos| pos / layout.scale_factor());
389        let event = WidgetEvent::MouseMoved(pos);
390
391        for (id, interest) in layout.interest_mouse.iter() {
392            if interest.intersects(EventInterest::MOUSE_MOVE) {
393                if let Some(mut node) = dom.get_mut(id) {
394                    self.fire_event(dom, layout, id, &mut node, &event);
395                }
396            }
397        }
398    }
399
400    fn send_mouse_enter(&self, dom: &Dom, layout: &LayoutDom) {
401        let mut intersections = self.intersections.borrow_mut();
402        let intersections = &mut *intersections;
403
404        for &hit in &intersections.mouse_hit {
405            if let Some(mut node) = dom.get_mut(hit) {
406                if !intersections.mouse_entered.contains(&hit) {
407                    intersections.mouse_entered.push(hit);
408
409                    let response =
410                        self.fire_event(dom, layout, hit, &mut node, &WidgetEvent::MouseEnter);
411
412                    if response == EventResponse::Sink {
413                        intersections.mouse_entered_and_sunk.push(hit);
414                        break;
415                    }
416                } else if intersections.mouse_entered_and_sunk.contains(&hit) {
417                    // This widget was hovered previously, is still hovered, and
418                    // sunk the mouse enter event before. In order to prevent
419                    // erroneously hovering other widgets, continue sinking this
420                    // event.
421                    break;
422                }
423            }
424        }
425    }
426
427    fn send_mouse_leave(&self, dom: &Dom, layout: &LayoutDom) {
428        let mut intersections = self.intersections.borrow_mut();
429
430        let mut to_remove = SmallVec::<[WidgetId; 4]>::new();
431
432        for &hit in &intersections.mouse_entered {
433            if !intersections.mouse_hit.contains(&hit) {
434                if let Some(mut node) = dom.get_mut(hit) {
435                    self.fire_event(dom, layout, hit, &mut node, &WidgetEvent::MouseLeave);
436                }
437
438                to_remove.push(hit);
439            }
440        }
441
442        for remove in to_remove {
443            intersections.mouse_entered.retain(|&id| id != remove);
444            intersections
445                .mouse_entered_and_sunk
446                .retain(|&id| id != remove);
447        }
448    }
449
450    fn mouse_hit_test(&self, dom: &Dom, layout: &LayoutDom) {
451        let mut intersections = self.intersections.borrow_mut();
452        let mouse = self.mouse.borrow();
453
454        intersections.mouse_hit.clear();
455
456        if let Some(mut mouse_pos) = mouse.position {
457            mouse_pos /= layout.scale_factor();
458            hit_test(dom, layout, mouse_pos, &mut intersections.mouse_hit);
459        }
460    }
461
462    fn settle_buttons(&self) {
463        let mut mouse = self.mouse.borrow_mut();
464
465        for state in mouse.buttons.values_mut() {
466            state.settle();
467        }
468    }
469
470    /// Notify the widget of an event, pushing it onto the stack first to ensure
471    /// that the DOM will have the correct widget at the top of the stack if
472    /// queried.
473    fn fire_event(
474        &self,
475        dom: &Dom,
476        layout: &LayoutDom,
477        id: WidgetId,
478        node: &mut DomNode,
479        event: &WidgetEvent,
480    ) -> EventResponse {
481        let context = EventContext {
482            dom,
483            layout,
484            input: self,
485        };
486
487        dom.enter(id);
488        let response = node.widget.event(context, event);
489        dom.exit(id);
490
491        response
492    }
493}
494
495#[profiling::function]
496fn hit_test(_dom: &Dom, layout: &LayoutDom, coords: Vec2, output: &mut Vec<WidgetId>) {
497    for (id, _interest) in layout.interest_mouse.iter() {
498        let Some(layout_node) = layout.get(id) else {
499            continue;
500        };
501
502        let mut rect = layout_node.rect;
503        let mut node = layout_node;
504        while let Some(parent) = node.clipped_by {
505            node = layout.get(parent).unwrap();
506            rect = rect.constrain(node.rect);
507        }
508
509        if rect.contains_point(coords) {
510            output.push(id);
511        }
512    }
513}