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