Skip to main content

teksilo_core/widget_tree/
pointer_state.rs

1// SPDX-License-Identifier: MPL-2.0
2// SPDX-FileCopyrightText: 2026 FernTech
3
4//! Pointer / hover / capture / gesture-owner state: the per-node probes
5//! the router consults, the per-pointer `PointerSequence` arbitration that
6//! enrols the competing ancestors, and the gesture-recognizer tick that
7//! drives them.
8
9use super::*;
10use crate::pointer::touch_action::{Axis, PanClaim, TouchAction};
11
12impl WidgetTree {
13    /// This tree's input clock — the one source of
14    /// [`EventTime`](crate::pointer::EventTime)s for everything the pointer
15    /// path does.
16    ///
17    /// A [`MonotonicClock`](crate::pointer::clock::MonotonicClock) anchored at
18    /// the tree epoch by default. The epoch is the same `Instant`
19    /// [`simulated_now`](Self::simulated_now) starts at, so the input timeline
20    /// and the simulated animation timeline are one axis rather than two.
21    pub fn input_clock(&self) -> std::rc::Rc<dyn crate::pointer::clock::InputClock> {
22        self.input_clock.clone()
23    }
24
25    /// Replace the input clock.
26    ///
27    /// A headless test installs a
28    /// [`ManualClock`](crate::pointer::clock::ManualClock) here so gesture
29    /// deadlines fire exactly when it says, with no sleeping and no dependence
30    /// on how long the test itself took.
31    pub fn set_input_clock(&mut self, clock: std::rc::Rc<dyn crate::pointer::clock::InputClock>) {
32        self.input_clock = clock;
33        // The axis just changed underneath. Any offset carried forward from a
34        // hand-back belongs to the *old* clock's readings and means nothing
35        // against the new one, so it goes with it; then re-anchor the simulated
36        // origin against the new clock (or drop it, if the new clock is itself
37        // the virtual axis).
38        self.sim_input_offset = std::time::Duration::ZERO;
39        self.sim_input_origin = None;
40        self.rearm_sim_input_origin();
41    }
42
43    /// Put this tree on the simulated clock, if it is not already.
44    ///
45    /// Called from the one door that moves simulated time, and it moves **both**
46    /// axes together:
47    ///
48    /// * the animation scheduler's stored instants are rebased from the wall
49    ///   clock onto [`simulated_now`](Self::simulated_now), so an animation
50    ///   in flight when the freeze happens keeps the phase it had. Without the
51    ///   rebase every such animation would be measured from a start lying in
52    ///   the simulated clock's future — the simulated clock reads the tree's
53    ///   epoch plus whatever has been advanced, which on a live window is far
54    ///   behind the wall clock — and its elapsed time would clamp to zero for
55    ///   good;
56    /// * the input axis is anchored where it stood — see the
57    ///   `sim_input_origin` field — so no stamp taken before the switch lands
58    ///   in the virtual future.
59    ///
60    /// Re-entrant on purpose: [`resume_real_time`](Self::resume_real_time) may
61    /// have handed time back since the last call, and the next advance has to
62    /// take it again.
63    pub(super) fn enter_simulated_mode(&mut self) {
64        if !self.sim_time_frozen {
65            self.sim_time_frozen = true;
66            let now = std::time::Instant::now();
67            self.animation_scheduler.rebase(now, self.sim_clock);
68            self.announcer_polite.rebase(now, self.sim_clock);
69            self.announcer_assertive.rebase(now, self.sim_clock);
70        }
71        if self.sim_input_origin.is_none() {
72            self.rearm_sim_input_origin();
73        }
74    }
75
76    /// Hand time back to the wall clock, carrying forward everything that was
77    /// advanced while it was simulated.
78    ///
79    /// **Who calls this.** A host that shares a live tree with a real event
80    /// loop — the debug automation bridge — after each operation that may have
81    /// advanced the clock. A headless test does not: a test wants the freeze,
82    /// and wants it to survive between calls, so that two samples it dispatches
83    /// without advancing are stamped the *same* instant rather than however
84    /// many microseconds apart the machine happened to run them.
85    ///
86    /// **The animation axis** is handed back by rebasing the scheduler's stored
87    /// instants the other way, the exact inverse of what
88    /// `enter_simulated_mode` did. An animation half-way through when the
89    /// operation ends is half-way through on the wall clock too, and the next
90    /// real layout pass advances it from there — neither snapped to its end
91    /// (which is what ticking at the wall clock against a start stamped on the
92    /// simulated one gives) nor stuck (which is what ticking a live tree at a
93    /// simulated clock nothing is advancing any more gives).
94    ///
95    /// **The input axis cannot simply drop its origin.** A frozen axis that has
96    /// been advanced reads *ahead* of the raw clock; dropping the origin would
97    /// send [`input_now`](Self::input_now) backwards, and a monotone
98    /// [`EventTime`](crate::pointer::EventTime) is a platform conformance
99    /// invariant every velocity tracker, tap streak and hold relies on. So the
100    /// gap is measured — afresh, against this hand-back's own readings, never
101    /// added to what a previous one measured, and floored at zero for the case
102    /// where the raw clock is already the later of the two — and kept in
103    /// `sim_input_offset`: the reading at this instant is the later of the
104    /// frozen reading and the raw one, and it moves with the wall clock from
105    /// here.
106    ///
107    /// A no-op on a tree that is not simulating time, so calling it after every
108    /// operation costs nothing.
109    pub fn resume_real_time(&mut self) {
110        if !self.sim_time_frozen {
111            return;
112        }
113        self.sim_time_frozen = false;
114        let now = std::time::Instant::now();
115        self.animation_scheduler.rebase(self.sim_clock, now);
116        self.announcer_polite.rebase(self.sim_clock, now);
117        self.announcer_assertive.rebase(self.sim_clock, now);
118        let Some((base, base_at)) = self.sim_input_origin.take() else {
119            // An unanchored clock — a `ManualClock` — *is* the virtual axis and
120            // was never frozen against the wall clock, so there is nothing to
121            // carry forward.
122            return;
123        };
124        let frozen = base + self.sim_clock.saturating_duration_since(base_at);
125        // `saturating_since` rather than `-`: on a tree advanced by less than
126        // it spent on the wall clock the raw reading is already ahead, and the
127        // right offset is then none at all.
128        self.sim_input_offset = frozen.saturating_since(self.input_clock.now());
129    }
130
131    /// Anchor (or drop) the simulated input origin against the current clock.
132    fn rearm_sim_input_origin(&mut self) {
133        self.sim_input_origin = if self.sim_time_frozen && self.input_clock.epoch().is_some() {
134            // `input_now`, not the raw clock: after a hand-back the axis runs
135            // an offset ahead of the clock, and re-freezing at the raw reading
136            // would step it backwards by exactly that offset.
137            Some((self.input_now(), self.sim_clock))
138        } else {
139            // Either the tree still runs on real time, or its clock has no
140            // wall-clock anchor and is moved directly by `advance_time`.
141            None
142        };
143    }
144
145    /// The current time on this tree's input timeline.
146    ///
147    /// While the axis is frozen this is a reading of
148    /// [`sim_clock`](Self::simulated_now), not of the wall clock, so a deadline
149    /// can only be reached by advancing the clock. Once
150    /// [`resume_real_time`](Self::resume_real_time) has handed it back it is
151    /// the clock again, plus everything that was advanced.
152    pub fn input_now(&self) -> crate::pointer::EventTime {
153        match self.sim_input_origin {
154            Some((base, base_at)) => base + self.sim_clock.saturating_duration_since(base_at),
155            None => self.input_clock.now() + self.sim_input_offset,
156        }
157    }
158
159    /// Everything a recognizer on `id` is allowed to know beyond the event in
160    /// front of it: now, the profile for the pointer being dispatched, the
161    /// node's own bounds, and the pointer itself.
162    ///
163    /// Rebuilt per dispatch rather than cached, so a theme change, a density
164    /// change or a different pointer kind reaches the recognizers without any
165    /// of them holding a copy of a threshold.
166    pub(crate) fn recognizer_context(
167        &self,
168        id: WidgetId,
169    ) -> crate::gesture::RecognizerContext<'static> {
170        let pointer = self.current_input.pointer;
171        let profile = *self.effective_theme.input.profile(pointer.kind);
172        let size = self.bounds(id).size();
173        // A dispatch that carries no timestamp of its own (a hand-built
174        // `WidgetEvent` from a test) reads the tree clock instead.
175        let now = if pointer.time == crate::pointer::EventTime::ZERO {
176            self.input_now()
177        } else {
178            pointer.time
179        };
180        crate::gesture::RecognizerContext::new(
181            now,
182            profile,
183            Rect::new(0.0, 0.0, size.width, size.height),
184            pointer,
185        )
186    }
187
188    // -----------------------------------------------------------------
189    // The pointer table
190    // -----------------------------------------------------------------
191
192    /// The pointer this dispatch is serving.
193    ///
194    /// Outside a pointer dispatch the input snapshot holds its default — the
195    /// mouse — which is exactly what every legacy `WidgetEvent` has always
196    /// meant, so a caller that names no pointer keeps naming the mouse.
197    pub(crate) fn current_pointer_id(&self) -> crate::pointer::PointerId {
198        self.current_input.pointer.id
199    }
200
201    /// The widget holding the capture of the pointer this dispatch is serving.
202    ///
203    /// Capture is **per pointer**: two contacts hold independent captures and
204    /// each is released only by its own Up or Cancel. For the mouse — the only
205    /// pointer that existed before the touch programme — this is the old
206    /// singular `pointer_captured_by`, unchanged.
207    pub(crate) fn current_pointer_capture(&self) -> Option<WidgetId> {
208        self.pointers
209            .get(self.current_pointer_id())
210            .and_then(|e| e.captured_by)
211    }
212
213    /// Set (or clear) the capture of the pointer this dispatch is serving.
214    pub(crate) fn set_current_pointer_capture(&mut self, captor: Option<WidgetId>) {
215        let id = self.current_pointer_id();
216        if let Some(entry) = self.pointers.get_mut(id) {
217            entry.captured_by = captor;
218        }
219    }
220
221    /// Set (or clear) the capture of a *named* pointer — the door
222    /// [`EventContext::capture_pointer_id`](crate::widget::EventContext::capture_pointer_id)
223    /// opens for a handler driving a pointer other than the one it is serving.
224    ///
225    /// A capture asked for on a pointer that is not live is dropped, not
226    /// invented: no sample will ever be delivered to it, so an entry conjured
227    /// to hold it would be a capture nothing can release. Reaching this means
228    /// the handler ran outside a pointer dispatch entirely (an assistive
229    /// technology action, a timer), where there is no pointer to capture.
230    pub(crate) fn set_pointer_capture(
231        &mut self,
232        pointer: crate::pointer::PointerId,
233        captor: Option<WidgetId>,
234    ) {
235        if let Some(entry) = self.pointers.get_mut(pointer) {
236            entry.captured_by = captor;
237        }
238    }
239
240    /// Release the capture a drag session was holding.
241    ///
242    /// A drag owns one pointer, but which one is not recorded on the session
243    /// yet (arbitration lands with the gesture package), so this releases both
244    /// the pointer being dispatched and anything the drag's source widget
245    /// still holds. For the mouse those are the same capture, which is why
246    /// this is a faithful stand-in for the old blanket clear.
247    pub(super) fn release_drag_capture(&mut self, source: Option<WidgetId>) {
248        self.set_current_pointer_capture(None);
249        if let Some(src) = source {
250            self.pointers.release_captures_of(src);
251        }
252    }
253
254    /// The widget the **hover owner** is over.
255    ///
256    /// Hover belongs to the hover owner and to nobody else: a contact never
257    /// produces hover, so a finger arriving beside a hovering mouse leaves
258    /// this — and every `on_hover` handler, tooltip dwell and cursor shape —
259    /// exactly where it was.
260    pub(crate) fn hovered_id(&self) -> Option<WidgetId> {
261        self.pointers.hover_owner().and_then(|e| e.hovered)
262    }
263
264    /// Where the hover owner is, if one is live.
265    ///
266    /// The position hover recovery must re-hit-test at. Distinct from
267    /// [`last_pointer_position`](Self::last_pointer_position), which reports
268    /// the *primary* pointer and so answers for a touch-only device too —
269    /// where re-deriving hover from it would invent a hover no finger ever
270    /// produced.
271    pub(crate) fn hover_owner_position(&self) -> Option<teksilo_canvas::Point> {
272        self.pointers.hover_owner().map(|e| e.position)
273    }
274
275    /// Admit the sample being dispatched into the pointer table, refreshing
276    /// its position and its [`PointerInfo`](crate::pointer::PointerInfo), and
277    /// publish the modality.
278    ///
279    /// Returns `false` when the table refused the pointer (a palm, or the
280    /// contact cap), in which case the sample must not be dispatched at all.
281    pub(super) fn admit_current_pointer(
282        &mut self,
283        position: teksilo_canvas::Point,
284        is_down: bool,
285        is_move: bool,
286    ) -> bool {
287        let info = self.current_input.pointer;
288        // Where this pointer was *before* this sample. Only a move of the
289        // hover owner updates `previous_pointer_position`, because the one
290        // reader — the overlay safe triangle — wants the last sample that was
291        // still over the anchor the pointer is leaving, and a press or a
292        // second contact is not that.
293        let was_hover_owner = self.pointers.hover_owner_id() == Some(info.id);
294        let before = self.pointers.get(info.id).map(|e| e.position);
295        if self.pointers.admit(info, position, is_down).is_none() {
296            return false;
297        }
298        if is_move && was_hover_owner {
299            self.previous_pointer_position = before;
300        }
301        if self.last_pointer_kind_signal.get() != info.kind {
302            self.last_pointer_kind_signal.set(info.kind);
303        }
304        true
305    }
306
307    /// Hand the hover-owner role to the pointer being dispatched, and take
308    /// hover away from whoever held it.
309    ///
310    /// The later sample wins: on a machine with both a mouse and a pen, the
311    /// device the user just moved owns hover, and the one that lost it is sent
312    /// a [`PointerLeave`](crate::event::WidgetEvent::PointerLeave) for the
313    /// widget it was over — otherwise that widget stays lit for a pointer that
314    /// is no longer pointing at it. A contact is refused outright.
315    pub(super) fn claim_hover_owner_for_current(&mut self, ops: &mut dyn crate::window::WindowOps) {
316        let id = self.current_pointer_id();
317        let Some(displaced) = self.pointers.claim_hover_owner(id) else {
318            return;
319        };
320        let stale = self
321            .pointers
322            .get_mut(displaced)
323            .and_then(|entry| entry.hovered.take());
324        if let Some(old) = stale {
325            // Credited to the pointer that just *lost* the role — it is the one
326            // no longer pointing at `old` — not to the claimant.
327            let leave = WidgetEvent::PointerLeave {
328                pointer: self
329                    .pointers
330                    .get(displaced)
331                    .map(|entry| entry.info)
332                    .unwrap_or_else(|| crate::pointer::PointerInfo::mouse(self.input_now())),
333            };
334            self.dispatch_to_widget(old, &leave, &mut *ops);
335            self.tooltip_pointer_leave(old, &mut *ops);
336        }
337        // The new owner starts with no hover of its own; the move that gave it
338        // the role establishes one immediately afterwards.
339        self.update_hover_within_signals(stale, None);
340        self.set_hovered(None);
341    }
342
343    /// Whether `id` carries a drag or swipe handler (hence gets a drag/swipe
344    /// recognizer once its arena is built).
345    fn widget_has_drag(&self, id: WidgetId) -> bool {
346        self.arena
347            .get(id)
348            .map(|n| n.any_handler(|h| h.on_drag.is_some() || h.on_swipe.is_some()))
349            .unwrap_or(false)
350    }
351
352    /// Whether `id` is a gesture dead-zone boundary — a press inside its
353    /// subtree must not arm a drag/swipe on any ancestor above it. See
354    /// [`WidgetNode::gesture_dead_zone`](crate::arena::WidgetNode::gesture_dead_zone).
355    fn is_gesture_dead_zone(&self, id: WidgetId) -> bool {
356        self.arena
357            .get(id)
358            .map(|n| n.gesture_dead_zone)
359            .unwrap_or(false)
360    }
361
362    /// Whether `id` is a keyboard-capture surface — while focused it
363    /// receives every `KeyDown` raw, bypassing shortcut resolution. See
364    /// [`WidgetNode::keyboard_capture`](crate::arena::WidgetNode::keyboard_capture).
365    pub(super) fn is_keyboard_capture(&self, id: WidgetId) -> bool {
366        self.arena
367            .get(id)
368            .map(|n| n.keyboard_capture)
369            .unwrap_or(false)
370    }
371
372    // -----------------------------------------------------------------
373    // The arbitration spine: one `PointerSequence` per live pointer
374    // -----------------------------------------------------------------
375
376    /// The frozen hit path for `target`: target → root.
377    fn hit_path(&self, target: WidgetId) -> Vec<WidgetId> {
378        let mut path = Vec::new();
379        let mut current = Some(target);
380        while let Some(id) = current {
381            path.push(id);
382            current = self.arena.parent(id);
383        }
384        path
385    }
386
387    /// The **innermost** gesture dead zone on `path`.
388    ///
389    /// Nothing at or above it may be enrolled — for a mouse exactly as for a
390    /// finger. A dead zone is deliberately *not* sugar for
391    /// [`TouchAction::NONE`]: a mouse ignores touch actions entirely, so the
392    /// substitution would delete the mouse behaviour the flag exists for, and
393    /// on a direct pointer it would drop the latch to `slop_precise` and turn
394    /// the `DeadZone` widget's own regression into a 2 px hair trigger.
395    fn dead_zone_on(&self, path: &[WidgetId]) -> Option<WidgetId> {
396        path.iter()
397            .copied()
398            .find(|id| self.is_gesture_dead_zone(*id))
399    }
400
401    /// The gesture profile for the pointer this dispatch is serving.
402    pub(super) fn current_profile(&self) -> teksilo_tokens::GestureProfile {
403        *self
404            .effective_theme
405            .input
406            .profile(self.current_input.pointer.kind)
407    }
408
409    /// The sequence for the pointer this dispatch is serving.
410    pub(crate) fn current_sequence(&self) -> Option<&crate::gesture::PointerSequence> {
411        self.pointers
412            .get(self.current_pointer_id())
413            .and_then(|e| e.sequence.as_ref())
414    }
415
416    /// Run `f` against the current pointer's sequence, taking it out of the
417    /// table for the duration so `self` stays fully borrowable.
418    ///
419    /// The sequence is put back only if the pointer is still live afterwards —
420    /// a handler that ended the pointer must not have its sequence resurrected.
421    fn with_sequence<R>(
422        &mut self,
423        f: impl FnOnce(&mut Self, &mut crate::gesture::PointerSequence) -> R,
424    ) -> Option<R> {
425        let pointer = self.current_pointer_id();
426        let mut sequence = self.pointers.get_mut(pointer)?.sequence.take()?;
427        let result = f(self, &mut sequence);
428        if let Some(entry) = self.pointers.get_mut(pointer) {
429            entry.sequence = Some(sequence);
430        }
431        Some(result)
432    }
433
434    /// Open the arbitration for the press being dispatched, **before** any
435    /// handler runs.
436    ///
437    /// It has to be before: `ctx.touch_action()` reports the frozen value from
438    /// inside the press handler, and an explicit `capture_pointer()` made there
439    /// needs a sequence to enrol into. Pan claimants are enrolled here too, so
440    /// that the `DragActivation::Auto` question — "is anything else already
441    /// claiming this axis?" — has an answer during the press.
442    ///
443    /// A direct pointer forms **no sequence at all** when
444    /// [`InputTokens::touch_enabled`](teksilo_tokens::InputTokens::touch_enabled)
445    /// is off: the kill switch means the framework arbitrates nothing for a
446    /// contact, and the sample takes the legacy route unchanged.
447    pub(super) fn begin_sequence(&mut self, target: WidgetId, position: teksilo_canvas::Point) {
448        use crate::gesture::{MemberRole, PointerSequence};
449
450        let pointer = self.current_input.pointer;
451        if pointer.kind.is_direct() && !self.effective_theme.input.touch_enabled {
452            crate::trace_input!(
453                Gestures,
454                "no sequence for {:?}: touch_enabled=false",
455                pointer.id
456            );
457            return;
458        }
459        let path = self.hit_path(target);
460        let touch_action = self.effective_touch_action(target);
461        let boundary = self.dead_zone_on(&path);
462        let now = self.recognizer_context(target).now;
463        let mut sequence =
464            PointerSequence::new(pointer, path, touch_action, boundary, position, now);
465
466        // Pan claimants: direct pointers only. `PanClaim::devices` defaults to
467        // DIRECT and the mouse profile has no `pan_slop` at all, so this loop
468        // adds nothing for a mouse — which is what keeps every mouse sequence
469        // arbitrating exactly as `drag_observers` did.
470        let profile = self.current_profile();
471        if pointer.kind.is_direct() {
472            for (id, claim) in self.pan_candidates(target, touch_action) {
473                if sequence.pan_is_eligible(&claim, &profile) {
474                    sequence.enrol(id, MemberRole::Pan(claim));
475                }
476            }
477        }
478
479        crate::trace_input!(
480            Gestures,
481            "sequence opened for {:?}: action={:?} dead_zone={:?} pan_members={}",
482            pointer.id,
483            touch_action,
484            boundary,
485            sequence.members().len()
486        );
487        if let Some(entry) = self.pointers.get_mut(pointer.id) {
488            entry.sequence = Some(sequence);
489        }
490    }
491
492    /// Enrol the competitors that only become knowable once the press has been
493    /// dispatched, and feed each of them the `Down`.
494    ///
495    /// The gesture members are the pre-existing drag observers, expressed on
496    /// the sequence and with the same three rules:
497    ///
498    /// * the captured widget's own drag owns the gesture — it is enrolled as
499    ///   the innermost member and no ancestor is;
500    /// * a dead-zone boundary stops the walk;
501    /// * only nodes carrying `on_drag` / `on_swipe` compete.
502    ///
503    /// The captured widget's arena has already seen this `Down` through the
504    /// normal bubble, so only the ancestors are fed here.
505    ///
506    /// **Both enrolment doors have a dual-role fallback.** A node that declares
507    /// a [`PanClaim`] is already a member by the time this runs —
508    /// `begin_sequence` enrols pan claimants before any handler does anything —
509    /// so if that same node also carries `on_drag`, `enrol` (captured) and
510    /// `enrol_drag` (ancestor) both refuse it. Neither refusal is a decision: it
511    /// is a slot collision. Each falls through to
512    /// [`PointerSequence::defer_own_drag`](crate::gesture::PointerSequence::defer_own_drag),
513    /// which attaches the drag's activation to the member the node already has.
514    ///
515    /// The ancestor door additionally **feeds** the deferred node its `Down`,
516    /// exactly as an ordinarily-enrolled drag ancestor is fed: the press bubble
517    /// stops at the captor (`try_handler_bubble`'s `Down` arm answers `Handled`
518    /// the moment a node has an arena), so without that feed the ancestor's
519    /// `DragRecognizer` has no origin and could never latch however the
520    /// arbitration ruled. The *move* bubble has no such stop, so nothing
521    /// afterwards needs feeding.
522    pub(super) fn enrol_sequence_members(
523        &mut self,
524        down_event: &WidgetEvent,
525        ops: &mut dyn crate::window::WindowOps,
526    ) {
527        use crate::gesture::MemberRole;
528
529        let captured = self.current_pointer_capture();
530        let profile = self.current_profile();
531
532        // Which ancestors compete, decided against the frozen path.
533        let Some(to_enrol) = self.with_sequence(|tree, sequence| {
534            sequence.set_capture(captured);
535            sequence.set_pressed_owner(captured);
536            // A decided sequence still enrols its competitors — as **rejected**
537            // ones. Enrolling them is what lets the router stop their
538            // recognizers from being fed at all: an ancestor that never becomes
539            // a member is invisible to the arbitration, and an explicit captor
540            // would lose the press to it on the very next move.
541            let decided = sequence.is_decided();
542            let Some(captured) = captured else {
543                // Nothing took the press, so there is nothing for an ancestor
544                // to observe *through* — the pre-existing gate, kept verbatim.
545                return Vec::new();
546            };
547            if tree.widget_has_drag(captured) {
548                // The innermost drag owns the gesture: it is the member, and no
549                // ancestor is. Its own arena drives it through the capture
550                // route, so it is never fed here.
551                if sequence.enrol(captured, MemberRole::Gesture) {
552                    if decided {
553                        sequence.reject(captured);
554                    }
555                } else {
556                    // Already enrolled — today only as the pan claimant
557                    // `begin_sequence` put there before any handler ran. One
558                    // node, one member, so its drag does not get a slot of its
559                    // own; `defer_own_drag` gives it a say on the member it
560                    // has, resolving its `DragActivation` against the pan it is
561                    // competing with. A mouse enrols no pan member, so it never
562                    // reaches this arm.
563                    let activation = Self::sequence_drag_activation(tree, sequence, captured);
564                    sequence.defer_own_drag(captured, activation, &profile);
565                }
566                return Vec::new();
567            }
568            if !sequence.may_enrol(captured) {
569                // The press landed inside a dead zone (the captured control
570                // *is* the dead zone) — arm no ancestor at all.
571                return Vec::new();
572            }
573            let mut out = Vec::new();
574            let mut current = tree.arena.parent(captured);
575            while let Some(id) = current {
576                if !sequence.may_enrol(id) {
577                    break;
578                }
579                if tree.widget_has_drag(id) {
580                    let activation = Self::sequence_drag_activation(tree, sequence, id);
581                    if sequence.enrol_drag(id, MemberRole::Gesture, activation, &profile) {
582                        if decided {
583                            sequence.reject(id);
584                        } else {
585                            out.push((id, true));
586                        }
587                    } else if sequence.defer_own_drag(id, activation, &profile) {
588                        // The second door to the same refusal, and the one a
589                        // *heavyweight* child's press goes through: `enrol_drag`
590                        // opens with `enrol`, which declines an ancestor already
591                        // enrolled as this sequence's pan claimant. Without this
592                        // arm that ancestor's drag is never fed the press at all
593                        // — so a dual-role container could not be dragged from
594                        // anywhere its own content took the capture.
595                        //
596                        // No `decided` guard, unlike the branch above: a decided
597                        // sequence is `defer_own_drag`'s own first refusal, so
598                        // this arm is unreachable once one exists.
599                        out.push((id, true));
600                    }
601                }
602                current = tree.arena.parent(id);
603            }
604            out
605        }) else {
606            return;
607        };
608
609        for (id, feed) in to_enrol {
610            if !feed {
611                continue;
612            }
613            // Build the arena (the bubble never reached this ancestor) and feed
614            // it the press so its DragRecognizer records the origin.
615            {
616                let WidgetTree {
617                    arena,
618                    gesture_owners,
619                    ..
620                } = self;
621                if let Some(node) = arena.get_mut(id) {
622                    Self::ensure_gesture_arena(node, id, gesture_owners);
623                }
624            }
625            self.feed_member_arena(id, down_event, &mut *ops);
626        }
627    }
628
629    /// The [`DragActivation`](teksilo_tokens::DragActivation) that governs
630    /// `id`'s own drag for **this** press: the per-press override a handler
631    /// queued with
632    /// [`set_drag_activation`](crate::widget::EventContext::set_drag_activation),
633    /// or failing that the node's build-time declaration.
634    ///
635    /// The override is read off the sequence rather than the node because a
636    /// press handler answers per *press*, not per node — see
637    /// `PointerSequence::drag_activation_overrides`.
638    fn sequence_drag_activation(
639        tree: &WidgetTree,
640        sequence: &crate::gesture::PointerSequence,
641        id: WidgetId,
642    ) -> teksilo_tokens::DragActivation {
643        sequence.drag_activation_override(id).unwrap_or_else(|| {
644            tree.arena
645                .get(id)
646                .map(|n| n.drag_activation)
647                .unwrap_or(teksilo_tokens::DragActivation::Auto)
648        })
649    }
650
651    /// A handler chose a [`DragActivation`](teksilo_tokens::DragActivation) for
652    /// **this press** with
653    /// [`set_drag_activation`](crate::widget::EventContext::set_drag_activation).
654    ///
655    /// Stashed on the sequence, so it dies with the press. Writing it back onto
656    /// the node would outlive the press it was chosen for — and
657    /// `on_pointer_event` previews root-first over every strict ancestor of the
658    /// target, so a node that answers here also answers for presses it does not
659    /// own.
660    pub(super) fn note_drag_activation_override(
661        &mut self,
662        source: WidgetId,
663        activation: teksilo_tokens::DragActivation,
664    ) {
665        self.with_sequence(|_, sequence| {
666            sequence.set_drag_activation_override(source, activation);
667        });
668    }
669
670    /// Record where the pointer is, so every positional threshold reads one
671    /// number rather than each member tracking its own.
672    pub(super) fn note_sequence_position(&mut self, position: teksilo_canvas::Point) {
673        let pointer = self.current_pointer_id();
674        if let Some(entry) = self.pointers.get_mut(pointer)
675            && let Some(sequence) = entry.sequence.as_mut()
676        {
677            sequence.set_last_position(position);
678        }
679    }
680
681    /// Now, on the input timeline, for the sample being dispatched.
682    ///
683    /// A hand-built `WidgetEvent` carries no timestamp, so it reads the tree
684    /// clock — which is what lets a test drive a deadline with a
685    /// [`ManualClock`](crate::pointer::clock::ManualClock).
686    pub(super) fn sequence_now(&self) -> crate::pointer::EventTime {
687        let stamped = self.current_input.pointer.time;
688        if stamped == crate::pointer::EventTime::ZERO {
689            self.input_now()
690        } else {
691            stamped
692        }
693    }
694
695    /// **Timers before positional thresholds** — step 4 of the decision
696    /// procedure, run before the move is dispatched anywhere.
697    ///
698    /// It has to be before: a member reached through the ordinary capture
699    /// bubble would otherwise recognize on this very sample, and a deferred
700    /// drag that should have withdrawn — or a peer that should have been frozen
701    /// by a hold — would already have won by the time the arbitration was
702    /// consulted.
703    ///
704    /// Three rules, all no-ops for a press that stayed where it landed:
705    ///
706    /// * a hold older than `profile.max_hold` is released, because the
707    ///   framework never trusts a holder to answer — the one rule here that is
708    ///   *also* driven by the clock, through
709    ///   [`expire_sequence_holds`](Self::expire_sequence_holds), so a contact
710    ///   that never moves is released on time too;
711    /// * a member armed by [`DragActivation::AfterLongPress`](teksilo_tokens::DragActivation::AfterLongPress) withdraws once
712    ///   the press leaves the tap boundary — that travel is a pan, not a
713    ///   considered grab — and so does the *self-drag* half of a dual-role
714    ///   member, which is deferred by the same resolution but cannot withdraw
715    ///   the member itself (the member is a pan claimant and goes on competing).
716    ///   The self-drag has a **second** positional rule the whole-member case
717    ///   does not: travel past `long_press_slop` before the deadline, the rule
718    ///   [`LongPressRecognizer`](crate::gesture::LongPressRecognizer) applies to
719    ///   itself, which is what makes its deferral a hold rather than a timer;
720    /// * the pressed node's **tap family** is revoked, once, when the press
721    ///   leaves the tap boundary — WCAG 2.2 SC 2.5.2's "slide off to abort":
722    ///   the activation is abandoned, a drag the same press started is not.
723    ///
724    /// All of them but that second self-drag rule read the one
725    /// [`TapBoundary`](crate::gesture::TapBoundary) predicate, which is also
726    /// what `TapRecognizer` fails on, so the router and the recognizer cannot
727    /// disagree about whether a press has slid off.
728    pub(super) fn tick_sequence_timers(&mut self) {
729        use crate::gesture::MemberState;
730
731        let profile = self.current_profile();
732        let now = self.sequence_now();
733        let Some(revoke) = self.with_sequence(|tree, sequence| {
734            sequence.expire_holds(now, &profile);
735            if sequence.is_decided() {
736                return None;
737            }
738            let origin = sequence.press_origin();
739            let position = sequence.last_position();
740            let boundary = crate::gesture::TapBoundary::for_pointer(&sequence.pointer(), &profile);
741            let left = |id: WidgetId| {
742                let bounds = tree.arena.is_active(id).then(|| tree.arena.bounds(id));
743                boundary.left(origin, position, bounds, &profile)
744            };
745            let rejects: Vec<WidgetId> = sequence
746                .members()
747                .iter()
748                .filter(|m| m.state == MemberState::Possible && m.rejects_on_tap_slop)
749                .filter(|m| left(m.id))
750                .map(|m| m.id)
751                .collect();
752            for id in rejects {
753                sequence.reject(id);
754            }
755            // The *self*-drag half of a dual-role member withdraws on either
756            // positional rule, because the member itself is a pan claimant and
757            // goes on competing — only its drag half can be taken out.
758            //
759            // `long_press_slop` is the load-bearing one, and it is the rule
760            // `LongPressRecognizer` applies to itself: it fails on the first
761            // move past that slop rather than waiting for its timer. Arming the
762            // self-drag on the clock alone made the deferral a *timer*, so a
763            // deliberate, slow pan — a finger positioning precisely, which is
764            // exactly when a scene is panned slowly — crossed the deadline
765            // mid-travel, armed the grab and lost the pan for the rest of the
766            // press. A hold that has already wandered 18 dp is not a hold.
767            //
768            // `TapBoundary` stays beside it and is not redundant: it is the
769            // node's own rect for a coarse pointer, so it bites on a small node
770            // the finger slides off without travelling far, where the radius
771            // does not. Conversely a viewport-filling claimant is never left, so
772            // on that shape the radius is the only positional rule there is.
773            //
774            // Both apply only while the self-drag is still unripe. Once the hold
775            // has been served the grab is live, and travel is the grab doing its
776            // job.
777            let travelled_past_hold = sequence.travel() > profile.long_press_slop;
778            let withdraw: Vec<WidgetId> = sequence
779                .unripe_own_drag_members(now)
780                .into_iter()
781                .filter(|id| travelled_past_hold || left(*id))
782                .collect();
783            for id in withdraw {
784                sequence.withdraw_own_drag(id);
785            }
786            let owner = sequence.pressed_owner()?;
787            if sequence.taps_cancelled() || !left(owner) {
788                return None;
789            }
790            sequence.set_taps_cancelled();
791            Some(owner)
792        }) else {
793            return;
794        };
795        let Some(owner) = revoke else {
796            return;
797        };
798        let pointer = self.current_pointer_id();
799        if let Some(node) = self.arena.get_mut(owner)
800            && let Some(set) = node.handlers.gesture_arena.as_mut()
801        {
802            set.cancel_taps(pointer);
803        }
804    }
805
806    /// Advance an undecided sequence with a move: **timers before positional
807    /// thresholds**, then members innermost-first.
808    ///
809    /// * a `RawDrag` member wins past the sequence's latch slop;
810    /// * a `Gesture` member wins when its own recognizer recognizes — which for
811    ///   a mouse is at `drag_slop`, the 5.0 it has always been;
812    /// * a `Pan` member wins only on an axis the frozen `TouchAction` permits
813    ///   and only past `pan_slop`, which a mouse profile does not have;
814    /// * a member deferred by [`DragActivation::AfterLongPress`](teksilo_tokens::DragActivation::AfterLongPress) cannot win
815    ///   before its timer and self-rejects once the press leaves the tap
816    ///   boundary.
817    ///
818    /// A `Gesture` member whose id is the press owner is skipped **and stops
819    /// the walk**: its recognizer is already being driven by the capture
820    /// dispatch, and letting an ancestor past it would break the "innermost
821    /// drag owns the gesture" rule. A `RawDrag` and a `Pan` are not, because
822    /// this walk is the *only* place either is ever evaluated — the press owner
823    /// is very often a pan claimant, since an implicit arena capture makes any
824    /// node with a tap handler the owner, and an editing surface has both.
825    /// `RawPreview` rides along in the same match and is dead there: a preview
826    /// claim decides the sequence as it is enrolled, and a decided sequence
827    /// yields no candidates at all.
828    ///
829    /// A **dual-role** member — one whose node also owns `on_drag`, so
830    /// [`PointerSequence::defer_own_drag`](crate::gesture::PointerSequence::defer_own_drag)
831    /// attached its self-drag to the same slot — is evaluated here **only** as
832    /// the pan it is enrolled as. Its own recognizers are driven by the ordinary
833    /// move bubble and gated there by `sequence_blocks_arena`, which reads the
834    /// same deferral, so its drag half needs nothing from this walk.
835    ///
836    /// That asymmetry is the bubble's, not this walk's, and it is worth stating
837    /// because it decides where a dual-role node's press and its moves each come
838    /// from. `try_handler_bubble`'s **`Down`** arm returns `Handled` the moment a
839    /// node has an arena, so the press bubble stops at the captor and an
840    /// *ancestor*'s recognizers never see the origin — which is why
841    /// `enrol_sequence_members` feeds the `Down` explicitly, for a dual-role
842    /// ancestor exactly as for an ordinary drag ancestor. Its **`Move`** arm
843    /// returns `Ignored` when nothing recognized, so the move bubble carries on
844    /// past the captor and reaches every ancestor by itself.
845    pub(super) fn advance_sequence(
846        &mut self,
847        move_event: &WidgetEvent,
848        ops: &mut dyn crate::window::WindowOps,
849    ) {
850        use crate::gesture::MemberRole;
851
852        let profile = self.current_profile();
853        let now = self.sequence_now();
854
855        let Some(candidates) = self.with_sequence(|_, sequence| {
856            if sequence.is_decided() || sequence.is_held() {
857                // No peer may win while a member is deferring its own answer.
858                return Vec::new();
859            }
860            sequence
861                .members()
862                .iter()
863                .filter(|m| m.is_eligible_at(now))
864                .map(|m| (m.id, m.role))
865                .collect()
866        }) else {
867            return;
868        };
869
870        // The stop rule reads the node whose arena took the **press**, not the
871        // live captor: a member that wins mid-dispatch takes the capture, and
872        // keying on the live value would make the winner look like the thing
873        // that stops the walk.
874        let owner = self.current_sequence().and_then(|s| s.pressed_owner());
875        for (id, role) in candidates {
876            // The stop rule is about the roles the **capture dispatch** drives,
877            // and only those. A `Gesture` member's recognizer is fed through the
878            // ordinary capture bubble, so evaluating it here would double-drive
879            // it, and letting an ancestor past it would break "the innermost
880            // drag owns the gesture" — it stops the walk.
881            //
882            // A `RawDrag` and a `Pan` are both decided *here* and nowhere else:
883            // a raw drag on the sequence's own travel, a pan on
884            // `pan_axis_past_slop`, whose product is a synthesised `Scroll`
885            // rather than a `GestureEvent` fed to an arena. Breaking at either
886            // would mean a member that also owns the press arena could never
887            // win — and for a pan claimant that is every editing surface, which
888            // takes the press for its caret (so the implicit arena capture makes
889            // it the `pressed_owner`) and scrolls itself under a finger.
890            if Some(id) == owner && matches!(role, MemberRole::Gesture | MemberRole::RawPreview) {
891                break;
892            }
893            let won = match role {
894                MemberRole::Gesture => self.feed_member_arena(id, move_event, &mut *ops),
895                MemberRole::RawDrag => self
896                    .current_sequence()
897                    .is_some_and(|s| s.travel() >= s.latch_slop(&profile)),
898                // A **dual-role** member is evaluated here only as the pan it is
899                // enrolled as; its own recognizers ride the ordinary move
900                // bubble, which reaches them either way. See this function's
901                // doc comment for why that asymmetry is the bubble's.
902                MemberRole::Pan(claim) => self
903                    .current_sequence()
904                    .and_then(|s| s.pan_axis_past_slop(&claim, &profile))
905                    .is_some(),
906                MemberRole::RawPreview => false,
907            };
908            // The `active_drag` half is a **guard**, not a second way in. The
909            // router enters this walk only while no drag is in flight (both
910            // call sites in `pointer_router.rs` test `active_drag.is_none()`),
911            // and the only thing here that can raise one is
912            // `feed_member_arena` on this very candidate — which reports it as
913            // `won` in the same breath. So the `else` winner below is not
914            // reachable on today's call paths, and the winner recorded is
915            // always the member that won. What DOES happen when a surface
916            // raises its own drag from the capture dispatch is that this walk
917            // is not entered at all, leaving the sequence undecided:
918            // `a_drag_raised_by_the_press_owner_takes_the_press_out_of_the_arbitration`
919            // (`pan_arbiter/tests.rs`) pins that.
920            if won || self.active_drag.is_some() {
921                let winner = if won { id } else { owner.unwrap_or(id) };
922                self.decide_sequence(winner);
923                // A pan claimant that won owns the rest of the press as a
924                // *scroll*: from here every sample for this contact is
925                // synthesised onto the claimant chain rather than delivered as
926                // a pointer move. Recorded only for a genuine pan win.
927                if won && matches!(role, MemberRole::Pan(_)) {
928                    let pointer = self.current_pointer_id();
929                    self.note_pan_claimed(pointer, winner);
930                    // The pan half of a dual-role member won, so its self-drag
931                    // is out for the rest of the press. Without this the
932                    // member's `Won` state would unblock its arena and a
933                    // deferral ripening mid-pan would start its drag under a
934                    // scrolling finger. Inert for a claimant that carries no
935                    // drag of its own.
936                    self.with_sequence(|_, sequence| sequence.withdraw_own_drag(winner));
937                    // …and the claimant's own **tap family** goes with it. A pan
938                    // that won IS the press; leaving the winner's
939                    // `TapRecognizer` armed fires its `on_tap` on the release as
940                    // well, and on a viewport-filling claimant the slide-off
941                    // sweep never revokes it — a coarse pointer's tap boundary
942                    // is the node's own rect, and a finger panning a full-window
943                    // surface never leaves it. Tap-family only: the same
944                    // `cancel_taps` grain WCAG 2.2 SC 2.5.2's slide-off rule
945                    // uses, so a drag the same press started is untouched and
946                    // the winner receives no `PointerCancel`.
947                    self.cancel_member_taps(winner, pointer);
948                }
949                return;
950            }
951        }
952    }
953
954    /// Declare `winner` the owner of the current sequence and cancel every
955    /// competitor it knocked out — each exactly once.
956    ///
957    /// "Exactly once" is structural rather than bookkept:
958    /// [`PointerSequence::decide`](crate::gesture::PointerSequence::decide)
959    /// reports only the members that were still live and flips them to
960    /// `Rejected` as it goes, and a decided sequence returns early above — so a
961    /// loser knocked out in an earlier sample cannot be knocked out again.
962    pub(super) fn decide_sequence(&mut self, winner: WidgetId) {
963        let mut noop = crate::window::NoopWindowOps;
964        self.decide_sequence_with_ops(winner, &mut noop);
965    }
966
967    /// [`decide_sequence`](Self::decide_sequence) with the caller's
968    /// [`WindowOps`](crate::window::WindowOps), so a loser's
969    /// `on_pointer_cancel` can reach the multi-window API like any other
970    /// handler.
971    pub(super) fn decide_sequence_with_ops(
972        &mut self,
973        winner: WidgetId,
974        ops: &mut dyn crate::window::WindowOps,
975    ) {
976        let Some(losers) = self.with_sequence(|_, sequence| {
977            if sequence.is_decided() {
978                return Vec::new();
979            }
980            crate::trace_input!(
981                Gestures,
982                "sequence for {:?} decided: {:?}",
983                sequence.pointer().id,
984                winner
985            );
986            sequence.decide(winner)
987        }) else {
988            return;
989        };
990        let pointer = self.current_pointer_id();
991        for id in losers {
992            // Member-level, not pointer-level: the pointer is very much alive
993            // and its winner is about to go on using it.
994            self.revoke_sequence_member(
995                pointer,
996                id,
997                crate::pointer::CancelReason::PeerClaimed,
998                &mut *ops,
999            );
1000        }
1001    }
1002
1003    /// Revoke only `id`'s **tap family** for `pointer` — tap, double tap,
1004    /// triple tap, long press — and leave everything else on the node running.
1005    ///
1006    /// The [`cancel_taps`](crate::gesture::GestureArenaSet::cancel_taps) grain,
1007    /// not [`cancel`](crate::gesture::GestureArenaSet::cancel): the node has not
1008    /// had an interaction taken away, so it is sent no `PointerCancel`, and a
1009    /// drag the same press is driving survives.
1010    pub(super) fn cancel_member_taps(&mut self, id: WidgetId, pointer: crate::pointer::PointerId) {
1011        if let Some(node) = self.arena.get_mut(id)
1012            && let Some(set) = node.handlers.gesture_arena.as_mut()
1013        {
1014            set.cancel_taps(pointer);
1015        }
1016    }
1017
1018    /// Take a member out of the running and revoke whatever its recognizers had
1019    /// accumulated for this contact.
1020    pub(super) fn cancel_member_arena(&mut self, id: WidgetId, pointer: crate::pointer::PointerId) {
1021        if let Some(node) = self.arena.get_mut(id)
1022            && let Some(set) = node.handlers.gesture_arena.as_mut()
1023        {
1024            set.cancel(pointer);
1025        }
1026    }
1027
1028    /// Re-check every member against the arena, once per sample.
1029    ///
1030    /// A member whose node was destroyed is cancelled **individually** and
1031    /// dropped; the sequence itself dies only when its winner or its captor
1032    /// goes away, because those are the two nodes the press actually belongs
1033    /// to.
1034    pub(super) fn revalidate_sequence(&mut self, ops: &mut dyn crate::window::WindowOps) {
1035        let pointer = self.current_pointer_id();
1036        let capture = self.current_pointer_capture();
1037        let Some((dead, lost_owner)) = self.with_sequence(|tree, sequence| {
1038            sequence.set_capture(capture);
1039            let dead = sequence.revalidate(&tree.arena);
1040            // Which owner died decides how the cancel reads: a winner that went
1041            // away had won the press outright, a captor that went away leaves
1042            // the capture with nobody holding it.
1043            let lost_owner = sequence
1044                .lost_owner(&tree.arena)
1045                .then(|| match sequence.winner() {
1046                    Some(winner) if !tree.arena.is_active(winner) => {
1047                        crate::pointer::CancelReason::WidgetDestroyed
1048                    }
1049                    _ => crate::pointer::CancelReason::CaptureOrphaned,
1050                });
1051            if lost_owner.is_some() {
1052                // The cancel that finishes the teardown is queued behind this
1053                // sample, so the sequence outlives this line by one dispatch.
1054                // Nothing may win a press whose owner is already gone in the
1055                // meantime — withdraw every competitor now, which also silences
1056                // their recognizers for the rest of the sample
1057                // (`sequence_blocks_arena`).
1058                let live: Vec<WidgetId> = sequence.members().iter().map(|m| m.id).collect();
1059                for id in live {
1060                    sequence.reject(id);
1061                }
1062            }
1063            (dead, lost_owner)
1064        }) else {
1065            return;
1066        };
1067        for id in dead {
1068            self.revoke_sequence_member(
1069                pointer,
1070                id,
1071                crate::pointer::CancelReason::WidgetDestroyed,
1072                &mut *ops,
1073            );
1074        }
1075        if let Some(reason) = lost_owner {
1076            crate::trace_input!(
1077                Gestures,
1078                "sequence for {pointer:?} cancelled: its owner is gone ({reason:?})"
1079            );
1080            self.cancel_pointer(pointer, reason, &mut *ops);
1081        }
1082    }
1083
1084    /// The release sweep. The pointer sequence ended without a positional
1085    /// competitor latching, so feed the terminating `Up` to every member that
1086    /// is still following the press.
1087    ///
1088    /// This is what stops an ancestor `DragRecognizer` — armed on the press
1089    /// while an interactive descendant held the capture — from staying armed
1090    /// indefinitely and starting a phantom drag on the next *hover* move.
1091    pub(super) fn end_sequence(
1092        &mut self,
1093        up_event: &WidgetEvent,
1094        ops: &mut dyn crate::window::WindowOps,
1095    ) {
1096        use crate::gesture::MemberRole;
1097
1098        let pointer = self.current_pointer_id();
1099        let capture = self.current_pointer_capture();
1100        let Some(members) = self.with_sequence(|_, sequence| {
1101            sequence.set_terminating(true);
1102            sequence.live_ids_with(|role| matches!(role, MemberRole::Gesture))
1103        }) else {
1104            return;
1105        };
1106        for id in members {
1107            if Some(id) == capture {
1108                // Its own arena is about to see this `Up` through the capture
1109                // dispatch; feeding it twice would count the release twice.
1110                continue;
1111            }
1112            // The press belongs to the captor, so a member's tap family is
1113            // revoked first: its TapRecognizer saw the whole press, and fed the
1114            // `Up` it would fire the member's tap beside the captor's (a button
1115            // in a dock collapsing the accordion whose header drags the dock).
1116            // What is left, an `Up` while the recognizer is not mid-drag,
1117            // resolves it to `Failed` and clears `down_position`, so this only
1118            // tidies recognizer state.
1119            self.cancel_member_taps(id, pointer);
1120            self.feed_member_arena(id, up_event, &mut *ops);
1121        }
1122        if let Some(entry) = self.pointers.get_mut(pointer) {
1123            entry.sequence = None;
1124        }
1125    }
1126
1127    /// Stop every gesture arena that is still following `pointer`.
1128    ///
1129    /// A press ends at exactly one node — the captor — and the release is
1130    /// delivered only there, so any *other* arena that saw the `Down` is left
1131    /// following a contact that no longer exists. It happens on the ordinary
1132    /// path: an ancestor drag that wins a press a tapping descendant was
1133    /// holding takes the capture with it, and the descendant's arena never
1134    /// sees the `Up`.
1135    ///
1136    /// A stale entry is not inert. The next press reuses it instead of
1137    /// instantiating fresh recognizers, so a contact starts mid-gesture on
1138    /// state left over from the previous one. Ended rather than cancelled: the
1139    /// press was completed, not revoked, and the node's
1140    /// [`TapStreak`](crate::gesture::TapStreak) — which lives outside the live
1141    /// set precisely so it can outlive a contact — must survive, or touch
1142    /// double-tap would be impossible.
1143    pub(super) fn release_arenas_following(&mut self, pointer: crate::pointer::PointerId) {
1144        let owners: Vec<WidgetId> = self.gesture_owners.iter().copied().collect();
1145        for id in owners {
1146            if let Some(node) = self.arena.get_mut(id)
1147                && let Some(set) = node.handlers.gesture_arena.as_mut()
1148            {
1149                set.end(pointer);
1150            }
1151        }
1152    }
1153
1154    /// Apply the arbitration acts a handler queued on its context.
1155    pub(super) fn apply_gesture_acts(
1156        &mut self,
1157        acts: &[crate::widget::GestureAct],
1158        source: WidgetId,
1159    ) {
1160        use crate::widget::GestureAct;
1161
1162        let now = self.sequence_now();
1163        let mut claim = false;
1164        self.with_sequence(|_, sequence| {
1165            for act in acts {
1166                match act {
1167                    GestureAct::Claim => claim = true,
1168                    GestureAct::Reject => {
1169                        claim = false;
1170                        sequence.reject(source);
1171                    }
1172                    GestureAct::Hold => {
1173                        claim = false;
1174                        if !sequence.has_member(source) {
1175                            sequence.enrol(source, crate::gesture::MemberRole::Gesture);
1176                        }
1177                        sequence.hold(source, now);
1178                    }
1179                    GestureAct::Release => {
1180                        sequence.release_hold(source);
1181                    }
1182                }
1183            }
1184        });
1185        if claim {
1186            self.with_sequence(|_, sequence| {
1187                if !sequence.has_member(source) {
1188                    sequence.enrol(source, crate::gesture::MemberRole::Gesture);
1189                }
1190            });
1191            self.decide_sequence(source);
1192        }
1193    }
1194
1195    /// A handler took the pointer with an explicit
1196    /// [`capture_pointer`](crate::widget::EventContext::capture_pointer).
1197    ///
1198    /// That is an arbitration act, not plumbing: the caller is enrolled as a
1199    /// [`MemberRole::RawDrag`](crate::gesture::MemberRole::RawDrag) competitor,
1200    /// and for a precise pointer with no eligible pan competitor the sequence
1201    /// is decided there and then — which is what makes the splitter handle, the
1202    /// dock resize handle and the table column grip win their own presses
1203    /// instead of losing them to an ancestor that happens to carry `on_drag`.
1204    pub(super) fn note_explicit_capture(&mut self, source: WidgetId) {
1205        use crate::gesture::MemberRole;
1206
1207        let Some(decide) = self.with_sequence(|_, sequence| {
1208            if sequence.is_decided() {
1209                return false;
1210            }
1211            if !sequence.enrol(source, MemberRole::RawDrag) && !sequence.has_member(source) {
1212                return false;
1213            }
1214            // A precise pointer has no pan competitor by construction
1215            // (`GestureProfile::pan_slop` is `None` for a mouse), so this is a
1216            // decision at press. A contact defers to `drag_slop` instead, which
1217            // is what lets a scroller still beat it at `pan_slop`.
1218            !sequence.pointer().kind.is_direct() && !sequence.has_eligible_pan()
1219        }) else {
1220            return;
1221        };
1222        if decide {
1223            self.decide_sequence(source);
1224        }
1225    }
1226
1227    /// A recognizer on `source` produced a gesture that owns the rest of the
1228    /// press (a drag or a swipe). That is the observable act of winning, so it
1229    /// decides the sequence — whether the recognizer was reached through the
1230    /// ordinary capture bubble or fed by the arbitration itself.
1231    pub(super) fn note_gesture_recognized(&mut self, source: WidgetId) {
1232        use crate::gesture::MemberRole;
1233
1234        let now = self.sequence_now();
1235        let claimed = self
1236            .with_sequence(|_, sequence| {
1237                if sequence.is_decided() {
1238                    return false;
1239                }
1240                // A self-drag still inside its deferral cannot claim. The arena
1241                // gate normally makes this unreachable — a blocked recognizer
1242                // produces nothing to report — but the guard is what makes "the
1243                // deferral binds every route" true by *reading* it rather than
1244                // by enumerating the routes.
1245                if sequence.own_drag_blocked(source, now) {
1246                    return false;
1247                }
1248                if !sequence.has_member(source) {
1249                    sequence.enrol(source, MemberRole::Gesture);
1250                }
1251                // The self-drag half of a dual-role member won: say so, so
1252                // `sequence_members` names the half that took the press rather
1253                // than the pan claim the node was also holding.
1254                sequence.promote_own_drag(source);
1255                sequence.has_member(source)
1256            })
1257            .unwrap_or(false);
1258        if claimed {
1259            self.decide_sequence(source);
1260        }
1261    }
1262
1263    /// Whether `id`'s gesture recognizers must be kept out of this pointer
1264    /// event.
1265    ///
1266    /// A member that lost — because a peer won, because it withdrew, or because
1267    /// another member is holding — must not go on recognizing through the
1268    /// ordinary bubble. Only its *recognizers* are silenced: its
1269    /// `on_pointer_event`, `on_hover` and everything else still run, because
1270    /// losing an arbitration is not the same as being removed from the tree.
1271    ///
1272    /// Inert for every sequence nothing has decided, held or rejected — which
1273    /// is every plain mouse tap.
1274    ///
1275    /// Asked of the sequence belonging to the pointer being dispatched. The
1276    /// timer-driven path has no pointer being dispatched and asks
1277    /// [`sequence_blocks_arena_for`](Self::sequence_blocks_arena_for) instead,
1278    /// naming the contact whose gesture is in hand.
1279    pub(super) fn sequence_blocks_arena(&self, id: WidgetId) -> bool {
1280        match self.current_sequence() {
1281            Some(sequence) => Self::sequence_blocks_member(sequence, id, self.sequence_now()),
1282            None => false,
1283        }
1284    }
1285
1286    /// [`sequence_blocks_arena`](Self::sequence_blocks_arena) for a named
1287    /// contact and a named instant, rather than for whatever sample is being
1288    /// dispatched.
1289    ///
1290    /// The timer path needs both: nothing is being dispatched during a tick, so
1291    /// `current_sequence` would answer about the wrong contact (or about none),
1292    /// and `sequence_now` would answer with the timestamp of the last sample
1293    /// dispatched — which for a contact resting on a control is its own press.
1294    pub(super) fn sequence_blocks_arena_for(
1295        &self,
1296        pointer: crate::pointer::PointerId,
1297        id: WidgetId,
1298        now: crate::pointer::EventTime,
1299    ) -> bool {
1300        match self.pointers.get(pointer).and_then(|e| e.sequence.as_ref()) {
1301            Some(sequence) => Self::sequence_blocks_member(sequence, id, now),
1302            None => false,
1303        }
1304    }
1305
1306    /// The rule itself, shared by both doors above.
1307    fn sequence_blocks_member(
1308        sequence: &crate::gesture::PointerSequence,
1309        id: WidgetId,
1310        now: crate::pointer::EventTime,
1311    ) -> bool {
1312        use crate::gesture::MemberState;
1313
1314        let Some(member) = sequence.members().iter().find(|m| m.id == id) else {
1315            return false;
1316        };
1317        // A node that also claims a pan has its *own* drag recognizers gated
1318        // separately from its membership: the member goes on competing as a pan
1319        // while its self-drag waits out the deferral its `DragActivation` asked
1320        // for, and stays silenced for good once that self-drag is withdrawn —
1321        // which is why the clause is read in the `Won` arm too. A withdrawal
1322        // means the pan took the press, and a pan that owns the press owns the
1323        // node's recognizers with it. Inert for every member carrying no
1324        // self-drag, which is every member a mouse ever enrols.
1325        let own_drag_armed = member.own_drag_armed_at(now);
1326        match member.state {
1327            MemberState::Rejected => true,
1328            MemberState::Won => !own_drag_armed,
1329            _ => {
1330                if !own_drag_armed {
1331                    return true;
1332                }
1333                if let Some(winner) = sequence.winner() {
1334                    return winner != id;
1335                }
1336                // A hold freezes every peer: no one may win while a member is
1337                // still deciding.
1338                if sequence.is_held() && member.state != MemberState::Held {
1339                    return true;
1340                }
1341                // A member deferred by `DragActivation::AfterLongPress` cannot
1342                // win before its timer, and that has to hold on the ordinary
1343                // bubble too — otherwise the deferral would only bind the
1344                // arbitration's own walk.
1345                !member.is_eligible_at(now)
1346            }
1347        }
1348    }
1349
1350    /// A preview handler answered `Handled` on a press. The **root-first**
1351    /// preview pass is the first step of the decision procedure, so this claims
1352    /// the sequence outright.
1353    pub(super) fn note_preview_claim(&mut self, source: WidgetId) {
1354        use crate::gesture::MemberRole;
1355
1356        let claimed = self
1357            .with_sequence(|_, sequence| {
1358                if sequence.is_decided() {
1359                    return false;
1360                }
1361                sequence.enrol(source, MemberRole::RawPreview)
1362            })
1363            .unwrap_or(false);
1364        if claimed {
1365            self.decide_sequence(source);
1366        }
1367    }
1368
1369    /// The winner of `pointer`'s sequence, if one has been decided.
1370    pub fn sequence_winner(&self, pointer: crate::pointer::PointerId) -> Option<WidgetId> {
1371        self.pointers
1372            .get(pointer)
1373            .and_then(|e| e.sequence.as_ref())
1374            .and_then(|s| s.winner())
1375    }
1376
1377    /// Every competitor for `pointer`'s press, innermost first.
1378    ///
1379    /// The observable form of the cross-widget arbitration, and the successor
1380    /// to the old `armed_drag_observers()`: an app can assert that a press on a
1381    /// control inside a draggable container enrols no ancestor at all.
1382    pub fn sequence_members(
1383        &self,
1384        pointer: crate::pointer::PointerId,
1385    ) -> Vec<(
1386        WidgetId,
1387        crate::gesture::MemberRole,
1388        crate::gesture::MemberState,
1389    )> {
1390        self.pointers
1391            .get(pointer)
1392            .and_then(|e| e.sequence.as_ref())
1393            .map(|s| s.member_report())
1394            .unwrap_or_default()
1395    }
1396
1397    /// The [`TouchAction`] frozen for the pointer this dispatch is serving —
1398    /// what [`EventContext::touch_action`](crate::widget::EventContext::touch_action)
1399    /// reports.
1400    pub(crate) fn current_frozen_touch_action(&self) -> TouchAction {
1401        self.current_sequence()
1402            .map(|s| s.touch_action())
1403            .unwrap_or(TouchAction::AUTO)
1404    }
1405
1406    /// The [`TouchAction`] frozen for `pointer`'s press.
1407    pub fn sequence_touch_action(&self, pointer: crate::pointer::PointerId) -> TouchAction {
1408        self.pointers
1409            .get(pointer)
1410            .and_then(|e| e.sequence.as_ref())
1411            .map(|s| s.touch_action())
1412            .unwrap_or(TouchAction::AUTO)
1413    }
1414
1415    /// Feed one raw pointer event to `id`'s gesture arena set WITHOUT firing
1416    /// its `on_pointer_event` or taking the implicit capture (another node
1417    /// already holds it). Returns `true` if a gesture was recognized, in which
1418    /// case it is dispatched so the `on_drag` handler's `start_drag` runs and
1419    /// `active_drag` takes over.
1420    fn feed_member_arena(
1421        &mut self,
1422        id: WidgetId,
1423        event: &WidgetEvent,
1424        ops: &mut dyn crate::window::WindowOps,
1425    ) -> bool {
1426        let localized = Self::localize_event(&self.arena.local_frame(id), event);
1427        let event = localized.as_ref().unwrap_or(event);
1428        let cx = self.recognizer_context(id);
1429        let raw = match event {
1430            WidgetEvent::PointerDown {
1431                position,
1432                button,
1433                modifiers,
1434                ..
1435            } => crate::gesture::RawPointerEvent::Down {
1436                position: *position,
1437                button: *button,
1438                modifiers: *modifiers,
1439                pointer: cx.pointer,
1440                time: cx.now,
1441            },
1442            WidgetEvent::PointerMove { position, .. } => crate::gesture::RawPointerEvent::Move {
1443                position: *position,
1444                pointer: cx.pointer,
1445                time: cx.now,
1446            },
1447            WidgetEvent::PointerUp {
1448                position,
1449                button,
1450                modifiers,
1451                ..
1452            } => crate::gesture::RawPointerEvent::Up {
1453                position: *position,
1454                button: *button,
1455                modifiers: *modifiers,
1456                pointer: cx.pointer,
1457                time: cx.now,
1458            },
1459            _ => return false,
1460        };
1461        let mut ctx = self.make_event_context(&mut *ops);
1462        let WidgetTree { arena, .. } = self;
1463        let recognized = if let Some(node) = arena.get_mut(id) {
1464            if let Some(arena_ref) = node.handlers.gesture_arena.as_mut() {
1465                if let Some(gesture) = arena_ref.process(&raw, &cx) {
1466                    Self::dispatch_recognized_gesture(node, gesture, &mut ctx);
1467                    true
1468                } else {
1469                    false
1470                }
1471            } else {
1472                false
1473            }
1474        } else {
1475            false
1476        };
1477        self.collect_from_ctx(ctx, id);
1478        recognized
1479    }
1480
1481    /// The clock a newly promoted animation must be stamped with: the same one
1482    /// the scheduler will later be ticked against.
1483    ///
1484    /// The wall clock, unless [`advance_time`](Self::advance_time) has taken
1485    /// this tree onto its simulated one and not yet handed it back — see
1486    /// [`resume_real_time`](Self::resume_real_time), which is what returns the
1487    /// answer to the wall clock, rebasing the scheduler as it goes.
1488    ///
1489    /// Everything that reads a time on the animation axis has to read it here,
1490    /// promotion and tick alike, or the two drift apart and the drift *is* the
1491    /// elapsed time the animation is measured by. Promoting at
1492    /// `Instant::now()` while ticking at [`Self::sim_clock`] gave every
1493    /// animation a start in the scheduler's future and froze its progress
1494    /// completely, with no number of further ticks recovering it — a failure
1495    /// that reproduced as a function of machine load rather than of behaviour,
1496    /// green when the suite ran alone and red once the runner filled the cores
1497    /// and each test's wall-clock time stretched past the simulated time it was
1498    /// asking for. The overlay manager keeps its real and simulated timestamps
1499    /// apart for the same reason.
1500    pub(super) fn animation_clock(&self) -> std::time::Instant {
1501        if self.sim_time_frozen {
1502            self.sim_clock
1503        } else {
1504            std::time::Instant::now()
1505        }
1506    }
1507
1508    /// Advance time-driven gesture recognizers (currently only
1509    /// [`crate::gesture::LongPressRecognizer`]) across every widget that
1510    /// has a gesture arena. Must be called by the event loop on each
1511    /// wake-up; otherwise long-press will never fire during an idle hold.
1512    ///
1513    /// When a recognizer transitions to `Recognized`, the corresponding
1514    /// handler on the owning widget is invoked with a fresh
1515    /// [`EventContext`], and any commands / overlay requests it emits are
1516    /// collected through the normal post-event path.
1517    /// Release every hold that has stood for `profile.max_hold`, across every
1518    /// contact — the time-driven half of
1519    /// [`tick_sequence_timers`](Self::tick_sequence_timers), lifted out so the
1520    /// gesture tick can run it too.
1521    ///
1522    /// Two things separate it from its move-driven sibling and are why it is a
1523    /// distinct function rather than a call to that one.
1524    ///
1525    /// * **It reads the caller's `now`, not the sample's.** `sequence_now`
1526    ///   answers with the timestamp of the last event *dispatched*, which
1527    ///   during a tick is the press — so calling `tick_sequence_timers` from
1528    ///   here would expire holds against the instant they were taken and never
1529    ///   expire anything at all.
1530    /// * **It is not scoped to the current pointer.** The rest of the sequence
1531    ///   machinery serves the contact being dispatched; a tick serves the whole
1532    ///   tree, and two fingers each holding on their own node must both be
1533    ///   released.
1534    ///
1535    /// The other two rules in `tick_sequence_timers` — the deferred member's
1536    /// withdrawal and the tap family's revocation — stay behind, because both
1537    /// are decided by the [`TapBoundary`](crate::gesture::TapBoundary)
1538    /// against where the pointer now is. A contact that has not moved cannot
1539    /// have left the boundary, so running them here could only ever repeat the
1540    /// answer the last move already gave.
1541    pub(super) fn expire_sequence_holds(&mut self, now: crate::pointer::EventTime) {
1542        let Self {
1543            pointers,
1544            effective_theme,
1545            ..
1546        } = self;
1547        for entry in pointers.iter_mut() {
1548            let Some(sequence) = entry.sequence.as_mut() else {
1549                continue;
1550            };
1551            let profile = effective_theme.input.profile(sequence.pointer().kind);
1552            sequence.expire_holds(now, profile);
1553        }
1554    }
1555
1556    pub fn tick_gestures(&mut self, now: std::time::Instant) {
1557        let mut noop = crate::window::NoopWindowOps;
1558        self.tick_gestures_with_ops(now, &mut noop);
1559    }
1560
1561    /// App-facing variant of [`tick_gestures`](Self::tick_gestures)
1562    /// that accepts a real [`WindowOps`](crate::window::WindowOps)
1563    /// sink so gesture-recognized handlers can call the multi-window
1564    /// API synchronously.
1565    pub fn tick_gestures_with_ops(
1566        &mut self,
1567        now: std::time::Instant,
1568        ops: &mut dyn crate::window::WindowOps,
1569    ) {
1570        // Snapshot the gesture-owners set into the reusable scratch.
1571        // Previously this iterated every active widget; in practice
1572        // only a tiny fraction carry a gesture arena, so visiting the
1573        // rest was pure overhead.
1574        // `mem::take` lets the loop borrow `&mut self` for
1575        // `make_event_context` etc. without conflicting with the
1576        // scratch buffer; we put the storage back at the end.
1577        // The fling pump rides the same pass. It is an input deadline like a
1578        // long press, it is folded into the same `WaitUntil`
1579        // (`next_input_deadline`), and giving it its own call site would mean
1580        // every host had to learn a second one.
1581        self.tick_flings_with_ops(now, &mut *ops);
1582        // …and so does the press-feedback delay, for the same reason: it is an
1583        // input deadline folded into the same `WaitUntil`, and a finger resting
1584        // on a control produces no further samples to resolve it from.
1585        self.resolve_press_delays(self.event_time_for(now));
1586        // …and so does a standing hold's expiry, for the third time for the same
1587        // reason: a contact resting on a control produces no further samples,
1588        // and the framework's promise is that it stops trusting a holder after
1589        // `max_hold` — not that it stops trusting one after `max_hold` *and* a
1590        // move. See `expire_sequence_holds`.
1591        self.expire_sequence_holds(self.event_time_for(now));
1592        // …and so does the tree-owned long press, for the fourth time for the
1593        // same reason. It is not a recognizer: the affordances it reaches (a
1594        // context-menu factory the router walks up to, a tooltip on a node that
1595        // may be **disabled** and so has no arena at all) are the tree's, not a
1596        // widget's. See `super::touch_route`.
1597        self.resolve_touch_routes(self.event_time_for(now), &mut *ops);
1598
1599        let mut ids = std::mem::take(&mut self.active_ids_scratch);
1600        ids.clear();
1601        ids.extend(
1602            self.gesture_owners
1603                .iter()
1604                .copied()
1605                .filter(|id| self.arena.is_active(*id)),
1606        );
1607        let now = self.event_time_for(now);
1608        for &id in &ids {
1609            let cx = self.recognizer_context(id);
1610            let cx = crate::gesture::RecognizerContext { now, ..cx };
1611            let gestures = match self.arena.get_mut(id) {
1612                Some(node) => node
1613                    .handlers
1614                    .gesture_arena
1615                    .as_mut()
1616                    .map(|arena| arena.tick(&cx))
1617                    .unwrap_or_default(),
1618                None => Vec::new(),
1619            };
1620            if gestures.is_empty() {
1621                continue;
1622            }
1623
1624            // One entry per contact: two fingers holding on the same node both
1625            // long-press, and neither may be dropped.
1626            for (pointer, gesture) in gestures {
1627                // The arbitration binds the timer path exactly as it binds the
1628                // sample path: a member that has been rejected, or that a peer's
1629                // hold has frozen, does not get to deliver a gesture just
1630                // because its own timer came due. Asked per contact, since two
1631                // fingers on one node are two independent sequences.
1632                //
1633                // It comes out one step later than on the sample path, which
1634                // withholds the *feed* — a tick is not addressed to a member,
1635                // so the whole node's recognizers advance and the gesture is
1636                // then dropped rather than deferred. That is what `Rejected`
1637                // wants anyway; for the transient `Held` case it means a peer
1638                // silenced at the instant its timer ripened loses that gesture
1639                // rather than firing it late, and the hold that silenced it is
1640                // released in this same pass (`expire_sequence_holds`, above)
1641                // once it reaches `max_hold`.
1642                if self.sequence_blocks_arena_for(pointer, id, now) {
1643                    continue;
1644                }
1645                // One hold cannot mean two things. Where the hold is what arms
1646                // a grab — a reorderable row under a finger, whose drag member
1647                // was deferred to this very deadline, or one inside a node that
1648                // declared `LongPressRole::DragHandle` — the row's own long
1649                // press does not also fire.
1650                //
1651                // A mouse keeps its hold wherever the claim was *inferred*: it
1652                // enrols no pan competitor, so `DragActivation::Auto` is never
1653                // resolved to `AfterLongPress` on its sequence and nothing is
1654                // deferred by that route, and the `DragHandle` walk is gated on
1655                // a direct pointer because a mouse spends no hold arming a drag
1656                // it never asked for. A node that *declares*
1657                // `DragActivation::AfterLongPress` has asked: the declaration
1658                // passes through `resolve_activation` untouched, so the grab is
1659                // deferred to the hold for every pointer kind and that node's
1660                // own long press is spent under a mouse too. See
1661                // `long_press_is_a_grab` for the three doors.
1662                if matches!(gesture, crate::gesture::GestureEvent::LongPress(_))
1663                    && self.long_press_is_a_grab(pointer, id)
1664                {
1665                    continue;
1666                }
1667                // Install the contact this gesture belongs to for the length
1668                // of the dispatch. A hold is recognised here, by a deadline,
1669                // not by a sample — and `current_input` is saved-and-restored
1670                // around every dispatch (`run_one_dispatch`), so without this
1671                // it holds `InputSnapshot::default()` and every handler reached
1672                // from a hold is told it is serving **the mouse**, whatever the
1673                // device was.
1674                //
1675                // The snapshot is more than the device: it is the key
1676                // `make_event_context` builds the rest of the context from, so
1677                // every answer looked up by `current_pointer_id()` comes out
1678                // for the wrong pointer without it. What the context carries
1679                // in, and all of it: the device and the id the snapshot holds
1680                // outright (`EventContext::pointer_kind`, `pointer`), the
1681                // captor (`current_pointer_capture`), the frozen `TouchAction`
1682                // (`current_frozen_touch_action`, keyed through the contact's
1683                // sequence) and the press snapshot (`current_press_snapshot`).
1684                // What the context carries back out is a separate list, below.
1685                // The fling pump resolves its pointer from the table the same
1686                // way (`dispatch_chained_scroll`).
1687                let installed = self
1688                    .pointers
1689                    .get(pointer)
1690                    .map(|entry| entry.info)
1691                    .unwrap_or(self.current_input.pointer);
1692                let previous_input = std::mem::replace(
1693                    &mut self.current_input,
1694                    crate::pointer::InputSnapshot::for_recognized_gesture(installed),
1695                );
1696                let mut ctx = self.make_event_context(&mut *ops);
1697                if let Some(node) = self.arena.get_mut(id) {
1698                    Self::dispatch_recognized_gesture(node, gesture, &mut ctx);
1699                }
1700                // `collect_from_ctx` **after** the restore would be wrong:
1701                // two of the requests a handler can queue name no pointer and
1702                // are applied to whichever one `current_pointer_id()` answers
1703                // with at collection time — an unnamed
1704                // `capture_pointer()`/`release_pointer()`, and
1705                // `cancel_pointer_sequence()`. Collected after the restore,
1706                // a hold that captured would have captured the mouse and a
1707                // hold that cancelled would have cancelled it. The sample path
1708                // has the same order — `run_one_dispatch` restores only once
1709                // `dispatch_event_impl`, collection included, has returned.
1710                self.collect_from_ctx(ctx, id);
1711                self.current_input = previous_input;
1712            }
1713            self.arena.mark_needs_paint(id);
1714        }
1715        self.active_ids_scratch = ids;
1716    }
1717
1718    /// Read an `Instant` handed in by the event loop on this tree's input
1719    /// timeline.
1720    ///
1721    /// Three answers, and not one of them is unconditionally the plain
1722    /// subtraction from the shared epoch (see
1723    /// [`input_clock`](Self::input_clock)) that a single axis would suggest:
1724    /// two ignore the caller's instant outright, and the third subtracts and
1725    /// then adds back whatever was advanced before the axis was handed back.
1726    ///
1727    /// * **Time is simulated.** The argument is discarded: the tree has one
1728    ///   now, and it is not the caller's — an `Instant` handed in by a real
1729    ///   event loop is on an axis this tree has stopped following.
1730    /// * **Time is real, under an anchored clock.** The caller's instant is
1731    ///   honoured, as the distance from the shared epoch, *plus* whatever was
1732    ///   advanced before the axis was handed back — a subtraction and then an
1733    ///   addition, because the axis runs `sim_input_offset` ahead of the clock
1734    ///   the caller read.
1735    /// * **A clock with no wall-clock anchor** — a
1736    ///   [`ManualClock`](crate::pointer::clock::ManualClock) in a test — also
1737    ///   ignores the argument and answers with its own reading, which is the
1738    ///   whole point of installing one.
1739    pub(super) fn event_time_for(&self, now: std::time::Instant) -> crate::pointer::EventTime {
1740        match self.input_clock.epoch() {
1741            // While the axis is frozen the tree has one now, and it is not the
1742            // caller's: an `Instant` handed in by a real event loop is on an
1743            // axis this tree has stopped following for the length of the
1744            // advance.
1745            Some(_) if self.sim_input_origin.is_some() => self.input_now(),
1746            // Otherwise the caller's instant is honoured — two real events
1747            // milliseconds apart must not be stamped the same moment — shifted
1748            // by whatever was advanced before the axis was handed back.
1749            Some(epoch) => {
1750                crate::pointer::EventTime::from_duration(now.saturating_duration_since(epoch))
1751                    + self.sim_input_offset
1752            }
1753            None => self.input_now(),
1754        }
1755    }
1756
1757    /// Turn an input-timeline deadline back into an `Instant` for the event
1758    /// loop, which schedules in wall-clock terms.
1759    pub(super) fn instant_for(&self, time: crate::pointer::EventTime) -> std::time::Instant {
1760        match self.input_clock.epoch() {
1761            // Inverse of the frozen branch of `event_time_for`: a deadline on
1762            // the virtual axis is reported against the virtual clock, so what
1763            // comes back is comparable with `simulated_now()` and not with a
1764            // wall clock this tree is not following for the length of the
1765            // advance.
1766            Some(_) if self.sim_input_origin.is_some() => {
1767                self.sim_clock + time.saturating_since(self.input_now())
1768            }
1769            // Inverse of the offset branch. Subtracting what was advanced is
1770            // what keeps this in the future: the deadline was stamped on an
1771            // axis running `sim_input_offset` ahead of the clock the event loop
1772            // schedules against, and reporting it unshifted would hand back an
1773            // instant already past — a `WaitUntil` that can never ripen and a
1774            // loop that spins on it.
1775            Some(epoch) => epoch + time.as_duration().saturating_sub(self.sim_input_offset),
1776            // An unanchored clock has no wall-clock answer; the best available
1777            // one is "as far from now as it is from the clock's reading".
1778            None => std::time::Instant::now() + time.saturating_since(self.input_now()),
1779        }
1780    }
1781
1782    /// Earliest wall-clock deadline at which any active gesture arena
1783    /// needs [`WidgetTree::tick_gestures`] called — typically a pending
1784    /// long-press timeout. Returns `None` when no recognizer is waiting.
1785    pub fn next_gesture_deadline(&self) -> Option<std::time::Instant> {
1786        // Iterate just the widgets that actually carry a gesture arena.
1787        // `filter` for `is_active` skips dormant entries that may still
1788        // be in the set after a hide-without-detach.
1789        self.gesture_owners
1790            .iter()
1791            .copied()
1792            .filter(|id| self.arena.is_active(*id))
1793            .filter_map(|id| self.arena.get(id))
1794            .filter_map(|node| node.handlers.gesture_arena.as_ref())
1795            .filter_map(|arena| arena.next_deadline())
1796            .min()
1797            .map(|deadline| self.instant_for(deadline))
1798    }
1799
1800    /// The earliest instant at which a standing hold reaches its
1801    /// `max_hold` and [`expire_sequence_holds`](Self::expire_sequence_holds)
1802    /// has work.
1803    ///
1804    /// Folded into [`next_input_deadline`](Self::next_input_deadline) beside
1805    /// the gesture, fling and press-feedback terms. A deadline the tick can
1806    /// serve but nothing reports is a wake the event loop never takes, which
1807    /// leaves the hold standing exactly as long as it did before the tick knew
1808    /// how to release it.
1809    pub(super) fn next_sequence_hold_deadline(&self) -> Option<crate::pointer::EventTime> {
1810        self.pointers
1811            .iter()
1812            .filter_map(|entry| entry.sequence.as_ref())
1813            .filter_map(|sequence| {
1814                let profile = self.effective_theme.input.profile(sequence.pointer().kind);
1815                sequence.next_hold_deadline(profile)
1816            })
1817            .min()
1818    }
1819
1820    /// The [`TouchAction`] permitted for `target`: every node's own
1821    /// declaration from the root down to `target` (inclusive), intersected.
1822    /// An ancestor's `NONE` wins no matter what a descendant declares —
1823    /// intersection is absorbing at `NONE` (see
1824    /// `crate::pointer::touch_action`), so this needs no early exit to get
1825    /// that right; it just folds the whole chain.
1826    ///
1827    /// One of the two path folds the arbitration reads: `begin_sequence` asks
1828    /// it what a press may do, and `feed_pinch` asks it whether a subtree
1829    /// admits a two-contact pinch.
1830    pub(crate) fn effective_touch_action(&self, target: WidgetId) -> TouchAction {
1831        let mut chain = Vec::new();
1832        let mut current = Some(target);
1833        while let Some(id) = current {
1834            chain.push(id);
1835            current = self.arena.parent(id);
1836        }
1837        // `chain` is target..=root (innermost first); fold root-to-target so
1838        // the read matches the CSS `touch-action` model this mirrors — an
1839        // ancestor's declaration is applied before a descendant's narrows it
1840        // further. `intersect` is commutative and associative, so the fold
1841        // order can never change the *answer*, only which step "loses" reads
1842        // as the natural one.
1843        chain
1844            .iter()
1845            .rev()
1846            .map(|&id| {
1847                self.arena
1848                    .get(id)
1849                    .map(|n| n.touch_action)
1850                    .unwrap_or(TouchAction::AUTO)
1851            })
1852            .fold(TouchAction::AUTO, TouchAction::intersect)
1853    }
1854
1855    /// Every [`PanClaim`] from `target` up to the root, **innermost first**
1856    /// — the order a boundary pan chains along (a nested scrollable hits its
1857    /// edge and hands off to its container), so it is normative. A claimant
1858    /// is excluded entirely — never narrowed — when `allowed` forbids any
1859    /// axis it declares.
1860    ///
1861    /// The second of the two path folds the arbitration reads, and the chain
1862    /// a synthesised pan is delivered along — see `widget_tree::pan_arbiter`.
1863    pub(crate) fn pan_candidates(
1864        &self,
1865        target: WidgetId,
1866        allowed: TouchAction,
1867    ) -> Vec<(WidgetId, PanClaim)> {
1868        let mut result = Vec::new();
1869        let mut current = Some(target);
1870        while let Some(id) = current {
1871            if let Some(claim) = self.arena.get(id).and_then(|n| n.pan_claim) {
1872                let x_ok = !claim.axes.contains(Axis::X) || allowed.allows_pan_x();
1873                let y_ok = !claim.axes.contains(Axis::Y) || allowed.allows_pan_y();
1874                if x_ok && y_ok {
1875                    result.push((id, claim));
1876                }
1877            }
1878            current = self.arena.parent(id);
1879        }
1880        result
1881    }
1882}
1883
1884#[cfg(test)]
1885mod tests {
1886    use super::*;
1887    use crate::test_widgets::FillWidget;
1888    use crate::widget_builder::WidgetBuilder;
1889
1890    #[test]
1891    fn destroy_subtree_clears_dangling_pointer_capture() {
1892        use crate::event::{EventResponse, Modifiers, PointerButton};
1893        use crate::test_widgets::StackWidget;
1894
1895        let mut tree = WidgetTree::new();
1896        let child = tree.add(FillWidget::new().on_pointer_event(|event, ctx| {
1897            if matches!(event, WidgetEvent::PointerDown { .. }) {
1898                ctx.capture_pointer();
1899            }
1900            EventResponse::Ignored
1901        }));
1902        let parent = tree.add(StackWidget::new().child(child));
1903        tree.layout(SizeProposal::exact(100.0, 50.0));
1904
1905        // A press inside the child captures the pointer to it.
1906        tree.dispatch_event(WidgetEvent::pointer_down(
1907            Point::new(50.0, 25.0),
1908            PointerButton::Primary,
1909            Modifiers::NONE,
1910        ));
1911        assert_eq!(
1912            tree.pointer_captured_by(),
1913            Some(child),
1914            "PointerDown handler should have captured the pointer"
1915        );
1916
1917        // Tearing down the capturing subtree (e.g. mid-gesture rebuild) must
1918        // release the capture eagerly rather than leaving a dangling id that
1919        // swallows every later Move/Up until the next layout pass heals it.
1920        tree.destroy_subtree(parent);
1921        assert_eq!(
1922            tree.pointer_captured_by(),
1923            None,
1924            "destroy_subtree must clear a capture anchored at a destroyed widget"
1925        );
1926    }
1927}
1928
1929/// The two path folds `effective_touch_action` / `pan_candidates` declare
1930/// for the arbitration package (P08) — exercised here in isolation, apart
1931/// from the dispatch paths that now read them (`begin_sequence`,
1932/// `feed_pinch`, and the pan arbiter).
1933#[cfg(test)]
1934mod touch_action_tests {
1935    use super::*;
1936    use crate::pointer::touch_action::PanAxes;
1937    use crate::test_widgets::FillWidget;
1938    use crate::widget_builder::WidgetBuilder;
1939
1940    /// A 20-deep chain, root to target, where one mid-level node declares
1941    /// `PAN_Y` and another declares `PINCH_ZOOM`. Neither permission is
1942    /// shared by the other, so the intersection collapses to `NONE` — the
1943    /// clearest possible demonstration that the fold really intersects the
1944    /// *whole* chain rather than reading only the nearest declaration.
1945    #[test]
1946    fn effective_touch_action_intersects_the_whole_root_to_target_chain() {
1947        let mut tree = WidgetTree::new();
1948        let mut chain = vec![tree.add(FillWidget::new())]; // depth 0: the root
1949        for depth in 1..20usize {
1950            let parent = *chain.last().expect("root was pushed");
1951            let id = if depth == 5 {
1952                tree.add_child(parent, FillWidget::new().touch_action(TouchAction::PAN_Y))
1953            } else if depth == 12 {
1954                tree.add_child(
1955                    parent,
1956                    FillWidget::new().touch_action(TouchAction::PINCH_ZOOM),
1957                )
1958            } else {
1959                tree.add_child(parent, FillWidget::new())
1960            };
1961            chain.push(id);
1962        }
1963        assert_eq!(chain.len(), 20, "the path must be 20 nodes deep");
1964        let target = *chain.last().expect("chain is non-empty");
1965        tree.layout(SizeProposal::exact(50.0, 50.0));
1966
1967        assert_eq!(
1968            tree.effective_touch_action(target),
1969            TouchAction::NONE,
1970            "PAN_Y at depth 5 and PINCH_ZOOM at depth 12 share no permission"
1971        );
1972
1973        // Every node above depth 5 (inclusive) is untouched: the plain
1974        // `AUTO` prefix intersects down to exactly `PAN_Y`.
1975        assert_eq!(tree.effective_touch_action(chain[5]), TouchAction::PAN_Y);
1976    }
1977
1978    /// `pan_candidates` walks target-to-root (innermost first) and drops a
1979    /// claimant whose declared axes the allowed action forbids, rather than
1980    /// narrowing it.
1981    #[test]
1982    fn pan_candidates_orders_innermost_first_and_filters_by_allowed_axes() {
1983        let mut tree = WidgetTree::new();
1984        // root claims X; an unclaimed node in between; target (innermost)
1985        // claims Y.
1986        let root = tree.add(FillWidget::new().scroll_container(PanAxes::X));
1987        let mid = tree.add_child(root, FillWidget::new());
1988        let target = tree.add_child(mid, FillWidget::new().scroll_container(PanAxes::Y));
1989        tree.layout(SizeProposal::exact(50.0, 50.0));
1990
1991        let x_claim = PanClaim {
1992            axes: PanAxes::X,
1993            devices: teksilo_tokens::PointerKindMask::DIRECT,
1994            kinetic: true,
1995        };
1996        let y_claim = PanClaim {
1997            axes: PanAxes::Y,
1998            devices: teksilo_tokens::PointerKindMask::DIRECT,
1999            kinetic: true,
2000        };
2001
2002        // Both axes allowed: both claims survive, innermost (target) first.
2003        assert_eq!(
2004            tree.pan_candidates(target, TouchAction::PAN),
2005            vec![(target, y_claim), (root, x_claim)]
2006        );
2007
2008        // Only PAN_X allowed: target's Y-axis claim is forbidden and
2009        // excluded outright; root's X-axis claim still survives.
2010        assert_eq!(
2011            tree.pan_candidates(target, TouchAction::PAN_X),
2012            vec![(root, x_claim)]
2013        );
2014
2015        // Only PAN_Y allowed: the reverse — root's claim is excluded,
2016        // target's survives.
2017        assert_eq!(
2018            tree.pan_candidates(target, TouchAction::PAN_Y),
2019            vec![(target, y_claim)]
2020        );
2021    }
2022}
2023
2024/// The one-clock rule: the input timeline and the tree's simulated clock are
2025/// one axis, seeded from one epoch.
2026#[cfg(test)]
2027mod clock_tests {
2028    use super::*;
2029    use crate::pointer::EventTime;
2030    use crate::pointer::clock::ManualClock;
2031
2032    /// The whole point of taking the epoch as a parameter rather than
2033    /// capturing it: `EventTime::ZERO` and the tree's simulated clock name the
2034    /// *same* instant, so one `advance_time` moves gesture deadlines and
2035    /// animations against the same origin.
2036    ///
2037    /// If this ever fails, the two timelines have drifted apart and a test that
2038    /// advances one has silently stopped advancing the other.
2039    #[test]
2040    fn the_input_clock_shares_the_trees_epoch() {
2041        let tree = WidgetTree::new();
2042        assert_eq!(
2043            tree.input_clock().epoch(),
2044            Some(tree.simulated_now()),
2045            "the input clock must be anchored at the instant sim_clock starts from"
2046        );
2047    }
2048
2049    /// …and stays one axis as simulated time moves: the offset between the
2050    /// simulated clock and the epoch is exactly what was advanced.
2051    #[test]
2052    fn advancing_simulated_time_moves_along_the_input_axis() {
2053        let mut tree = WidgetTree::new();
2054        let epoch = tree
2055            .input_clock()
2056            .epoch()
2057            .expect("the default input clock is monotonic");
2058
2059        tree.advance_time(std::time::Duration::from_millis(400));
2060        assert_eq!(
2061            tree.simulated_now().duration_since(epoch),
2062            std::time::Duration::from_millis(400)
2063        );
2064
2065        tree.advance_time(std::time::Duration::from_millis(100));
2066        assert_eq!(
2067            tree.simulated_now().duration_since(epoch),
2068            std::time::Duration::from_millis(500)
2069        );
2070    }
2071
2072    /// The simulated input axis continues from where the wall clock left it,
2073    /// rather than restarting at the simulated clock's own offset.
2074    ///
2075    /// A stamp taken before the switch would otherwise land in the virtual
2076    /// *future*: `sim_clock` only moves when it is advanced, so on a tree that
2077    /// has been alive for 50 ms it still reads the epoch while every sample
2078    /// dispatched so far is stamped 50 ms. Every interval measured from one of
2079    /// those is then clamped to zero — a hold that can never elapse, a coast
2080    /// that never starts.
2081    #[test]
2082    fn the_simulated_input_axis_continues_from_the_wall_clock() {
2083        let mut tree = WidgetTree::new();
2084        // Time the tree spent on the wall clock before anything simulated it —
2085        // in a real suite this is however long the test took to get here.
2086        std::thread::sleep(std::time::Duration::from_millis(50));
2087        let stamped_before_the_switch = tree.input_now();
2088        assert!(
2089            stamped_before_the_switch >= EventTime::from_millis(50),
2090            "the default clock is the wall clock until told otherwise: {stamped_before_the_switch:?}"
2091        );
2092
2093        tree.advance_time(std::time::Duration::from_millis(20));
2094        let after_one = tree.input_now();
2095        assert!(
2096            after_one >= stamped_before_the_switch + std::time::Duration::from_millis(20),
2097            "the axis carries on from the reading it had, not from the epoch: \
2098             {stamped_before_the_switch:?} -> {after_one:?}"
2099        );
2100
2101        // …and from then on it moves by exactly what is advanced, and by
2102        // nothing else — however long this test itself takes.
2103        std::thread::sleep(std::time::Duration::from_millis(20));
2104        tree.advance_time(std::time::Duration::from_millis(30));
2105        assert_eq!(
2106            tree.input_now(),
2107            after_one + std::time::Duration::from_millis(30),
2108            "a simulated tree's input timeline answers to the clock alone"
2109        );
2110    }
2111
2112    /// A test can take the input timeline over entirely.
2113    #[test]
2114    fn a_manual_clock_replaces_the_default() {
2115        let mut tree = WidgetTree::new();
2116        let manual = std::rc::Rc::new(ManualClock::new(EventTime::from_millis(30)));
2117        tree.set_input_clock(manual.clone());
2118
2119        assert_eq!(tree.input_now(), EventTime::from_millis(30));
2120        manual.advance(std::time::Duration::from_millis(70));
2121        assert_eq!(tree.input_now(), EventTime::from_millis(100));
2122        // Reading does not move it — a manual clock is only moved by its owner.
2123        assert_eq!(tree.input_now(), EventTime::from_millis(100));
2124    }
2125
2126    /// The default clock actually reads the wall clock, so a real window's
2127    /// gestures advance without anyone ticking anything.
2128    #[test]
2129    fn the_default_clock_is_monotonic() {
2130        let tree = WidgetTree::new();
2131        let a = tree.input_now();
2132        let b = tree.input_now();
2133        assert!(b >= a);
2134    }
2135
2136    /// Handing the axis back never steps it backwards.
2137    ///
2138    /// The whole reason the hand-back is not just "drop the origin": a frozen
2139    /// axis that has been advanced reads *ahead* of the raw clock, and every
2140    /// stamp already issued sits at that reading. Going back to the raw clock
2141    /// would re-issue times that have already been handed out — a velocity
2142    /// tracker fitting a negative interval, a tap streak whose second tap is
2143    /// older than its first, a hold that un-elapses.
2144    #[test]
2145    fn handing_the_axis_back_never_steps_it_backwards() {
2146        let mut tree = WidgetTree::new();
2147        tree.advance_time(std::time::Duration::from_millis(500));
2148        let frozen = tree.input_now();
2149
2150        tree.resume_real_time();
2151        let resumed = tree.input_now();
2152
2153        assert!(
2154            resumed >= frozen,
2155            "the axis must carry the advance forward, not discard it: \
2156             {frozen:?} -> {resumed:?}"
2157        );
2158        // …and it keeps every bit of what was advanced, rather than trading it
2159        // for however little wall time the test itself took.
2160        assert!(
2161            resumed >= EventTime::from_millis(500),
2162            "500 ms was advanced and must still be on the axis: {resumed:?}"
2163        );
2164    }
2165
2166    /// After the hand-back, real events are stamped from the real clock again:
2167    /// two of them separated by real time are two distinct moments.
2168    ///
2169    /// This is the whole point of the hand-back. While the axis is frozen every
2170    /// dispatch reads one instant, which is exactly right for a test driving
2171    /// the clock itself and exactly wrong for a live window: a bridge that
2172    /// advanced the clock once would leave every subsequent human keystroke,
2173    /// tap and drag stamped the same moment, and no gesture decided by time
2174    /// could ever be recognized again.
2175    #[test]
2176    fn after_the_hand_back_real_events_get_distinct_and_later_times() {
2177        use crate::test_widgets::FillWidget;
2178        use crate::widget_builder::WidgetBuilder;
2179        use std::cell::RefCell;
2180        use std::rc::Rc;
2181
2182        let seen: Rc<RefCell<Vec<EventTime>>> = Rc::new(RefCell::new(Vec::new()));
2183        let log = seen.clone();
2184        let mut tree = WidgetTree::new();
2185        tree.add(FillWidget::new().on_pointer_event(move |_event, ctx| {
2186            log.borrow_mut().push(ctx.pointer().time);
2187            crate::event::EventResponse::Ignored
2188        }));
2189        tree.layout(SizeProposal::exact(100.0, 100.0));
2190
2191        tree.pointer_move(Point::new(10.0, 10.0));
2192        tree.advance_time(std::time::Duration::from_millis(200));
2193        tree.resume_real_time();
2194
2195        tree.pointer_move(Point::new(20.0, 20.0));
2196        std::thread::sleep(std::time::Duration::from_millis(5));
2197        tree.pointer_move(Point::new(30.0, 30.0));
2198
2199        let seen = seen.borrow();
2200        assert_eq!(seen.len(), 3, "three moves reached the widget: {seen:?}");
2201        assert!(
2202            seen[1] >= seen[0] + std::time::Duration::from_millis(200),
2203            "an event after the advance is at least the advance later: {seen:?}"
2204        );
2205        assert!(
2206            seen[2] > seen[1],
2207            "two real events 5 ms apart are two moments, not one: {seen:?}"
2208        );
2209    }
2210
2211    /// …and the next advance freezes it again.
2212    ///
2213    /// The hand-back is not a latch in the other direction: a bridge running a
2214    /// second operation must get the same determinism the first one did, and a
2215    /// test's helpers (which enter simulated mode on every sample) must keep
2216    /// stamping two un-advanced samples the same instant.
2217    #[test]
2218    fn the_freeze_comes_back_after_a_hand_back() {
2219        let mut tree = WidgetTree::new();
2220        tree.advance_time(std::time::Duration::from_millis(100));
2221        tree.resume_real_time();
2222
2223        tree.advance_time(std::time::Duration::from_millis(100));
2224        let a = tree.input_now();
2225        std::thread::sleep(std::time::Duration::from_millis(5));
2226        let b = tree.input_now();
2227        assert_eq!(a, b, "re-frozen: the wall clock stopped moving the axis");
2228    }
2229
2230    /// Every hand-back re-measures the offset against its own readings; none
2231    /// of them adds to what the last one measured.
2232    ///
2233    /// The live bridge hands the axis back after **every** operation, so this
2234    /// is its ordinary path rather than an edge case — and accumulating
2235    /// instead of assigning compounds: each cycle would carry the previous
2236    /// offset into the frozen reading *and* add the previous offset again, so
2237    /// the axis would run `2ⁿ − 1` advances ahead after `n` of them. Four
2238    /// 50 ms cycles is 200 ms of advance and would be reported as 750 ms, and
2239    /// every duration measured from a stamp taken before the run — a hold, a
2240    /// tap streak, a fling's velocity window — would be wrong by the
2241    /// difference.
2242    ///
2243    /// **This is the only guard on the offset's magnitude.** Its companion
2244    /// `a_deadline_armed_after_the_hand_back_is_in_the_future` is insensitive
2245    /// to it by construction — the offset cancels between `event_time_for` and
2246    /// `instant_for`, so that test holds for a wrong offset as readily as for a
2247    /// right one — and nothing else asserts a number. So the
2248    /// ceiling below is expressed **per cycle**: the allowance scales with the
2249    /// work done, so the error it admits per hand-back stays at
2250    /// `SLACK_PER_CYCLE` whatever `CYCLES` is. A single absolute ceiling
2251    /// instead divides by the cycle count — slack at a handful of cycles, and
2252    /// firing on the loop's own wall-clock noise at a hundred.
2253    #[test]
2254    fn repeated_hand_backs_re_measure_the_offset_rather_than_accumulating_it() {
2255        const CYCLES: u32 = 4;
2256        const PER_CYCLE: std::time::Duration = std::time::Duration::from_millis(50);
2257        // What one hand-back may cost beyond what it advanced: the wall clock
2258        // moves while the loop runs, and the loop's own work is not free.
2259        const SLACK_PER_CYCLE: std::time::Duration = std::time::Duration::from_millis(10);
2260        let advanced = PER_CYCLE * CYCLES;
2261
2262        let mut tree = WidgetTree::new();
2263        let before = tree.input_now();
2264        for _ in 0..CYCLES {
2265            tree.advance_time(PER_CYCLE);
2266            tree.resume_real_time();
2267        }
2268        let after = tree.input_now();
2269        let gained = after.saturating_since(before);
2270
2271        assert!(
2272            gained >= advanced,
2273            "every advance must still be on the axis: {gained:?} < {advanced:?}"
2274        );
2275        // The wall clock also moved while the loop ran, so the axis is allowed
2276        // to have gained a little more than was advanced — but only a little,
2277        // and the allowance is per hand-back rather than for the run.
2278        // Accumulating would put it at 750 ms, three and a half times over.
2279        assert!(
2280            gained <= advanced + SLACK_PER_CYCLE * CYCLES,
2281            "the axis gained {gained:?} for {advanced:?} of advancing over \
2282             {CYCLES} hand-backs — the offset is compounding across them"
2283        );
2284    }
2285
2286    /// An animation in flight when the tree is put on the simulated clock
2287    /// keeps the phase it had, and goes on progressing.
2288    ///
2289    /// The scheduler stores absolute instants, and the simulated clock reads
2290    /// the tree's epoch plus whatever has been advanced — on a live window,
2291    /// far behind the wall clock the animation was stamped against. Measuring
2292    /// it there without rebasing clamps its elapsed time to roughly zero and
2293    /// it never moves again, however many frames the operation advances.
2294    #[test]
2295    fn an_animation_in_flight_keeps_its_phase_when_time_is_taken_over() {
2296        use crate::signal::Signal;
2297        use crate::test_widgets::FillWidget;
2298        use std::time::Duration;
2299
2300        let mut tree = WidgetTree::new();
2301        let owner = tree.add(FillWidget::new());
2302        tree.layout(SizeProposal::exact(100.0, 100.0));
2303
2304        // Long enough that a loaded CI runner overshooting the sleep below by
2305        // a few hundred milliseconds — a macOS runner has been seen to take
2306        // 250 ms over a 100 ms sleep — cannot run it to completion before the
2307        // take-over, which would leave nothing for the advance to move.
2308        const DURATION: Duration = Duration::from_millis(4000);
2309
2310        let value = Signal::<f32>::new_animated(0.0);
2311        tree.register_animated_signal(&value, owner);
2312        value.animate_to(1.0, DURATION, teksilo_tokens::Easing::Linear);
2313
2314        // Promote and age it on the wall clock, exactly as a live window does.
2315        tree.layout(SizeProposal::exact(100.0, 100.0));
2316        std::thread::sleep(Duration::from_millis(100));
2317        tree.layout(SizeProposal::exact(100.0, 100.0));
2318        let on_the_wall_clock = value.get();
2319        // 100 ms of 4000 is 0.025; the sleep never undershoots, and the bound
2320        // above allows the runner nearly two seconds of overshoot.
2321        assert!(
2322            (0.02..0.5).contains(&on_the_wall_clock),
2323            "in flight after 100 ms of {DURATION:?}: {on_the_wall_clock}"
2324        );
2325
2326        // Now an automation operation takes time over and advances a further
2327        // 1000 ms. The animation must have moved on by exactly that share of
2328        // its duration — not stuck where the wall clock left it, and not
2329        // restarted from the simulated clock's own reading.
2330        tree.advance_time(Duration::from_millis(1000));
2331        let simulated = value.get();
2332        let moved = simulated - on_the_wall_clock;
2333        // 1000 ms of 4000 is 0.25. Whatever wall-clock time passed between the
2334        // reading above and the take-over is in there too, so the bound is
2335        // loose above — by 400 ms — and tight below.
2336        assert!(
2337            (0.245..0.35).contains(&moved),
2338            "the advance keeps the phase and adds its own 1000 ms of \
2339             {DURATION:?}: {on_the_wall_clock} -> {simulated}"
2340        );
2341    }
2342
2343    /// …and once time is handed back, the wall clock goes on driving it from
2344    /// where the advance left it — neither frozen nor snapped to its end.
2345    ///
2346    /// The two failures this rules out are the two halves of getting the axis
2347    /// wrong on a *live* attached window. Ticking at the simulated clock a
2348    /// live tree no longer advances freezes every animation outright. Ticking
2349    /// at the wall clock against a start stamped on the simulated one hands
2350    /// the animation an elapsed time of the tree's whole age and completes it
2351    /// on the first real frame.
2352    #[test]
2353    fn an_animation_goes_on_progressing_after_the_hand_back() {
2354        use crate::signal::Signal;
2355        use crate::test_widgets::FillWidget;
2356        use std::time::Duration;
2357
2358        let mut tree = WidgetTree::new();
2359        let owner = tree.add(FillWidget::new());
2360        tree.layout(SizeProposal::exact(100.0, 100.0));
2361
2362        // The tree spends real time alive before anything simulates it, and
2363        // more of it than will be advanced. That is the live condition, and it
2364        // is what makes the *second* assertion below discriminating: with the
2365        // wall clock ahead of the simulated one, an un-rebased hand-back hands
2366        // the animation an elapsed time longer than its whole duration.
2367        std::thread::sleep(Duration::from_millis(300));
2368
2369        let value = Signal::<f32>::new_animated(0.0);
2370        tree.register_animated_signal(&value, owner);
2371        // 2 s rather than something short: the 100 ms slept after the
2372        // hand-back is a floor, not a figure, and a loaded runner has
2373        // overshot it by 150 ms — the "not snapped to the end" bound below
2374        // must hold through that.
2375        value.animate_to(
2376            1.0,
2377            Duration::from_millis(2000),
2378            teksilo_tokens::Easing::Linear,
2379        );
2380
2381        // The operation promotes it and advances it a twentieth of the way.
2382        tree.advance_time(Duration::from_millis(100));
2383        let at_hand_back = value.get();
2384        assert!(
2385            (0.04..0.06).contains(&at_hand_back),
2386            "a twentieth through after 100 ms of 2000: {at_hand_back}"
2387        );
2388
2389        // The operation ends and the window goes back to painting frames.
2390        tree.resume_real_time();
2391        std::thread::sleep(Duration::from_millis(100));
2392        tree.layout(SizeProposal::exact(100.0, 100.0));
2393
2394        let after = value.get();
2395        // At least 100 ms of 2000 (0.05) moved it; the sleep only overshoots.
2396        assert!(
2397            after > at_hand_back + 0.045,
2398            "frozen: 100 ms of real time moved it from {at_hand_back} to {after}"
2399        );
2400        assert!(
2401            after < 0.95,
2402            "snapped to the end: 100 ms of 2000 took it from {at_hand_back} to {after}"
2403        );
2404    }
2405
2406    /// A `max_duration` cap measures the animation's own age across the
2407    /// hand-back, not the tree's.
2408    ///
2409    /// `started_at` is the only stored instant the cap reads, and until this
2410    /// test nothing asserted that the rebase shifts it: no production caller
2411    /// sets `max_duration` at all, so the branch is entered only from the
2412    /// public
2413    /// [`Signal::try_animate_with_options`](crate::signal::Signal::try_animate_with_options)
2414    /// and from the scheduler's own unit tests, which never change axis.
2415    /// Left behind on the abandoned axis, `started_at` makes the cap measure
2416    /// the tree's whole wall-clock age instead of the animation's own elapsed
2417    /// time, and the first real frame after the hand-back retires the
2418    /// animation outright — the same snap the rebase exists to prevent,
2419    /// arriving through a different door.
2420    ///
2421    /// The cap is deliberately smaller than the tree's age at the final layout
2422    /// and larger than the animation's own elapsed time there, so the two ways
2423    /// of measuring it disagree about whether it has been reached.
2424    #[test]
2425    fn a_capped_animation_survives_the_hand_back() {
2426        use crate::animation::AnimationRequest;
2427        use crate::signal::Signal;
2428        use crate::test_widgets::FillWidget;
2429        use std::time::Duration;
2430
2431        // The animation lives 200 ms before the final reading — 100 simulated,
2432        // 100 real — and the tree at least 600 ms. The cap sits between the
2433        // two with room on both sides: a loaded runner overshoots a sleep by
2434        // 100 ms or more, and every overshoot ages the tree further but the
2435        // animation only through the real half.
2436        const CAP: Duration = Duration::from_millis(400);
2437        const DURATION: Duration = Duration::from_millis(2000);
2438
2439        let mut tree = WidgetTree::new();
2440        let owner = tree.add(FillWidget::new());
2441        tree.layout(SizeProposal::exact(100.0, 100.0));
2442
2443        // Age the tree past the cap before the animation is armed at all.
2444        std::thread::sleep(Duration::from_millis(500));
2445
2446        let value = Signal::<f32>::new_animated(0.0);
2447        tree.register_animated_signal(&value, owner);
2448        value
2449            .try_animate_with_options(AnimationRequest {
2450                target: 1.0,
2451                duration: DURATION,
2452                easing: teksilo_tokens::Easing::Linear,
2453                max_duration: Some(CAP),
2454                ..AnimationRequest::default()
2455            })
2456            .expect("an animated signal accepts a request");
2457
2458        // 100 ms simulated, then 100 ms real: 200 ms of the animation's own
2459        // life, against a tree already older than the cap.
2460        tree.advance_time(Duration::from_millis(100));
2461        let at_hand_back = value.get();
2462        tree.resume_real_time();
2463        std::thread::sleep(Duration::from_millis(100));
2464        tree.layout(SizeProposal::exact(100.0, 100.0));
2465
2466        let after = value.get();
2467        assert!(
2468            tree.has_active_animations(),
2469            "a {CAP:?} cap retired a {DURATION:?} animation 200 ms in: \
2470             {at_hand_back} -> {after}"
2471        );
2472        assert!(
2473            after > at_hand_back,
2474            "the animation must go on progressing: {at_hand_back} -> {after}"
2475        );
2476    }
2477
2478    /// An animation paused across a hand-back resumes from where it was
2479    /// paused, rather than being driven backwards by the gap between the axes.
2480    ///
2481    /// A loop, because only loops are paused with their window: a one-shot
2482    /// tween runs on without focus and never reads the pause mark.
2483    ///
2484    /// The pause mark is the scheduler's one instant that is not per-animation,
2485    /// and until this test nothing asserted that the rebase shifts it: on
2486    /// reactivate the scheduler moves each `start_time` forward by
2487    /// `now - paused_at`, so a mark left behind on the abandoned axis measures
2488    /// the whole gap between the axes and puts the start ahead of the reading
2489    /// that follows it — the animation's elapsed time collapses, and it
2490    /// replays from near zero once the wall clock reaches the new start.
2491    ///
2492    /// The wall clock is deliberately left further ahead of the simulated one
2493    /// than the real time that elapses after the hand-back, which is exactly
2494    /// the condition under which the collapse leaves the animation *behind*
2495    /// where it was paused rather than merely slowed.
2496    #[test]
2497    fn an_animation_paused_across_the_hand_back_resumes_forwards() {
2498        use crate::signal::Signal;
2499        use crate::test_widgets::FillWidget;
2500        use std::time::Duration;
2501
2502        let mut tree = WidgetTree::new();
2503        let owner = tree.add(FillWidget::new());
2504        tree.layout(SizeProposal::exact(100.0, 100.0));
2505        std::thread::sleep(Duration::from_millis(300));
2506
2507        let value = Signal::<f32>::new_animated(0.0);
2508        tree.register_animated_signal(&value, owner);
2509        value.animate_looping(
2510            1.0,
2511            Duration::from_millis(1000),
2512            teksilo_tokens::Easing::Linear,
2513            None,
2514        );
2515
2516        tree.advance_time(Duration::from_millis(100));
2517        let at_pause = value.get();
2518        assert!(
2519            (0.05..0.2).contains(&at_pause),
2520            "a tenth through after 100 ms of 1000: {at_pause}"
2521        );
2522
2523        // The window loses focus while the operation still owns the clock, and
2524        // regains it after the hand-back — the ordering that makes the pause
2525        // mark and the reading it is subtracted from land on different axes.
2526        tree.set_window_active(false);
2527        tree.resume_real_time();
2528        tree.set_window_active(true);
2529
2530        std::thread::sleep(Duration::from_millis(150));
2531        tree.layout(SizeProposal::exact(100.0, 100.0));
2532
2533        let after = value.get();
2534        assert!(
2535            after > at_pause,
2536            "frozen or driven backwards across a paused hand-back: \
2537             {at_pause} -> {after}"
2538        );
2539    }
2540
2541    /// …and the same, with the gap between the two axes the other way round:
2542    /// the pause mark is stamped on the axis the scheduler is measured
2543    /// against, not on the wall clock.
2544    ///
2545    /// The twin of the test above, and it cannot be merged with it. Which of
2546    /// the two mistakes is observable depends on the *sign* of the gap at the
2547    /// moment of the pause: a mark the rebase left behind only yields a
2548    /// spurious offset while the wall clock leads, and a mark taken from the
2549    /// wall clock instead of the animation clock only survives the
2550    /// subtraction — rather than flooring at zero — while the simulated clock
2551    /// leads. Each test rules out the sign the other needs, so each covers one
2552    /// mistake.
2553    ///
2554    /// Here the simulated clock is advanced past a tree milliseconds old, so
2555    /// it leads — and a wall-clock mark, shifted by the hand-back's rebase
2556    /// like the animation-axis instant it is not, comes out a whole advance
2557    /// early and is subtracted from the reading on reactivate as if the window
2558    /// had been dark for that long.
2559    #[test]
2560    fn a_pause_mark_is_stamped_on_the_animation_axis() {
2561        use crate::signal::Signal;
2562        use crate::test_widgets::FillWidget;
2563        use std::time::Duration;
2564
2565        let mut tree = WidgetTree::new();
2566        let owner = tree.add(FillWidget::new());
2567        tree.layout(SizeProposal::exact(100.0, 100.0));
2568
2569        let value = Signal::<f32>::new_animated(0.0);
2570        tree.register_animated_signal(&value, owner);
2571        value.animate_looping(
2572            1.0,
2573            Duration::from_millis(5000),
2574            teksilo_tokens::Easing::Linear,
2575            None,
2576        );
2577
2578        // A second of simulated time on a tree milliseconds old: the advance,
2579        // not a sleep, is what separates the axes, and it separates them the
2580        // other way.
2581        tree.advance_time(Duration::from_millis(1000));
2582        let at_pause = value.get();
2583        assert!(
2584            (0.15..0.25).contains(&at_pause),
2585            "a fifth through after 1000 ms of 5000: {at_pause}"
2586        );
2587
2588        tree.set_window_active(false);
2589        tree.resume_real_time();
2590        tree.set_window_active(true);
2591
2592        std::thread::sleep(Duration::from_millis(150));
2593        tree.layout(SizeProposal::exact(100.0, 100.0));
2594
2595        let after = value.get();
2596        assert!(
2597            after > at_pause,
2598            "the deactivation cost the animation its phase: {at_pause} -> {after}"
2599        );
2600    }
2601
2602    /// A gesture the **timer** recognised is dispatched under its own contact.
2603    ///
2604    /// The whole class of defect this pins: a hold is not a sample, so nothing
2605    /// on the sample path installs a snapshot for it, and `current_input` is
2606    /// saved-and-restored around every dispatch — so a handler reached from a
2607    /// hold used to be told, unconditionally, that it was serving the mouse.
2608    /// It was measured that way (a probe on a real `long_press_at(Touch, ..)`
2609    /// printed `Mouse`), and it cost the first host of the touch-text contract
2610    /// a duplicate guard: `TouchSelection::on_long_press` refused every finger.
2611    ///
2612    /// This asserts the whole of what the context carries **in**: the device,
2613    /// the id, the captor, the frozen `TouchAction` and the press snapshot.
2614    /// What a handler asks the tree *for* from inside a hold — a capture, a
2615    /// cancel — travels the other way and is asserted by
2616    /// `a_hold_captures_and_cancels_the_contact_that_held`.
2617    ///
2618    /// The mouse half is not decoration: it is what proves the fix installs the
2619    /// *holding contact* rather than hard-coding a finger.
2620    #[test]
2621    fn a_hold_is_dispatched_under_the_contact_that_held() {
2622        use crate::TouchAction;
2623        use crate::test_widgets::FillWidget;
2624        use crate::widget_builder::WidgetBuilder;
2625
2626        /// Every answer the context is built from the installed snapshot.
2627        #[derive(Debug, Clone, Copy)]
2628        struct Answers {
2629            kind: teksilo_tokens::PointerKind,
2630            id: crate::pointer::PointerId,
2631            captor: Option<WidgetId>,
2632            touch_action: TouchAction,
2633            press_inside: bool,
2634        }
2635
2636        for kind in [
2637            teksilo_tokens::PointerKind::Touch,
2638            teksilo_tokens::PointerKind::Mouse,
2639        ] {
2640            let seen: std::rc::Rc<std::cell::Cell<Option<Answers>>> = Default::default();
2641            let mut tree = WidgetTree::new();
2642            let held_by = {
2643                let seen = seen.clone();
2644                tree.add(
2645                    // A declared, non-`AUTO` action so the frozen value is
2646                    // distinguishable from the neutral one a pointer with no
2647                    // sequence answers with.
2648                    FillWidget::new()
2649                        .touch_action(TouchAction::PAN_Y)
2650                        .on_long_press(move |_e, ctx| {
2651                            seen.set(Some(Answers {
2652                                kind: ctx.pointer_kind(),
2653                                id: ctx.pointer().id,
2654                                // Read off the field rather than through
2655                                // `owns_pointer()`, which also needs a
2656                                // `dispatch_node` — and a timer dispatch,
2657                                // addressed to a node rather than walking to
2658                                // one, sets none.
2659                                captor: ctx.pointer_captor,
2660                                touch_action: ctx.touch_action(),
2661                                press_inside: ctx.press_is_inside(),
2662                            }));
2663                        }),
2664                )
2665            };
2666            tree.layout(SizeProposal::exact(100.0, 100.0));
2667
2668            let held = tree.long_press_at(kind, Point::new(50.0, 50.0));
2669            let seen = seen.get().expect("the hold was dispatched");
2670
2671            assert_eq!(
2672                seen.kind, kind,
2673                "a {kind:?} hold was dispatched as {:?}",
2674                seen.kind
2675            );
2676            assert_eq!(
2677                seen.id, held,
2678                "a hold must carry the identity of the contact that held"
2679            );
2680            assert_eq!(
2681                seen.captor,
2682                Some(held_by),
2683                "the captor is looked up by the dispatched pointer, and the \
2684                 holding contact's is the node whose arena took its press"
2685            );
2686            assert_eq!(
2687                seen.touch_action,
2688                TouchAction::PAN_Y,
2689                "the frozen action is read off the dispatched pointer's \
2690                 sequence, so a hold under the wrong pointer reads the \
2691                 neutral {:?} of a pointer that has none",
2692                TouchAction::AUTO
2693            );
2694            assert!(
2695                seen.press_inside,
2696                "the press snapshot is keyed by the dispatched pointer, and \
2697                 the contact that held is holding a press"
2698            );
2699        }
2700    }
2701
2702    /// What a hold handler asks the tree **for** is applied to the contact that
2703    /// held.
2704    ///
2705    /// The other half of
2706    /// `a_hold_is_dispatched_under_the_contact_that_held`: that one asserts
2707    /// what the context is built with, this one what the context is collected
2708    /// into. Two requests carry no pointer of their own
2709    /// and are resolved against `current_pointer_id()` at collection —
2710    /// `capture_pointer()` and `cancel_pointer_sequence()` — so collecting
2711    /// after `current_input` is restored, rather than before, silently
2712    /// addresses both to the mouse.
2713    ///
2714    /// The mouse is made live in both arms and is *not* the contact under test,
2715    /// so a misrouted request lands somewhere the assertions can see rather
2716    /// than on a pointer the table does not hold.
2717    #[test]
2718    fn a_hold_captures_and_cancels_the_contact_that_held() {
2719        use crate::pointer::{CancelReason, PointerId};
2720        use crate::test_widgets::FillWidget;
2721        use crate::widget_builder::WidgetBuilder;
2722
2723        // The capture the handler takes rides on the contact, and the mouse —
2724        // live, hovering, holding nothing — is left alone. The contact's own
2725        // entry is already captured by this node (the arena takes it implicitly
2726        // at the press), so the mouse half is what a misroute shows up in.
2727        {
2728            let mut tree = WidgetTree::new();
2729            let node = tree.add(FillWidget::new().on_long_press(|_e, ctx| {
2730                ctx.capture_pointer();
2731            }));
2732            tree.layout(SizeProposal::exact(100.0, 100.0));
2733            tree.pointer_move(Point::new(10.0, 10.0));
2734
2735            let contact = hold_a_finger(&mut tree, Point::new(50.0, 50.0));
2736
2737            assert_eq!(
2738                tree.captured_by(contact),
2739                Some(node),
2740                "a capture taken from a hold belongs to the contact that held"
2741            );
2742            assert_eq!(
2743                tree.captured_by(PointerId::MOUSE),
2744                None,
2745                "the mouse was not the thing holding, and must not have been \
2746                 captured on its behalf"
2747            );
2748        }
2749
2750        // The cancel the handler raises revokes the contact, and only it. The
2751        // finger's entry is gone (a contact that is taken away ceases to exist);
2752        // the mouse's press-less entry is untouched.
2753        {
2754            let mut tree = WidgetTree::new();
2755            tree.add(FillWidget::new().on_long_press(|_e, ctx| {
2756                ctx.cancel_pointer_sequence(CancelReason::WidgetDestroyed);
2757            }));
2758            tree.layout(SizeProposal::exact(100.0, 100.0));
2759            tree.pointer_move(Point::new(10.0, 10.0));
2760
2761            let contact = hold_a_finger(&mut tree, Point::new(50.0, 50.0));
2762
2763            assert!(
2764                !tree.live_pointers().any(|p| p.id == contact),
2765                "a cancel raised from a hold revokes the contact that held"
2766            );
2767            assert!(
2768                tree.live_pointers().any(|p| p.id == PointerId::MOUSE),
2769                "and revokes nothing else"
2770            );
2771        }
2772    }
2773
2774    /// Press one finger at `at` and let its hold ripen, without releasing it.
2775    ///
2776    /// [`long_press_at`](WidgetTree::long_press_at) lifts the contact, and a
2777    /// lift takes the capture back and ends the entry — so what the hold's own
2778    /// handler did to the pointer table is only observable before it.
2779    fn hold_a_finger(tree: &mut WidgetTree, at: Point) -> crate::pointer::PointerId {
2780        let hold = tree
2781            .effective_theme
2782            .input
2783            .profile(teksilo_tokens::PointerKind::Touch)
2784            .long_press;
2785        let contact = tree.new_contact();
2786        tree.touch_down(contact, at);
2787        tree.advance_input_time(hold);
2788        contact
2789    }
2790
2791    /// A deadline armed after the hand-back is reported to the event loop as a
2792    /// *future* instant.
2793    ///
2794    /// `instant_for` is what the winit loop turns into
2795    /// `ControlFlow::WaitUntil`. A deadline reported in the past is not a
2796    /// harmless rounding error: the loop wakes immediately, finds nothing
2797    /// ripe, re-derives the same past instant and spins at full CPU on a
2798    /// deadline that can never arrive.
2799    ///
2800    /// The advance is deliberately a large fraction of the hold, so that
2801    /// shifting by it once too often or once too few — the two ways
2802    /// `instant_for` can be wrong — moves the answer by far more than the
2803    /// tolerance below. Reported *early* is the spinning loop above; reported
2804    /// *late* is a long press the user waits an extra advance for.
2805    ///
2806    /// What this test does **not** cover is the offset's magnitude: it is
2807    /// subtracted here by exactly the amount `event_time_for` added, so the two
2808    /// cancel and the assertions below hold for a wrong offset as readily as
2809    /// for a right one. That number is guarded only by
2810    /// `repeated_hand_backs_re_measure_the_offset_rather_than_accumulating_it`.
2811    #[test]
2812    fn a_deadline_armed_after_the_hand_back_is_in_the_future() {
2813        use crate::test_widgets::FillWidget;
2814        use crate::widget_builder::WidgetBuilder;
2815
2816        // The tree spends real time alive before anything simulates it — which
2817        // on a live app is every second since launch, and is what makes a
2818        // deadline reported against the simulated clock land in the past.
2819        let mut tree = WidgetTree::new();
2820        tree.add(FillWidget::new().on_long_press(|_e, _c| {}));
2821        tree.layout(SizeProposal::exact(100.0, 100.0));
2822        std::thread::sleep(std::time::Duration::from_millis(60));
2823
2824        let advanced = std::time::Duration::from_millis(200);
2825        tree.advance_time(advanced);
2826        tree.resume_real_time();
2827
2828        tree.pointer_down_button(Point::new(50.0, 50.0), PointerButton::Primary);
2829        let hold = tree
2830            .theme()
2831            .input
2832            .profile(teksilo_tokens::PointerKind::Mouse)
2833            .long_press;
2834        let slack = std::time::Duration::from_millis(50);
2835        // The two assertions below only mean something while the advance
2836        // dominates the tolerance. Stated here so a later change to either
2837        // constant fails loudly instead of quietly re-opening the gap a 10 ms
2838        // advance and a 30 ms tolerance left.
2839        assert!(
2840            slack * 4 <= advanced && advanced * 4 >= hold,
2841            "the tolerance must be a fraction of the advance, and the advance a \
2842             large fraction of the hold: slack {slack:?}, advanced {advanced:?}, \
2843             hold {hold:?}"
2844        );
2845        let deadline = tree
2846            .next_timer_deadline()
2847            .expect("a held press has a long-press deadline");
2848        let wait = deadline.saturating_duration_since(std::time::Instant::now());
2849
2850        assert!(
2851            wait > std::time::Duration::ZERO,
2852            "the loop must be given something it can wait for"
2853        );
2854        // And it is the hold away — not the hold minus what was advanced
2855        // (subtracted a second time, the spinning loop), and not the hold plus
2856        // it (never subtracted at all). The tolerance is a quarter of the
2857        // advance, so neither can hide inside it.
2858        assert!(
2859            wait <= hold,
2860            "wake in ~{hold:?} after a {advanced:?} advance, got {wait:?} — reported late"
2861        );
2862        assert!(
2863            wait + slack >= hold,
2864            "wake in ~{hold:?} after a {advanced:?} advance, got {wait:?} — reported early"
2865        );
2866        tree.pointer_up_button(Point::new(50.0, 50.0), PointerButton::Primary);
2867    }
2868}
2869
2870/// The per-pointer table replacing the tree's singular pointer state: two
2871/// contacts hold two captures, hover belongs to the hover owner alone, the
2872/// contact cap is enforced at the door, and a nested dispatch waits its turn.
2873#[cfg(test)]
2874mod pointer_table_tests {
2875    use super::*;
2876    use crate::event::{EventResponse, Modifiers, PointerButton};
2877    use crate::pointer::{
2878        BackendDeviceKey, EventTime, PointerId, PointerIdAllocator, PointerInfo, PointerPhase,
2879        PointerSample,
2880    };
2881    use crate::test_widgets::FillWidget;
2882    use crate::widget_builder::WidgetBuilder;
2883    use std::cell::RefCell;
2884    use std::rc::Rc;
2885
2886    /// A fresh contact identity. Minted through the real allocator so it is
2887    /// monotonic and distinct from [`PointerId::MOUSE`].
2888    fn contact_id(raw: u64) -> PointerId {
2889        let alloc = PointerIdAllocator::global();
2890        let device = BackendDeviceKey::new(0xC0FFEE);
2891        let id = alloc.begin(device, raw);
2892        alloc.end(device, raw);
2893        id
2894    }
2895
2896    fn contact(id: PointerId, phase: PointerPhase, at: Point) -> PointerSample {
2897        PointerSample {
2898            pointer: PointerInfo::touch(id, EventTime::ZERO),
2899            phase,
2900            position: at,
2901            button: None,
2902            modifiers: Modifiers::NONE,
2903            coalesced: Vec::new(),
2904        }
2905    }
2906
2907    /// A widget that captures the pointer on press and holds it. The shape of
2908    /// every real drag handle (a slider knob, a splitter divider).
2909    fn capturing() -> impl crate::widget::Widget + 'static {
2910        FillWidget::new().on_pointer_event(|event, ctx| {
2911            if matches!(event, WidgetEvent::PointerDown { .. }) {
2912                ctx.capture_pointer();
2913            }
2914            EventResponse::Ignored
2915        })
2916    }
2917
2918    /// Two contacts on two widgets hold **independent** captures, and one
2919    /// lifting leaves the other's alone. With a single `pointer_captured_by`
2920    /// the second press overwrote the first, and the first finger's stream
2921    /// silently moved to the second widget.
2922    #[test]
2923    fn two_contacts_hold_independent_captures() {
2924        let mut tree = WidgetTree::new();
2925        let a = tree.add(capturing());
2926        let b = tree.add(capturing());
2927        let _root = tree.add(SideBySide { a, b });
2928        tree.layout(SizeProposal::exact(100.0, 100.0));
2929
2930        let first = contact_id(1);
2931        let second = contact_id(2);
2932        tree.dispatch_pointer(contact(first, PointerPhase::Down, Point::new(25.0, 50.0)));
2933        tree.dispatch_pointer(contact(second, PointerPhase::Down, Point::new(75.0, 50.0)));
2934
2935        assert_eq!(tree.captured_by(first), Some(a));
2936        assert_eq!(tree.captured_by(second), Some(b));
2937
2938        tree.dispatch_pointer(contact(first, PointerPhase::Up, Point::new(25.0, 50.0)));
2939        assert_eq!(tree.captured_by(first), None, "the lifted contact is gone");
2940        assert_eq!(
2941            tree.captured_by(second),
2942            Some(b),
2943            "one contact lifting must not release the other's capture"
2944        );
2945    }
2946
2947    /// A finger arriving while the mouse hovers must not touch hover at all —
2948    /// not the id, not the signal, not the `on_hover` handlers behind it.
2949    #[test]
2950    fn a_second_contact_never_churns_the_hover_signal() {
2951        let mut tree = WidgetTree::new();
2952        let a = tree.add(FillWidget::new());
2953        let b = tree.add(FillWidget::new());
2954        let _root = tree.add(SideBySide { a, b });
2955        tree.layout(SizeProposal::exact(100.0, 100.0));
2956
2957        tree.dispatch_event(WidgetEvent::pointer_move(Point::new(25.0, 50.0)));
2958        assert_eq!(tree.hovered(), Some(a));
2959
2960        let churn = Rc::new(std::cell::Cell::new(0usize));
2961        let observer = {
2962            let churn = churn.clone();
2963            tree.hovered_signal()
2964                .observe(move |_| churn.set(churn.get() + 1))
2965        };
2966
2967        let finger = contact_id(3);
2968        tree.dispatch_pointer(contact(finger, PointerPhase::Down, Point::new(75.0, 50.0)));
2969        tree.dispatch_pointer(contact(finger, PointerPhase::Move, Point::new(80.0, 50.0)));
2970        tree.dispatch_pointer(contact(finger, PointerPhase::Up, Point::new(80.0, 50.0)));
2971
2972        assert_eq!(
2973            tree.hovered(),
2974            Some(a),
2975            "the mouse is still hovering where it was"
2976        );
2977        assert_eq!(churn.get(), 0, "a contact must not write the hover signal");
2978        drop(observer);
2979    }
2980
2981    /// …and the contact is never the hover owner, so it has no hover of its
2982    /// own to report either.
2983    #[test]
2984    fn a_contact_is_never_the_hover_owner() {
2985        let mut tree = WidgetTree::new();
2986        let target = tree.add(FillWidget::new());
2987        tree.layout(SizeProposal::exact(100.0, 100.0));
2988
2989        let finger = contact_id(4);
2990        tree.dispatch_pointer(contact(finger, PointerPhase::Down, Point::new(50.0, 50.0)));
2991        tree.dispatch_pointer(contact(finger, PointerPhase::Move, Point::new(52.0, 50.0)));
2992
2993        assert_eq!(tree.hover_owner(), None, "a finger cannot own hover");
2994        assert_eq!(tree.hovered(), None);
2995        assert_eq!(tree.hovered_for(finger), None);
2996        assert_eq!(
2997            tree.primary_pointer().map(|p| p.id),
2998            Some(finger),
2999            "it is still the primary pointer — primary and hover owner are not the same role"
3000        );
3001        assert_eq!(
3002            tree.pointer_position(finger),
3003            Some(Point::new(52.0, 50.0)),
3004            "and its position is tracked all the same"
3005        );
3006        let _ = target;
3007    }
3008
3009    /// A synthetic pen: a hovering-capable pointer *does* take the role, and
3010    /// the mouse it displaces is told its widget is no longer hovered.
3011    #[test]
3012    fn a_pen_takes_the_hover_owner_role_from_the_mouse() {
3013        let mut tree = WidgetTree::new();
3014        let a = tree.add(FillWidget::new());
3015        let b = tree.add(FillWidget::new());
3016        let _root = tree.add(SideBySide { a, b });
3017        tree.layout(SizeProposal::exact(100.0, 100.0));
3018
3019        tree.dispatch_event(WidgetEvent::pointer_move(Point::new(25.0, 50.0)));
3020        assert_eq!(tree.hovered(), Some(a));
3021
3022        let stylus = contact_id(5);
3023        let mut pen = PointerInfo::touch(stylus, EventTime::ZERO);
3024        pen.kind = teksilo_tokens::PointerKind::Pen(teksilo_tokens::PenKind::Pen);
3025        let mut sample = contact(stylus, PointerPhase::Move, Point::new(75.0, 50.0));
3026        sample.pointer = pen;
3027        tree.dispatch_pointer(sample);
3028
3029        assert_eq!(
3030            tree.hover_owner().map(|p| p.id),
3031            Some(stylus),
3032            "the later hovering sample wins the role"
3033        );
3034        assert_eq!(tree.hovered(), Some(b));
3035        assert_eq!(
3036            tree.hovered_for(PointerId::MOUSE),
3037            None,
3038            "the displaced owner was told to let go"
3039        );
3040    }
3041
3042    /// The tenth simultaneous contact is admitted; the eleventh is refused at
3043    /// the door and produces no event at all.
3044    #[test]
3045    fn the_eleventh_contact_is_dropped_at_the_door() {
3046        use crate::pointer::table::PointerTable;
3047
3048        let presses = Rc::new(std::cell::Cell::new(0usize));
3049        let mut tree = WidgetTree::new();
3050        let counted = {
3051            let presses = presses.clone();
3052            tree.add(FillWidget::new().on_pointer_event(move |event, _ctx| {
3053                if matches!(event, WidgetEvent::PointerDown { .. }) {
3054                    presses.set(presses.get() + 1);
3055                }
3056                EventResponse::Ignored
3057            }))
3058        };
3059        tree.layout(SizeProposal::exact(100.0, 100.0));
3060
3061        let ids: Vec<_> = (0..PointerTable::DEFAULT_CAP)
3062            .map(|n| contact_id(100 + n as u64))
3063            .collect();
3064        for &id in &ids {
3065            tree.dispatch_pointer(contact(id, PointerPhase::Down, Point::new(50.0, 50.0)));
3066        }
3067        assert_eq!(presses.get(), PointerTable::DEFAULT_CAP);
3068        assert_eq!(tree.live_pointers().count(), PointerTable::DEFAULT_CAP);
3069
3070        let overflow = contact_id(200);
3071        tree.dispatch_pointer(contact(
3072            overflow,
3073            PointerPhase::Down,
3074            Point::new(50.0, 50.0),
3075        ));
3076        assert_eq!(
3077            presses.get(),
3078            PointerTable::DEFAULT_CAP,
3079            "the eleventh contact must not reach a widget"
3080        );
3081        assert_eq!(tree.captured_by(overflow), None);
3082        assert_eq!(tree.live_pointers().count(), PointerTable::DEFAULT_CAP);
3083        let _ = counted;
3084    }
3085
3086    /// A palm the digitizer flagged never reaches a widget either.
3087    #[test]
3088    fn a_palm_never_reaches_a_widget() {
3089        let presses = Rc::new(std::cell::Cell::new(0usize));
3090        let mut tree = WidgetTree::new();
3091        {
3092            let presses = presses.clone();
3093            tree.add(FillWidget::new().on_pointer_event(move |event, _ctx| {
3094                if matches!(event, WidgetEvent::PointerDown { .. }) {
3095                    presses.set(presses.get() + 1);
3096                }
3097                EventResponse::Ignored
3098            }));
3099        }
3100        tree.layout(SizeProposal::exact(100.0, 100.0));
3101
3102        let id = contact_id(300);
3103        let mut sample = contact(id, PointerPhase::Down, Point::new(50.0, 50.0));
3104        sample.pointer.palm = true;
3105        tree.dispatch_pointer(sample);
3106        assert_eq!(presses.get(), 0);
3107        assert_eq!(tree.live_pointers().count(), 0);
3108    }
3109
3110    /// A dispatch reached from inside a dispatch is queued, not run inline:
3111    /// the rest of the outer bubble runs on the state it started with, and the
3112    /// nested dispatch replays afterwards — still before the top-level call
3113    /// returns.
3114    #[test]
3115    fn a_nested_dispatch_is_queued_and_drained_after() {
3116        let log: Rc<RefCell<Vec<&'static str>>> = Rc::new(RefCell::new(Vec::new()));
3117
3118        let mut tree = WidgetTree::new();
3119        let other = {
3120            let log = log.clone();
3121            tree.add(FillWidget::new().on_tap(move |_e, _ctx| {
3122                log.borrow_mut().push("nested");
3123            }))
3124        };
3125        // Two handlers on the child, in the order the bubble runs them:
3126        // `on_pointer_event` (the pre-gesture intercept) queues the nested
3127        // dispatch, `on_tap` is the outer work still to come after it. That
3128        // pair is what makes the ordering below evidence rather than
3129        // coincidence.
3130        let child = {
3131            let log_pointer = log.clone();
3132            let log_tap = log.clone();
3133            tree.add(
3134                FillWidget::new()
3135                    .on_pointer_event(move |event, ctx| {
3136                        if matches!(event, WidgetEvent::PointerUp { .. }) {
3137                            log_pointer.borrow_mut().push("child-press");
3138                            // Re-enters the dispatch door from inside a handler.
3139                            ctx.synthetic_click(other);
3140                        }
3141                        EventResponse::Ignored
3142                    })
3143                    .on_tap(move |_e, _ctx| {
3144                        log_tap.borrow_mut().push("child-tap");
3145                    }),
3146            )
3147        };
3148        // Keep the two halves disjoint, so the synthetic click lands on
3149        // `other` and not back on `child` (which would re-enter for ever).
3150        let _root = tree.add(SideBySide { a: child, b: other });
3151        tree.layout(SizeProposal::exact(100.0, 100.0));
3152
3153        let at = tree.bounds(child).center();
3154        tree.dispatch_event(WidgetEvent::pointer_down(
3155            at,
3156            PointerButton::Primary,
3157            Modifiers::NONE,
3158        ));
3159        tree.dispatch_event(WidgetEvent::pointer_up(
3160            at,
3161            PointerButton::Primary,
3162            Modifiers::NONE,
3163        ));
3164
3165        assert_eq!(
3166            *log.borrow(),
3167            vec!["child-press", "child-tap", "nested"],
3168            "the outer dispatch must finish on the state it started with, and the \
3169             nested dispatch replay only once it has — run inline it would read \
3170             child-press, nested, child-tap"
3171        );
3172        assert!(
3173            !tree.has_pending_dispatch(),
3174            "the queue must be empty again before the top-level call returns"
3175        );
3176    }
3177
3178    /// Localisation reads the captor's **current** bounds on every event, so a
3179    /// captured control inside a container that moves keeps reporting sensible
3180    /// widget-local coordinates rather than coordinates relative to where it
3181    /// used to be.
3182    #[test]
3183    fn a_captured_widget_localises_against_its_moving_bounds() {
3184        let seen: Rc<RefCell<Vec<Point>>> = Rc::new(RefCell::new(Vec::new()));
3185        let mut tree = WidgetTree::new();
3186        let knob = {
3187            let seen = seen.clone();
3188            tree.add(FillWidget::new().on_pointer_event(move |event, ctx| {
3189                match event {
3190                    WidgetEvent::PointerDown { .. } => ctx.capture_pointer(),
3191                    WidgetEvent::PointerMove { position, .. } => seen.borrow_mut().push(*position),
3192                    _ => {}
3193                }
3194                EventResponse::Ignored
3195            }))
3196        };
3197        let offset = crate::signal::Signal::new(0.0f32);
3198        let _root = tree.add(ShiftedSlot {
3199            child: knob,
3200            offset: offset.clone(),
3201        });
3202        tree.layout(SizeProposal::exact(100.0, 100.0));
3203
3204        tree.dispatch_event(WidgetEvent::pointer_down(
3205            Point::new(30.0, 40.0),
3206            PointerButton::Primary,
3207            Modifiers::NONE,
3208        ));
3209        assert_eq!(tree.pointer_captured_by(), Some(knob));
3210        tree.dispatch_event(WidgetEvent::pointer_move(Point::new(30.0, 40.0)));
3211
3212        // The container slides its child 20 dp to the trailing side.
3213        offset.set(20.0);
3214        tree.arena.mark_all_dirty();
3215        tree.layout(SizeProposal::exact(100.0, 100.0));
3216
3217        tree.dispatch_event(WidgetEvent::pointer_move(Point::new(30.0, 40.0)));
3218
3219        assert_eq!(
3220            *seen.borrow(),
3221            vec![Point::new(30.0, 40.0), Point::new(10.0, 40.0)],
3222            "the same window position must localise against the captor's new origin"
3223        );
3224    }
3225
3226    /// The localisation contract is **deliberately asymmetric**, and this pins
3227    /// the asymmetry so nobody "fixes" it into a defect.
3228    ///
3229    /// `localize_event` rewrites `PointerDown` / `PointerUp` / `PointerMove` /
3230    /// `Gesture` into the receiver's own space, and has no arm for `Scroll` or
3231    /// `PointerCancel`. That is why those two name their position
3232    /// `window_position`: the router routes by the first (hit-testing is
3233    /// necessarily window-space), and `common/scrollable.rs` feeds it to
3234    /// `KineticScroller::pan`, whose tracker follows the *pointer* — and since
3235    /// localisation resolves against the captor's **current** bounds on every
3236    /// event (the test above), a localised value would fold the measured
3237    /// widget's own motion into the velocity.
3238    ///
3239    /// Two independent things keep it that way, and the two assertions below
3240    /// answer for one each — which is why both are here rather than one
3241    /// standing in for the other:
3242    ///
3243    /// * **`Scroll`** reaches its handler through the localising route
3244    ///   (`dispatch_to_widget` → `localize_event`), so the missing arm is the
3245    ///   whole of its protection. Adding one reads like a tidy-up; the scroll
3246    ///   assertion is what goes red.
3247    /// * **`PointerCancel`** is delivered by the cancel funnel through
3248    ///   `dispatch_to_widget_direct`, which does not localise at all, so an arm
3249    ///   added to `localize_event` would be inert on that path. What guards it
3250    ///   is that `pointer_cancel_event` records the pointer table's own
3251    ///   (window-space) position verbatim; localising it at the funnel, which
3252    ///   knows the recipient and could, is the single change the cancel
3253    ///   assertion catches.
3254    #[test]
3255    fn scroll_and_cancel_stay_in_window_space_while_a_press_is_localised() {
3256        #[derive(Default)]
3257        struct Seen {
3258            press: Vec<Point>,
3259            scroll: Vec<Option<Point>>,
3260            cancel: Vec<Option<Point>>,
3261        }
3262        let seen: Rc<RefCell<Seen>> = Rc::new(RefCell::new(Seen::default()));
3263        let mut tree = WidgetTree::new();
3264        let target = {
3265            let a = seen.clone();
3266            let b = seen.clone();
3267            tree.add(
3268                FillWidget::new()
3269                    .on_pointer_event(move |event, ctx| {
3270                        match event {
3271                            WidgetEvent::PointerDown { position, .. } => {
3272                                a.borrow_mut().press.push(*position);
3273                                // Hold the pointer so the cancel funnel has
3274                                // someone to address.
3275                                ctx.capture_pointer();
3276                            }
3277                            WidgetEvent::PointerCancel {
3278                                window_position, ..
3279                            } => a.borrow_mut().cancel.push(*window_position),
3280                            _ => {}
3281                        }
3282                        EventResponse::Ignored
3283                    })
3284                    .on_scroll(move |event, _ctx| {
3285                        if let WidgetEvent::Scroll {
3286                            window_position, ..
3287                        } = event
3288                        {
3289                            b.borrow_mut().scroll.push(*window_position);
3290                        }
3291                        EventResponse::Ignored
3292                    }),
3293            )
3294        };
3295        // The slot puts its child 20 dp along, so window x and local x differ by
3296        // exactly 20 and a localised value is distinguishable from a raw one.
3297        let _root = tree.add(ShiftedSlot {
3298            child: target,
3299            offset: crate::signal::Signal::new(20.0f32),
3300        });
3301        tree.layout(SizeProposal::exact(100.0, 100.0));
3302
3303        let at = Point::new(30.0, 40.0);
3304        tree.dispatch_event(WidgetEvent::pointer_down(
3305            at,
3306            PointerButton::Primary,
3307            Modifiers::NONE,
3308        ));
3309        tree.dispatch_event(WidgetEvent::Scroll {
3310            delta: crate::event::ScrollDelta::Lines { x: 0.0, y: 1.0 },
3311            modifiers: Modifiers::NONE,
3312            window_position: Some(at),
3313            phase: crate::pointer::ScrollPhase::Discrete,
3314            pointer: crate::pointer::PointerInfo::mouse(crate::pointer::EventTime::ZERO),
3315        });
3316        let captor = tree.pointer_captured_by();
3317        assert_eq!(
3318            captor,
3319            Some(target),
3320            "the press must have taken the capture"
3321        );
3322        let pointer = tree.pointers.primary_id().expect("a live pointer");
3323        tree.cancel_pointer(
3324            pointer,
3325            crate::pointer::CancelReason::Platform,
3326            &mut crate::window::NoopWindowOps,
3327        );
3328
3329        let seen = seen.borrow();
3330        assert_eq!(
3331            seen.press,
3332            vec![Point::new(10.0, 40.0)],
3333            "a press is localised: window x 30 minus the slot's 20 dp offset"
3334        );
3335        assert_eq!(
3336            seen.scroll,
3337            vec![Some(at)],
3338            "`Scroll::window_position` must arrive as produced: localising it would feed \
3339             the kinetic tracker a frame that moves with the widget it measures"
3340        );
3341        assert_eq!(
3342            seen.cancel,
3343            vec![Some(at)],
3344            "`PointerCancel::window_position` must arrive as the revoking path recorded \
3345             it, for the same reason"
3346        );
3347    }
3348
3349    // --- fixtures --------------------------------------------------------
3350
3351    /// Splits its bounds down the middle: `a` on the leading half, `b` on the
3352    /// trailing one, so two pointers can land on two different widgets.
3353    #[derive(Debug)]
3354    struct SideBySide {
3355        a: WidgetId,
3356        b: WidgetId,
3357    }
3358
3359    impl crate::widget::Widget for SideBySide {
3360        fn layout_response(
3361            &self,
3362            proposal: SizeProposal,
3363            _ctx: &crate::widget::LayoutContext,
3364        ) -> crate::widget::LayoutResponse {
3365            proposal.resolve(0.0, 0.0).into()
3366        }
3367        fn place_children(
3368            &self,
3369            bounds: Rect,
3370            _proposal: SizeProposal,
3371            children: &mut [crate::widget::WidgetPlacement],
3372            _ctx: &crate::widget::LayoutContext,
3373        ) {
3374            let half = bounds.width / 2.0;
3375            for (index, c) in children.iter_mut().enumerate() {
3376                c.origin = Point::new(bounds.x + half * index as f32, bounds.y);
3377                c.size = teksilo_canvas::Size::new(half, bounds.height);
3378            }
3379        }
3380        fn children(&self) -> Vec<WidgetId> {
3381            vec![self.a, self.b]
3382        }
3383    }
3384
3385    /// Places its single child at a signal-driven horizontal offset, so a test
3386    /// can move a captured widget between two pointer samples.
3387    #[derive(Debug)]
3388    struct ShiftedSlot {
3389        child: WidgetId,
3390        offset: crate::signal::Signal<f32>,
3391    }
3392
3393    impl crate::widget::Widget for ShiftedSlot {
3394        fn layout_response(
3395            &self,
3396            proposal: SizeProposal,
3397            _ctx: &crate::widget::LayoutContext,
3398        ) -> crate::widget::LayoutResponse {
3399            proposal.resolve(0.0, 0.0).into()
3400        }
3401        fn place_children(
3402            &self,
3403            bounds: Rect,
3404            _proposal: SizeProposal,
3405            children: &mut [crate::widget::WidgetPlacement],
3406            _ctx: &crate::widget::LayoutContext,
3407        ) {
3408            for c in children.iter_mut() {
3409                c.origin = Point::new(bounds.x + self.offset.get(), bounds.y);
3410                c.size = bounds.size();
3411            }
3412        }
3413        fn children(&self) -> Vec<WidgetId> {
3414            vec![self.child]
3415        }
3416    }
3417}
3418
3419/// The arbitration binds the **timer** path, not only the sample path.
3420#[cfg(test)]
3421mod tick_arbitration_tests {
3422    use super::*;
3423    use crate::event::EventResponse;
3424    use crate::test_widgets::{FillWidget, StackWidget};
3425    use crate::widget_builder::WidgetBuilder;
3426    use std::cell::Cell;
3427    use std::rc::Rc;
3428    use std::time::Duration;
3429
3430    /// A tree whose innermost child holds the sequence on its press, under an
3431    /// ancestor that competes for the same press (`on_drag` is what enrols it)
3432    /// and also carries a long-press recognizer.
3433    ///
3434    /// `max_hold` is raised past `long_press` so the hold is still standing
3435    /// when the ancestor's timer comes due; with the shipped 250 ms hold and
3436    /// 500 ms long press the hold always expires first and the two never
3437    /// overlap, so there would be nothing to observe.
3438    fn tree_with_a_holder_under_a_long_pressing_peer(
3439        hold_on_press: bool,
3440    ) -> (WidgetTree, Rc<Cell<bool>>) {
3441        let long_pressed = Rc::new(Cell::new(false));
3442        let flag = long_pressed.clone();
3443
3444        let mut tree = WidgetTree::new();
3445        let mut theme = tree.theme().clone();
3446        theme.input.gestures.mouse.max_hold = Duration::from_millis(2000);
3447        tree.set_theme(theme);
3448
3449        let child = tree.add(FillWidget::new().on_pointer_event(move |event, ctx| {
3450            if hold_on_press && matches!(event, WidgetEvent::PointerDown { .. }) {
3451                ctx.hold_gesture();
3452            }
3453            EventResponse::Ignored
3454        }));
3455        tree.add(
3456            StackWidget::new()
3457                .child(child)
3458                .on_drag(|_phase, _c| {})
3459                .on_long_press(move |_e, _c| flag.set(true)),
3460        );
3461        tree.layout(SizeProposal::exact(300.0, 50.0));
3462        (tree, long_pressed)
3463    }
3464
3465    /// The control: with nothing holding, the peer's long press does fire on
3466    /// the tick. Without this the test below would pass on a fixture that
3467    /// could never long-press at all.
3468    #[test]
3469    fn a_peers_long_press_fires_on_the_tick_when_nothing_holds() {
3470        let (mut tree, long_pressed) = tree_with_a_holder_under_a_long_pressing_peer(false);
3471        tree.pointer_down_button(Point::new(20.0, 25.0), PointerButton::Primary);
3472        tree.advance_time(Duration::from_millis(600));
3473        assert!(
3474            long_pressed.get(),
3475            "the ancestor is a member with a long-press recognizer and its \
3476             timer came due"
3477        );
3478    }
3479
3480    /// …and it does not while a peer is holding.
3481    ///
3482    /// `hold_gesture` freezes the arbitration: no other member may win while a
3483    /// member is still deciding. The sample path has always honoured that
3484    /// (`sequence_blocks_arena`); the timer path dispatched whatever a
3485    /// recognizer produced, so a long press whose deadline happened to fall
3486    /// inside a hold fired anyway — which is the same recognizer winning, one
3487    /// door over.
3488    #[test]
3489    fn a_peers_long_press_does_not_fire_on_the_tick_while_a_member_holds() {
3490        let (mut tree, long_pressed) = tree_with_a_holder_under_a_long_pressing_peer(true);
3491        tree.pointer_down_button(Point::new(20.0, 25.0), PointerButton::Primary);
3492        assert!(
3493            tree.sequence_members(crate::pointer::PointerId::MOUSE)
3494                .iter()
3495                .any(|(_, _, state)| *state == crate::gesture::MemberState::Held),
3496            "the fixture must actually be holding"
3497        );
3498
3499        tree.advance_time(Duration::from_millis(600));
3500        assert!(
3501            !long_pressed.get(),
3502            "no peer may win while a member is holding — the timer path is not \
3503             a way around the arbitration"
3504        );
3505    }
3506}