Skip to main content

teksilo_core/pointer/
table.rs

1// SPDX-License-Identifier: MPL-2.0
2// SPDX-FileCopyrightText: 2026 FernTech
3
4//! The live pointer table: one entry per pointer the tree currently knows
5//! about, and the two elected roles that replace the tree's old singular
6//! pointer state.
7//!
8//! # Three notions that used to be one
9//!
10//! Before this table the tree had a single `hovered`, a single
11//! `last_pointer_position` and a single `pointer_captured_by`, and "the
12//! pointer" meant all three at once. Multi-touch splits that into three
13//! genuinely different questions:
14//!
15//! 1. [`PointerInfo::primary`](crate::pointer::PointerInfo::primary) is the
16//!    **W3C Pointer Events Level 3 per-kind flag**. Every mouse event is
17//!    primary, and so is the first touch of a sequence. On a hybrid machine a
18//!    mouse and a first touch are *both* primary. It is a property of the
19//!    sample, decided by whoever produced it, and this table never rewrites it.
20//! 2. [`PointerTable::primary`] is **Teksilo's** single pointer — the one that
21//!    backs the legacy singular position accessor
22//!    ([`WidgetTree::last_pointer_position`](crate::WidgetTree::last_pointer_position)).
23//!    Exactly one live pointer holds the role, a mouse always wins it, and it
24//!    is a table-level election rather than anything carried on a sample.
25//! 3. [`PointerTable::hover_owner`] is the most recent **hovering-capable**
26//!    pointer — a mouse, or a pen in proximity ([`PointerKind::hovers`]) — and
27//!    the one [`WidgetTree::hovered`](crate::WidgetTree::hovered) reads.
28//! 3. [`PointerTable::hover_owner`] is the most recent **hovering-capable**
29//!    pointer — a mouse, or a pen in proximity ([`PointerKind::hovers`]).
30//!
31//! **Hover follows the hover owner, never the primary.** Enter/leave, the
32//! cursor, tooltip dwell and every `on_hover` handler are hover-owner-only, and
33//! a touch contact is never a hover owner: a finger has no hover state to
34//! report, so a contact that wrote hover would make every hover affordance fire
35//! on tap and stick there after the lift. Affordances that today rely on hover
36//! get their own touch routes; they do not get them by pretending a finger
37//! hovers.
38//!
39//! # Why a `Vec`
40//!
41//! The live set is at most [`PointerTable::DEFAULT_CAP`] entries, so every
42//! operation is a linear scan of a handful of elements — cheaper than hashing
43//! a [`PointerId`], and it keeps iteration in a stable, oldest-first order.
44//!
45//! Reference: `docs/touch-and-pen.md`.
46
47use teksilo_canvas::Point;
48use teksilo_tokens::PointerKind;
49
50use super::{PointerId, PointerInfo, PointerPhase, PointerSample};
51use crate::widget_id::WidgetId;
52
53/// Everything the tree tracks about one live pointer.
54///
55/// One of these exists from the pointer's first sample until it lifts (a
56/// contact) or the window loses it (a hovering pointer never lifts, so a
57/// mouse's entry persists for the life of the tree once it has been seen).
58///
59/// `#[non_exhaustive]`: the velocity tracker lands here in a later package
60/// (the gesture-sequence handle already has), and it should not break a
61/// construction site.
62#[non_exhaustive]
63#[derive(Clone, Debug, PartialEq)]
64pub struct PointerEntry {
65    /// Who this pointer is, as of its most recent sample.
66    pub info: PointerInfo,
67    /// Where it is now, in window-logical coordinates.
68    pub position: Point,
69    /// Where its most recent press landed. Equal to
70    /// [`position`](Self::position) until the first press, so a slop test
71    /// against it is never nonsense.
72    pub down_position: Point,
73    /// The widget it is hovering, if it is a hovering-capable pointer that
74    /// currently holds the hover-owner role. Always `None` for a contact.
75    pub hovered: Option<WidgetId>,
76    /// The widget that captured *this* pointer. Capture is per pointer: two
77    /// contacts hold independent captures, and each is released only by its
78    /// own Up or Cancel.
79    pub captured_by: Option<WidgetId>,
80    /// Strict ancestors of [`hovered`](Self::hovered) whose `hover_within`
81    /// signal this pointer currently holds `true`.
82    pub hover_within: Vec<WidgetId>,
83    /// The arbitration in progress for this pointer's press: who is competing
84    /// for it, and who won. `None` between presses — a hovering mouse has an
85    /// entry but no sequence. See
86    /// [`PointerSequence`](crate::gesture::PointerSequence).
87    pub sequence: Option<crate::gesture::PointerSequence>,
88    /// The last widget that answered `Handled` to one of this pointer's
89    /// positional events.
90    ///
91    /// The fallback recipient of a
92    /// [`PointerCancel`](crate::event::WidgetEvent::PointerCancel) when the
93    /// pointer holds no capture: something was interacting with this pointer,
94    /// and it is the only thing the tree can name. Kept per pointer rather
95    /// than per tree, because two contacts on two widgets each have their own
96    /// answer.
97    pub last_accepted: Option<WidgetId>,
98}
99
100impl PointerEntry {
101    /// A fresh entry for `info` first seen at `position`.
102    fn new(info: PointerInfo, position: Point) -> Self {
103        Self {
104            info,
105            position,
106            down_position: position,
107            hovered: None,
108            captured_by: None,
109            hover_within: Vec::new(),
110            sequence: None,
111            last_accepted: None,
112        }
113    }
114
115    /// Whether this pointer can report a position without a button held, and
116    /// so can own hover. See [`PointerKind::hovers`].
117    pub fn hovers(&self) -> bool {
118        self.info.kind.hovers()
119    }
120
121    /// Whether this pointer is touching the surface: a contact is touching for
122    /// as long as it is live, a hovering-capable pointer only while a button
123    /// is held.
124    pub fn is_contacting(&self) -> bool {
125        !self.hovers() || !self.info.buttons.is_empty()
126    }
127}
128
129/// The live pointers, plus the primary and hover-owner elections.
130///
131/// See the [module docs](self) for what those two roles mean and why they are
132/// not the same thing as [`PointerInfo::primary`].
133#[derive(Clone, Debug)]
134pub struct PointerTable {
135    entries: Vec<PointerEntry>,
136    primary: Option<PointerId>,
137    hover_owner: Option<PointerId>,
138    cap: usize,
139}
140
141impl Default for PointerTable {
142    fn default() -> Self {
143        Self::new()
144    }
145}
146
147impl PointerTable {
148    /// How many pointers the table holds at once.
149    ///
150    /// Ten: the maximum simultaneous contacts a Windows digitiser and a
151    /// Wayland `wl_touch` seat both report. An eleventh arrival is refused at
152    /// [`begin`](Self::begin) rather than evicting one of the ten, because
153    /// evicting a contact mid-gesture is how a pinch turns into a fling.
154    pub const DEFAULT_CAP: usize = 10;
155
156    /// An empty table with the default cap.
157    pub fn new() -> Self {
158        Self {
159            entries: Vec::new(),
160            primary: None,
161            hover_owner: None,
162            cap: Self::DEFAULT_CAP,
163        }
164    }
165
166    /// An empty table that holds at most `cap` pointers. For tests that want
167    /// to reach the cap without simulating ten fingers.
168    pub fn with_cap(cap: usize) -> Self {
169        Self {
170            cap: cap.max(1),
171            ..Self::new()
172        }
173    }
174
175    /// The cap this table was built with.
176    pub fn cap(&self) -> usize {
177        self.cap
178    }
179
180    /// The entry for `id`, if that pointer is live.
181    pub fn get(&self, id: PointerId) -> Option<&PointerEntry> {
182        self.entries.iter().find(|e| e.info.id == id)
183    }
184
185    /// Mutable access to the entry for `id`, if that pointer is live.
186    pub fn get_mut(&mut self, id: PointerId) -> Option<&mut PointerEntry> {
187        self.entries.iter_mut().find(|e| e.info.id == id)
188    }
189
190    /// Admit `sample`'s pointer, creating its entry on first sight and
191    /// refreshing it afterwards.
192    ///
193    /// Returns `None` — and traces the refusal — when the sample must not be
194    /// dispatched at all:
195    ///
196    /// * the backend flagged the contact as a palm
197    ///   ([`PointerInfo::palm`](crate::pointer::PointerInfo::palm)), or
198    /// * the table is full ([`DEFAULT_CAP`](Self::DEFAULT_CAP)).
199    ///
200    /// A pointer already in the table is always refreshed, cap or no cap: the
201    /// cap bounds how many pointers exist, never how many samples an admitted
202    /// pointer may send.
203    pub fn begin(&mut self, sample: &PointerSample) -> Option<PointerId> {
204        if !self.would_admit(&sample.pointer) {
205            return None;
206        }
207        self.admit(
208            sample.pointer,
209            sample.position,
210            sample.phase == PointerPhase::Down,
211        )
212    }
213
214    /// Whether [`begin`](Self::begin) would accept `info`, without touching the
215    /// table — and tracing the refusal, since a dropped sample that leaves no
216    /// evidence is the hardest input bug there is.
217    ///
218    /// The dispatcher asks this *before* it lowers a sample onto an event, so a
219    /// refused pointer produces no event at all rather than one that is later
220    /// discovered to have no entry behind it.
221    pub fn would_admit(&self, info: &PointerInfo) -> bool {
222        if self.contains(info.id) {
223            // The cap bounds how many pointers exist, never how many samples an
224            // admitted pointer may send.
225            return true;
226        }
227        if info.palm {
228            crate::trace_input!(
229                Samples,
230                "dropping {:?}: the backend classified it as a palm",
231                info.id
232            );
233            return false;
234        }
235        if self.entries.len() >= self.cap {
236            crate::trace_input!(
237                Samples,
238                "dropping {:?}: the pointer table already holds {} pointers",
239                info.id,
240                self.cap
241            );
242            return false;
243        }
244        true
245    }
246
247    /// The primitive behind [`begin`](Self::begin), for the legacy
248    /// [`WidgetEvent`](crate::event::WidgetEvent) path, which has no
249    /// [`PointerSample`] to hand.
250    ///
251    /// `is_down` records `position` as the entry's
252    /// [`down_position`](PointerEntry::down_position).
253    pub fn admit(
254        &mut self,
255        info: PointerInfo,
256        position: Point,
257        is_down: bool,
258    ) -> Option<PointerId> {
259        let id = info.id;
260        if let Some(entry) = self.get_mut(id) {
261            entry.info = info;
262            entry.position = position;
263            if is_down {
264                entry.down_position = position;
265            }
266            self.elect();
267            return Some(id);
268        }
269        if !self.would_admit(&info) {
270            return None;
271        }
272        let mut entry = PointerEntry::new(info, position);
273        if is_down {
274            entry.down_position = position;
275        }
276        self.entries.push(entry);
277        self.elect();
278        Some(id)
279    }
280
281    /// Forget `id`, returning its entry if it was live.
282    ///
283    /// Called for a contact's Up or Cancel. A hovering-capable pointer is
284    /// **not** ended by an Up — a mouse that releases a button is still there,
285    /// still hovering, and its entry is what every legacy singular accessor
286    /// reads.
287    pub fn end(&mut self, id: PointerId) -> Option<PointerEntry> {
288        let index = self.entries.iter().position(|e| e.info.id == id)?;
289        let entry = self.entries.remove(index);
290        self.elect();
291        Some(entry)
292    }
293
294    /// Teksilo's single pointer: the one backing the legacy singular
295    /// accessors. A mouse is preferred; failing that, the oldest live pointer.
296    ///
297    /// Not to be confused with
298    /// [`PointerInfo::primary`](crate::pointer::PointerInfo::primary) — see
299    /// the [module docs](self).
300    pub fn primary(&self) -> Option<&PointerEntry> {
301        self.primary.and_then(|id| self.get(id))
302    }
303
304    /// The most recent hovering-capable pointer, if any is live.
305    ///
306    /// Hover, enter/leave, the cursor and tooltip dwell all follow this
307    /// pointer. A touch contact is never it.
308    pub fn hover_owner(&self) -> Option<&PointerEntry> {
309        self.hover_owner.and_then(|id| self.get(id))
310    }
311
312    /// Mutable access to the hover owner's entry.
313    pub fn hover_owner_mut(&mut self) -> Option<&mut PointerEntry> {
314        let id = self.hover_owner?;
315        self.get_mut(id)
316    }
317
318    /// The hover owner's id, without borrowing its entry.
319    pub fn hover_owner_id(&self) -> Option<PointerId> {
320        self.hover_owner
321    }
322
323    /// The primary pointer's id, without borrowing its entry.
324    pub fn primary_id(&self) -> Option<PointerId> {
325        self.primary
326    }
327
328    /// Every live pointer, oldest first.
329    pub fn iter(&self) -> impl Iterator<Item = &PointerEntry> + '_ {
330        self.entries.iter()
331    }
332
333    /// Every live pointer, mutably, oldest first.
334    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut PointerEntry> + '_ {
335        self.entries.iter_mut()
336    }
337
338    /// How many live pointers there are, hovering or not.
339    pub fn len(&self) -> usize {
340        self.entries.len()
341    }
342
343    /// Whether no pointer is live.
344    pub fn is_empty(&self) -> bool {
345        self.entries.is_empty()
346    }
347
348    /// How many live pointers are **touching the surface** — every contact,
349    /// plus any hovering-capable pointer with a button held. This is the
350    /// number a multi-touch recognizer counts fingers with; it is `0` for a
351    /// resting mouse and `2` for a two-finger pinch.
352    pub fn contact_count(&self) -> usize {
353        self.entries.iter().filter(|e| e.is_contacting()).count()
354    }
355
356    /// Whether `id` names a live pointer.
357    pub fn contains(&self, id: PointerId) -> bool {
358        self.get(id).is_some()
359    }
360
361    /// Scrub widget references that no longer name an active node.
362    ///
363    /// A rebuild mints fresh ids, so a hover target, a capture or a
364    /// `hover_within` ancestor recorded before it can be dangling afterwards —
365    /// and a dangling capture swallows every later Move and Up, because
366    /// dispatch refuses an inactive target. Called from the tree's
367    /// post-layout revalidation *after* it has run the hover-owner's own
368    /// signal-firing recovery, so this is the sweep for every other pointer.
369    pub fn retain_active(&mut self, arena: &crate::arena::WidgetArena) {
370        for entry in &mut self.entries {
371            if entry.hovered.is_some_and(|id| !arena.is_active(id)) {
372                entry.hovered = None;
373            }
374            if entry.captured_by.is_some_and(|id| !arena.is_active(id)) {
375                entry.captured_by = None;
376            }
377            entry.hover_within.retain(|id| arena.is_active(*id));
378            if entry.last_accepted.is_some_and(|id| !arena.is_active(id)) {
379                entry.last_accepted = None;
380            }
381        }
382    }
383
384    /// Release every capture held on `widget` — it is going away, and a
385    /// capture that outlives its owner strands the pointer.
386    pub fn release_captures_of(&mut self, widget: WidgetId) {
387        for entry in &mut self.entries {
388            if entry.captured_by == Some(widget) {
389                entry.captured_by = None;
390            }
391        }
392    }
393
394    /// Re-run both elections after the live set or a pointer's kind changed.
395    ///
396    /// **Primary**: a mouse if one is live (there is at most one), else the
397    /// oldest pointer — ids are minted monotonically, so "oldest" is "lowest
398    /// id", and a stable choice is what keeps the legacy accessors from
399    /// flickering between two contacts.
400    ///
401    /// **Hover owner**: the *most recent* hovering-capable pointer, which for
402    /// a mouse-plus-pen machine means the one the user last used. Preserved
403    /// across an election when it is still live and still hovering-capable, so
404    /// a contact arriving alongside a hovering mouse cannot take the role.
405    fn elect(&mut self) {
406        self.primary = self
407            .entries
408            .iter()
409            .find(|e| e.info.kind == PointerKind::Mouse)
410            .or_else(|| self.entries.iter().min_by_key(|e| e.info.id))
411            .map(|e| e.info.id);
412
413        let owner_still_valid = self
414            .hover_owner
415            .and_then(|id| self.get(id))
416            .is_some_and(|e| e.hovers());
417        if !owner_still_valid {
418            self.hover_owner = self
419                .entries
420                .iter()
421                .filter(|e| e.hovers())
422                .max_by_key(|e| e.info.id)
423                .map(|e| e.info.id);
424        }
425    }
426
427    /// Hand the hover-owner role to `id`, reporting the pointer that lost it.
428    ///
429    /// Called when a hovering-capable pointer produces a sample: the later
430    /// sample wins, and the caller sends the displaced owner a
431    /// [`PointerLeave`](crate::event::WidgetEvent::PointerLeave). A contact is
432    /// refused outright — it can never own hover — and so is a pointer that is
433    /// not live.
434    pub fn claim_hover_owner(&mut self, id: PointerId) -> Option<PointerId> {
435        if !self.get(id).is_some_and(|e| e.hovers()) {
436            return None;
437        }
438        let previous = self.hover_owner;
439        if previous == Some(id) {
440            return None;
441        }
442        self.hover_owner = Some(id);
443        previous.filter(|prev| self.contains(*prev))
444    }
445}
446
447#[cfg(test)]
448mod tests {
449    use super::*;
450    use crate::pointer::{EventTime, PointerAxes};
451    use teksilo_tokens::PenKind;
452
453    fn id(raw: u64) -> PointerId {
454        // Mint through the allocator so ids stay monotonic and distinct from
455        // `PointerId::MOUSE`; the device key is per-test so nothing collides.
456        let alloc = crate::pointer::PointerIdAllocator::global();
457        let device = crate::pointer::BackendDeviceKey::new(0x7AB1E ^ raw);
458        let out = alloc.begin(device, raw);
459        alloc.end(device, raw);
460        out
461    }
462
463    fn touch(pid: PointerId) -> PointerInfo {
464        PointerInfo::touch(pid, EventTime::ZERO)
465    }
466
467    fn mouse() -> PointerInfo {
468        PointerInfo::mouse(EventTime::ZERO)
469    }
470
471    fn pen(pid: PointerId) -> PointerInfo {
472        let mut info = PointerInfo::touch(pid, EventTime::ZERO);
473        info.kind = PointerKind::Pen(PenKind::Pen);
474        info
475    }
476
477    fn sample(info: PointerInfo, phase: PointerPhase, at: Point) -> PointerSample {
478        PointerSample {
479            pointer: info,
480            phase,
481            position: at,
482            button: None,
483            modifiers: crate::event::Modifiers::NONE,
484            coalesced: Vec::new(),
485        }
486    }
487
488    #[test]
489    fn a_mouse_wins_the_primary_role_over_an_older_contact() {
490        let mut table = PointerTable::new();
491        let finger = id(1);
492        table.admit(touch(finger), Point::ZERO, true);
493        assert_eq!(table.primary_id(), Some(finger), "the only live pointer");
494
495        table.admit(mouse(), Point::new(5.0, 5.0), false);
496        assert_eq!(
497            table.primary_id(),
498            Some(PointerId::MOUSE),
499            "a mouse always wins the primary role"
500        );
501    }
502
503    #[test]
504    fn a_contact_is_never_the_hover_owner() {
505        let mut table = PointerTable::new();
506        let finger = id(2);
507        table.admit(touch(finger), Point::ZERO, true);
508        assert_eq!(table.hover_owner_id(), None, "a finger cannot hover");
509        assert_eq!(table.claim_hover_owner(finger), None);
510        assert_eq!(table.hover_owner_id(), None);
511    }
512
513    #[test]
514    fn a_pen_can_own_hover_and_displaces_the_mouse() {
515        let mut table = PointerTable::new();
516        table.admit(mouse(), Point::ZERO, false);
517        assert_eq!(table.hover_owner_id(), Some(PointerId::MOUSE));
518
519        let stylus = id(3);
520        table.admit(pen(stylus), Point::new(2.0, 2.0), false);
521        // The pen is admitted but does not seize the role until it produces a
522        // sample the tree routes — `claim_hover_owner` is that moment.
523        assert_eq!(table.hover_owner_id(), Some(PointerId::MOUSE));
524        assert_eq!(
525            table.claim_hover_owner(stylus),
526            Some(PointerId::MOUSE),
527            "the displaced owner is reported so it can be sent a leave"
528        );
529        assert_eq!(table.hover_owner_id(), Some(stylus));
530    }
531
532    #[test]
533    fn ending_the_hover_owner_falls_back_to_another_hovering_pointer() {
534        let mut table = PointerTable::new();
535        table.admit(mouse(), Point::ZERO, false);
536        let stylus = id(4);
537        table.admit(pen(stylus), Point::ZERO, false);
538        table.claim_hover_owner(stylus);
539
540        table.end(stylus);
541        assert_eq!(table.hover_owner_id(), Some(PointerId::MOUSE));
542    }
543
544    #[test]
545    fn captures_are_independent_per_pointer() {
546        let mut table = PointerTable::new();
547        let a = id(5);
548        let b = id(6);
549        table.admit(touch(a), Point::ZERO, true);
550        table.admit(touch(b), Point::new(50.0, 0.0), true);
551
552        let mut arena = crate::arena::WidgetArena::new();
553        let w1 = arena.insert(Box::new(crate::test_widgets::FillWidget::new()));
554        let w2 = arena.insert(Box::new(crate::test_widgets::FillWidget::new()));
555        table.get_mut(a).expect("a is live").captured_by = Some(w1);
556        table.get_mut(b).expect("b is live").captured_by = Some(w2);
557
558        table.end(a);
559        assert_eq!(
560            table.get(b).and_then(|e| e.captured_by),
561            Some(w2),
562            "ending one contact must leave the other's capture alone"
563        );
564    }
565
566    #[test]
567    fn the_eleventh_contact_is_refused() {
568        let mut table = PointerTable::new();
569        for n in 0..PointerTable::DEFAULT_CAP {
570            let pid = id(100 + n as u64);
571            assert_eq!(
572                table.begin(&sample(touch(pid), PointerPhase::Down, Point::ZERO)),
573                Some(pid),
574                "contact {} is within the cap",
575                n + 1
576            );
577        }
578        assert_eq!(table.len(), PointerTable::DEFAULT_CAP);
579
580        let overflow = id(200);
581        assert_eq!(
582            table.begin(&sample(touch(overflow), PointerPhase::Down, Point::ZERO)),
583            None,
584            "the eleventh contact must be refused, not evicted onto another"
585        );
586        assert_eq!(table.len(), PointerTable::DEFAULT_CAP);
587    }
588
589    #[test]
590    fn a_live_pointer_is_refreshed_even_at_the_cap() {
591        let mut table = PointerTable::with_cap(1);
592        let finger = id(7);
593        table.admit(touch(finger), Point::ZERO, true);
594        assert_eq!(
595            table.admit(touch(finger), Point::new(9.0, 9.0), false),
596            Some(finger),
597            "the cap bounds pointers, not samples"
598        );
599        assert_eq!(
600            table.get(finger).map(|e| e.position),
601            Some(Point::new(9.0, 9.0))
602        );
603        assert_eq!(
604            table.get(finger).map(|e| e.down_position),
605            Some(Point::ZERO),
606            "a move must not move the press origin"
607        );
608    }
609
610    #[test]
611    fn a_palm_is_refused() {
612        let mut table = PointerTable::new();
613        let mut info = touch(id(8));
614        info.palm = true;
615        assert_eq!(
616            table.begin(&sample(info, PointerPhase::Down, Point::ZERO)),
617            None
618        );
619        assert!(table.is_empty());
620    }
621
622    #[test]
623    fn contact_count_ignores_a_resting_mouse() {
624        let mut table = PointerTable::new();
625        table.admit(mouse(), Point::ZERO, false);
626        assert_eq!(
627            table.contact_count(),
628            0,
629            "a hovering mouse is not a contact"
630        );
631
632        let mut pressed = mouse();
633        pressed.buttons = crate::event::ButtonMask::PRIMARY;
634        table.admit(pressed, Point::ZERO, true);
635        assert_eq!(table.contact_count(), 1, "a held button is a contact");
636
637        table.admit(touch(id(9)), Point::ZERO, true);
638        assert_eq!(table.contact_count(), 2);
639    }
640
641    #[test]
642    fn retain_active_scrubs_dead_widget_references() {
643        let mut arena = crate::arena::WidgetArena::new();
644        let live = arena.insert(Box::new(crate::test_widgets::FillWidget::new()));
645        let dead = WidgetId::default(); // the slotmap null key: never active
646
647        let mut table = PointerTable::new();
648        let finger = id(10);
649        table.admit(touch(finger), Point::ZERO, true);
650        {
651            let entry = table.get_mut(finger).expect("finger is live");
652            entry.hovered = Some(dead);
653            entry.captured_by = Some(dead);
654            entry.hover_within = vec![live, dead];
655        }
656        table.retain_active(&arena);
657
658        let entry = table.get(finger).expect("finger is still live");
659        assert_eq!(entry.hovered, None);
660        assert_eq!(entry.captured_by, None);
661        assert_eq!(entry.hover_within, vec![live]);
662    }
663
664    /// The default axes are carried through unchanged — the table stores the
665    /// sample's `PointerInfo` verbatim rather than rebuilding it.
666    #[test]
667    fn the_entry_keeps_the_samples_own_info() {
668        let mut table = PointerTable::new();
669        let stylus = id(11);
670        let mut info = pen(stylus);
671        info.axes = PointerAxes {
672            pressure: Some(0.4),
673            ..PointerAxes::default()
674        };
675        table.admit(info, Point::new(1.0, 2.0), true);
676        assert_eq!(
677            table.get(stylus).map(|e| e.info.axes.pressure),
678            Some(Some(0.4))
679        );
680    }
681}