Skip to main content

densa_ui/
dispatch.rs

1//! Delivering input: hit-testing, hover, pointer capture, bubbling to parents, focus.
2
3use std::time::Duration;
4
5use densa_core::Point;
6
7use crate::ui::Press;
8use crate::{
9    Event, EventCx, Handled, Ime, Key, KeyEvent, NamedKey, Overflow, PointerButton, PointerEvent,
10    PointerKind, Ui, WidgetId,
11};
12
13/// Presses closer than this in time count as one multi-click.
14const MULTI_CLICK_TIME: Duration = Duration::from_millis(500);
15/// ... and closer than this in logical pixels.
16const MULTI_CLICK_DISTANCE: f32 = 4.0;
17
18impl Ui {
19    /// Delivers a pointer event.
20    ///
21    /// A press goes to the topmost widget under the pointer, then up its parents until one
22    /// handles it; that widget then holds the pointer: it alone gets the moves, any other
23    /// press and the release, which ends the hold. A release no widget holds goes nowhere,
24    /// so a press elsewhere never ends in another widget's click. Moves and scrolls without a
25    /// holder go to the widget under the pointer, then up. A press first focuses the nearest
26    /// widget (from the one under the pointer up) that accepts focus. Hover follows the
27    /// pointer, except while a widget holds it: `Enter` and `Leave` go to the topmost widget
28    /// the pointer comes over and leaves (its parents see [`PaintCx::is_hovered`]).
29    ///
30    /// Open popups are over everything. A press or a scroll outside all of them closes them
31    /// and goes no further, unless it is on the widget that opened them: that widget gets it
32    /// with the popups still open (a menu bar closing its menu, or switching to another),
33    /// and they close only if it does not handle it. A secondary press also reports
34    /// [`ActionKind::ContextMenu`], except inside a popup (outside them it closes them and
35    /// reports it for the widget under the pointer).
36    ///
37    /// [`PaintCx::is_hovered`]: crate::PaintCx::is_hovered
38    /// [`ActionKind::ContextMenu`]: crate::ActionKind::ContextMenu
39    pub fn pointer(&mut self, mut event: PointerEvent) -> Handled {
40        self.layout();
41        match event.kind {
42            PointerKind::Down(button) => event.clicks = self.count_clicks(&event, button),
43            // The Ui sends these itself.
44            PointerKind::Enter | PointerKind::Leave | PointerKind::Cancel => return Handled::No,
45            _ => {}
46        }
47        self.state.pointer = Some(event.position);
48        let (hit, in_popup) = self.hit_top(event.position);
49        if self.state.capture.is_none() {
50            self.set_hover(hit, event.position);
51        }
52        let outside = !in_popup
53            && !self.state.popups.is_empty()
54            && self.state.capture.is_none()
55            && matches!(event.kind, PointerKind::Down(_) | PointerKind::Scroll(_));
56        if outside {
57            let on_opener = hit.is_some_and(|hit| {
58                self.state.popups.iter().any(|popup| {
59                    popup
60                        .opener
61                        .is_some_and(|opener| self.state.is_within(hit, opener))
62                })
63            });
64            if !on_opener {
65                self.state.close_from(0);
66                // A press that only closed popups does not begin a double click.
67                self.state.last_press = None;
68                // A right-click elsewhere closes a context menu and asks for one there, as
69                // native menus do.
70                if event.kind == PointerKind::Down(PointerButton::Secondary) {
71                    self.context_menu(hit.unwrap_or(self.state.root), event.position);
72                }
73                self.settle();
74                return Handled::Yes;
75            }
76        }
77        let delivered = Event::Pointer(event);
78        let handled = match (event.kind, self.state.capture) {
79            (PointerKind::Up(_), holder) => {
80                // The holder still holds the pointer while it handles the release.
81                let handled =
82                    holder.is_some_and(|holder| self.deliver(holder, &delivered) == Handled::Yes);
83                self.state.capture = None;
84                self.set_hover(hit, event.position);
85                handled
86            }
87            (_, Some(holder)) => self.deliver(holder, &delivered) == Handled::Yes,
88            (PointerKind::Down(button), None) => {
89                self.focus_from(hit);
90                let handler = self.bubble(hit, &delivered);
91                self.state.capture = handler;
92                if button == PointerButton::Secondary && !in_popup {
93                    let source = handler.or(hit).unwrap_or(self.state.root);
94                    self.context_menu(source, event.position);
95                }
96                if outside && handler.is_none() {
97                    // The opener let the press go: it closes the popups as any press would.
98                    self.state.close_from(0);
99                }
100                handler.is_some()
101            }
102            (_, None) => {
103                let handled = self.bubble(hit, &delivered).is_some();
104                if outside && !handled {
105                    self.state.close_from(0);
106                }
107                handled
108            }
109        };
110        self.settle();
111        handled.into()
112    }
113
114    /// The pointer left the window: nothing is hovered any more (unless a widget holds the
115    /// pointer, which keeps it until the release).
116    pub fn pointer_left(&mut self, position: Point) {
117        self.state.pointer = None;
118        if self.state.capture.is_none() {
119            self.set_hover(None, position);
120        }
121    }
122
123    /// The press in progress was cancelled, say because the window lost focus: the widget
124    /// holding the pointer gets [`PointerKind::Cancel`] and lets it go, and nothing is
125    /// hovered.
126    pub fn cancel_pointer(&mut self) {
127        let at = self.state.pointer.unwrap_or(Point::ORIGIN);
128        if let Some(holder) = self.state.capture {
129            let cancel = PointerEvent::new(PointerKind::Cancel, at);
130            self.deliver(holder, &Event::Pointer(cancel));
131            self.state.capture = None;
132        }
133        self.state.pointer = None;
134        self.set_hover(None, at);
135        self.settle();
136    }
137
138    /// Delivers a key event to the focused widget (the root if none), then up its parents
139    /// until one handles it. An unhandled Tab moves focus to the next widget that accepts it
140    /// (Shift+Tab to the previous), in tree order (within the topmost popup while popups are
141    /// open). An unhandled context menu key (or Shift+F10) reports
142    /// [`ActionKind::ContextMenu`](crate::ActionKind::ContextMenu) for the focused widget.
143    pub fn key(&mut self, event: &KeyEvent<'_>) -> Handled {
144        self.layout();
145        let target = self.state.focus.or(Some(self.state.root));
146        let mut handled = self.bubble_key(target, &Event::Key(*event)).is_some();
147        let menu_key = event.key == Key::Named(NamedKey::ContextMenu)
148            || (event.key == Key::Named(NamedKey::F(10)) && event.modifiers.shift());
149        if !handled && event.pressed && menu_key {
150            let in_popup = self.state.focus.is_some_and(|focus| {
151                self.state
152                    .popups
153                    .iter()
154                    .any(|p| self.state.is_within(focus, p.id))
155            });
156            if let Some(focus) = self.state.focus.filter(|_| !in_popup) {
157                let rect = self.state.nodes[focus.slot()].rect;
158                self.context_menu(focus, Point::new(rect.left(), rect.top()));
159                handled = true;
160            }
161        }
162        if !handled && event.pressed && event.key == Key::Named(NamedKey::Tab) {
163            let next = self.next_focus(!event.modifiers.shift());
164            if next.is_some() {
165                self.give_focus(next);
166            }
167            handled = true;
168        }
169        self.settle();
170        handled.into()
171    }
172
173    /// Delivers an input method event to the focused widget, then up its parents.
174    pub fn ime(&mut self, ime: &Ime<'_>) -> Handled {
175        self.layout();
176        let Some(focus) = self.state.focus else {
177            return Handled::No;
178        };
179        let handled = self.bubble(Some(focus), &Event::Ime(*ime)).is_some();
180        self.settle();
181        handled.into()
182    }
183
184    /// The widget with keyboard focus.
185    pub fn focus(&self) -> Option<WidgetId> {
186        self.state.focus
187    }
188
189    /// Gives keyboard focus to `id` (`None`: to nobody), sending `FocusLost` and
190    /// `FocusGained`. A removed or hidden widget gets nothing.
191    pub fn set_focus(&mut self, id: Option<WidgetId>) {
192        self.give_focus(id);
193        self.settle();
194    }
195
196    pub(crate) fn give_focus(&mut self, id: Option<WidgetId>) {
197        let id = id.filter(|id| self.state.is_shown(*id));
198        if id == self.state.focus {
199            return;
200        }
201        let old = std::mem::replace(&mut self.state.focus, id);
202        if let Some(old) = old {
203            self.deliver(old, &Event::FocusLost);
204        }
205        if let Some(new) = id {
206            self.deliver(new, &Event::FocusGained);
207        }
208        self.state.needs_paint = true;
209    }
210
211    /// The widget under the pointer, deepest first.
212    pub fn hovered(&self) -> Option<WidgetId> {
213        self.state.hover
214    }
215
216    /// The topmost visible widget at `at`: later children are over earlier ones, and a
217    /// widget that clips hides its children outside it.
218    pub fn hit_test(&mut self, at: Point) -> Option<WidgetId> {
219        self.layout();
220        self.hit_top(at).0
221    }
222
223    /// The topmost widget at `at`, popups first; and whether it is in a popup.
224    pub(crate) fn hit_top(&self, at: Point) -> (Option<WidgetId>, bool) {
225        let layer = &self.state.nodes[self.state.layer.slot()];
226        for &popup in layer.children.iter().rev() {
227            if let Some(hit) = self.hit(popup, at) {
228                return (Some(hit), true);
229            }
230        }
231        (self.hit(self.state.root, at), false)
232    }
233
234    /// Reports a context menu request for `source` at `at`.
235    fn context_menu(&mut self, source: WidgetId, at: Point) {
236        self.state.actions.push_back(crate::Action {
237            source,
238            kind: crate::ActionKind::ContextMenu { at },
239        });
240    }
241
242    pub(crate) fn hit(&self, id: WidgetId, at: Point) -> Option<WidgetId> {
243        let node = &self.state.nodes[id.slot()];
244        if node.style.hidden {
245            return None;
246        }
247        let clips = node.style.overflow != Overflow::Visible;
248        if clips && !node.rect.contains(at) {
249            return None;
250        }
251        for &child in node.children.iter().rev() {
252            if let Some(hit) = self.hit(child, at) {
253                return Some(hit);
254            }
255        }
256        node.rect.contains(at).then_some(id)
257    }
258
259    fn deliver(&mut self, id: WidgetId, event: &Event<'_>) -> Handled {
260        if !self.state.is_live(id) {
261            return Handled::No;
262        }
263        let Some(widget) = self.widgets.get_mut(id.slot()).and_then(Option::as_mut) else {
264            return Handled::No;
265        };
266        let mut cx = EventCx {
267            state: &mut self.state,
268            id,
269        };
270        widget.event(&mut cx, event)
271    }
272
273    /// Delivers a key `event` to `target`, then its parents, until one handles it; from a
274    /// popup it goes on to the widget that opened the popup (a menu passing Left and Right on
275    /// to its menu bar, a submenu passing them to its menu).
276    fn bubble_key(&mut self, mut target: Option<WidgetId>, event: &Event<'_>) -> Option<WidgetId> {
277        while let Some(id) = target.filter(|id| self.state.is_live(*id)) {
278            if self.deliver(id, event) == Handled::Yes {
279                return Some(id);
280            }
281            let parent = self.state.nodes[id.slot()].parent;
282            target = match parent {
283                Some(layer) if layer == self.state.layer => self
284                    .state
285                    .popups
286                    .iter()
287                    .find(|popup| popup.id == id)
288                    .and_then(|popup| popup.opener),
289                _ => parent,
290            };
291        }
292        None
293    }
294
295    /// Delivers `event` to `target`, then its parents, until one handles it.
296    fn bubble(&mut self, mut target: Option<WidgetId>, event: &Event<'_>) -> Option<WidgetId> {
297        while let Some(id) = target.filter(|id| self.state.is_live(*id)) {
298            if self.deliver(id, event) == Handled::Yes {
299                return Some(id);
300            }
301            target = self.state.nodes[id.slot()].parent;
302        }
303        None
304    }
305
306    pub(crate) fn set_hover(&mut self, hover: Option<WidgetId>, position: Point) {
307        let old = self.state.hover;
308        if old == hover {
309            return;
310        }
311        self.mark_hovered(old, false);
312        self.mark_hovered(hover, true);
313        self.state.hover = hover;
314        if let Some(old) = old {
315            let leave = PointerEvent::new(PointerKind::Leave, position);
316            self.deliver(old, &Event::Pointer(leave));
317        }
318        if let Some(new) = hover {
319            let enter = PointerEvent::new(PointerKind::Enter, position);
320            self.deliver(new, &Event::Pointer(enter));
321        }
322        self.state.needs_paint = true;
323    }
324
325    /// Sets the hovered flag of `from` and its parents.
326    pub(crate) fn mark_hovered(&mut self, from: Option<WidgetId>, hovered: bool) {
327        let mut at = from;
328        while let Some(id) = at.filter(|id| self.state.is_live(*id)) {
329            let node = &mut self.state.nodes[id.slot()];
330            node.hovered = hovered;
331            at = node.parent;
332        }
333    }
334
335    /// Focuses the nearest widget from `from` up that accepts focus, if any.
336    fn focus_from(&mut self, from: Option<WidgetId>) {
337        let mut at = from;
338        while let Some(id) = at.filter(|id| self.state.is_live(*id)) {
339            let widget = self.widgets.get(id.slot()).and_then(Option::as_deref);
340            if widget.is_some_and(|w| w.accepts_focus() && w.focus_on_press()) {
341                self.give_focus(Some(id));
342                return;
343            }
344            at = self.state.nodes[id.slot()].parent;
345        }
346    }
347
348    pub(crate) fn accepts_focus(&self, id: WidgetId) -> bool {
349        self.widgets
350            .get(id.slot())
351            .and_then(Option::as_deref)
352            .is_some_and(|w| w.accepts_focus())
353    }
354
355    /// 1 for a press, or one more than the last press of the same button if it was close in
356    /// time and place.
357    fn count_clicks(&mut self, event: &PointerEvent, button: crate::PointerButton) -> u8 {
358        let near = MULTI_CLICK_DISTANCE * self.state.scale;
359        let clicks = match self.state.last_press {
360            Some(last)
361                if last.button == button
362                    && event.time >= last.time
363                    && event.time - last.time <= MULTI_CLICK_TIME
364                    && (event.position.x - last.position.x).abs() <= near
365                    && (event.position.y - last.position.y).abs() <= near =>
366            {
367                last.clicks.saturating_add(1)
368            }
369            _ => 1,
370        };
371        self.state.last_press = Some(Press {
372            time: event.time,
373            position: event.position,
374            button,
375            clicks,
376        });
377        clicks
378    }
379
380    /// The widget Tab (`forward`) or Shift+Tab moves focus to: the next (or previous) visible
381    /// widget accepting focus in tree order, wrapping around.
382    fn next_focus(&self, forward: bool) -> Option<WidgetId> {
383        let current = self.state.focus;
384        let mut order = FocusOrder {
385            current,
386            seen: false,
387            first: None,
388            last: None,
389            before: None,
390            after: None,
391        };
392        // While popups are open, Tab stays in the topmost.
393        let within = self
394            .state
395            .popups
396            .last()
397            .map_or(self.state.root, |popup| popup.id);
398        self.visit_focusable(within, &mut order);
399        if forward {
400            order.after.or(order.first)
401        } else {
402            order.before.or(order.last)
403        }
404    }
405
406    fn visit_focusable(&self, id: WidgetId, order: &mut FocusOrder) {
407        let node = &self.state.nodes[id.slot()];
408        if node.style.hidden {
409            return;
410        }
411        if self.accepts_focus(id) {
412            order.visit(id);
413        }
414        for &child in &node.children {
415            self.visit_focusable(child, order);
416        }
417    }
418}
419
420/// Focusable widgets seen around the focused one, in tree order.
421struct FocusOrder {
422    current: Option<WidgetId>,
423    seen: bool,
424    first: Option<WidgetId>,
425    last: Option<WidgetId>,
426    before: Option<WidgetId>,
427    after: Option<WidgetId>,
428}
429
430impl FocusOrder {
431    fn visit(&mut self, id: WidgetId) {
432        self.first = self.first.or(Some(id));
433        self.last = Some(id);
434        if Some(id) == self.current {
435            self.seen = true;
436        } else if self.seen {
437            self.after = self.after.or(Some(id));
438        } else {
439            self.before = Some(id);
440        }
441    }
442}