Prune commentary and stale Rust port notes
This commit is contained in:
1 parent
3ae034a47b
commit
1e6d3b1edd
84 files changed
+334
-5648
No files matched your search
@@ -1,11 +1,3 @@
|
||||
//! IRIS_TODO.md's "Input does not fall through by input type": a widget
|
||||
//! that only registered `click()` used to also block a `ScrollArea` meant for
|
||||
//! whatever is behind it, because `run_sensors` decided "consumed, stop
|
||||
//! looking at lower layers" from mere hover, not from anything actually
|
||||
//! matching. Exercised as a plain unit test for the same reason
|
||||
//! `layout_tests.rs` is one: `UiRenderState` and a minimal `HasEvents`
|
||||
//! impl need no GPU or window.
|
||||
|
||||
use crate::prelude::*;
|
||||
use std::{cell::Cell, rc::Rc, time::Instant};
|
||||
|
||||
@@ -62,7 +54,6 @@ fn a_button_over_a_list_scrolls_the_list_and_still_clicks() {
|
||||
events: EventManager::default(),
|
||||
};
|
||||
|
||||
// Both cover the whole window -- the button "sitting over" the list,
|
||||
// the case in IRIS_TODO.md's report.
|
||||
let list = rsc.ui.widgets.add_strong(Rect::new(UiColor::WHITE));
|
||||
let list_weak = list.weak();
|
||||
@@ -88,9 +79,6 @@ fn a_button_over_a_list_scrolls_the_list_and_still_clicks() {
|
||||
});
|
||||
}
|
||||
|
||||
// A Stack draws its children on separate layers in order, which is
|
||||
// exactly the "one thing drawn over another" shape `run_sensors`
|
||||
// walks top layer first.
|
||||
let root = rsc
|
||||
.ui
|
||||
.widgets
|
||||
@@ -130,15 +118,6 @@ fn a_button_over_a_list_scrolls_the_list_and_still_clicks() {
|
||||
);
|
||||
}
|
||||
|
||||
/// The bug behind "finger flings do nothing" (RUST.md's P0 phone report,
|
||||
/// defect 2): a fast gesture's `PressEnd` can land at a screen position
|
||||
/// nothing is registered at -- past the edge of whatever widget noticed
|
||||
/// the press, in a gap, or off the loaded content entirely. Before pointer
|
||||
/// capture, `run_sensors`' hit test simply delivered nothing that frame,
|
||||
/// so a widget mid-drag never saw its release and never got a chance to
|
||||
/// start a fling. `PointerRequests::capture`/`DragGesture` fix this
|
||||
/// by giving the drag's widget every frame regardless of where the
|
||||
/// pointer is, including the terminal `Drop` in place of `PressEnd`.
|
||||
#[test]
|
||||
fn a_release_outside_every_hit_region_still_reaches_the_captured_widget() {
|
||||
let mut rsc = SenseRsc {
|
||||
@@ -146,8 +125,6 @@ fn a_release_outside_every_hit_region_still_reaches_the_captured_widget() {
|
||||
events: EventManager::default(),
|
||||
};
|
||||
|
||||
// A small draggable widget in the corner -- the release below lands
|
||||
// far outside it, exactly the "moved off the hit region" case.
|
||||
let draggable = rsc.ui.widgets.add_strong(Rect::new(UiColor::WHITE)).any();
|
||||
let draggable_weak = draggable.weak();
|
||||
|
||||
@@ -159,10 +136,6 @@ fn a_release_outside_every_hit_region_still_reaches_the_captured_widget() {
|
||||
CursorSense::click_or_drag() | CursorSense::unclick() | CursorSense::Drop,
|
||||
move |ctx, rsc| match ctx.data.sense {
|
||||
CursorSense::PressStart(_) | CursorSense::Pressing(_) => {
|
||||
// Any committed drag takes capture -- a real caller
|
||||
// would gate this on a `DragArbiter`/`DragGesture`
|
||||
// decision, but this test only needs to exercise the
|
||||
// capture-and-release mechanics themselves.
|
||||
ctx.data.pointer.capture(draggable_weak.id());
|
||||
let _ = rsc;
|
||||
}
|
||||
@@ -187,8 +160,6 @@ fn a_release_outside_every_hit_region_still_reaches_the_captured_widget() {
|
||||
"the press should have taken capture"
|
||||
);
|
||||
|
||||
// The release lands nowhere near the widget's own region -- the exact
|
||||
// shape of a fast fling's `ACTION_UP`.
|
||||
let mut release = cursor_at((95.0, 95.0).into());
|
||||
release.buttons.left = ActivationState::End;
|
||||
render.run_sensors(&mut rsc, &mut state, release, (100.0, 100.0).into());
|
||||
@@ -206,12 +177,6 @@ fn a_release_outside_every_hit_region_still_reaches_the_captured_widget() {
|
||||
);
|
||||
}
|
||||
|
||||
/// A widget that never registers `CursorSense::Drop` at all must not be
|
||||
/// affected by someone else's capture -- capture is per-gesture, not
|
||||
/// global suppression of the whole input system for widgets that were
|
||||
/// never party to it. (Practically this matters because a captured
|
||||
/// widget's registration list still has to include `Drop` for `should_run`
|
||||
/// to ever match it; this pins that half of the contract.)
|
||||
#[test]
|
||||
fn capturing_one_widget_starves_every_other_widget_of_events() {
|
||||
let mut rsc = SenseRsc {
|
||||
@@ -257,13 +222,6 @@ fn capturing_one_widget_starves_every_other_widget_of_events() {
|
||||
);
|
||||
}
|
||||
|
||||
/// IRIS_TODO.md's "the composer has no touch-drag scroll": `ScrollArea` only
|
||||
/// answered a wheel, so a finger drag over overflowed text did nothing.
|
||||
/// End-to-end over the real wiring -- `scrollable()`'s own registration,
|
||||
/// `run_sensors`' dispatch, `ScrollController::drag`, `DragGesture`'s arbitration and
|
||||
/// pointer capture -- rather than only `ScrollController::drag`'s own unit tests in
|
||||
/// `scroll.rs`, because the registration is exactly the half those cannot
|
||||
/// see.
|
||||
#[test]
|
||||
fn a_finger_drag_over_a_scroll_area_pans_it() {
|
||||
let mut rsc = SenseRsc {
|
||||
@@ -271,7 +229,6 @@ fn a_finger_drag_over_a_scroll_area_pans_it() {
|
||||
events: EventManager::default(),
|
||||
};
|
||||
|
||||
// 1000px of content in a 100px window: room to pan.
|
||||
let scroll_strong = rect(UiColor::WHITE)
|
||||
.height(Len::abs(1000.0))
|
||||
.scrollable(Axis::Y, Pin::Start)
|
||||
@@ -282,11 +239,6 @@ fn a_finger_drag_over_a_scroll_area_pans_it() {
|
||||
let mut render = UiRenderState::new();
|
||||
render.resize((100.0, 100.0));
|
||||
render.update(&root, &mut rsc);
|
||||
// `ScrollArea` reads its content length back from the draw it just did, so
|
||||
// the frame after is the first one that knows there is anything to pan
|
||||
// -- the one-frame lag LAYOUT.md section 4 documents. `scroll(0.0)` is
|
||||
// how `layout_tests.rs` asks for that second frame, and it also drops
|
||||
// `snap_end`, leaving this parked at the start of the content.
|
||||
rsc.ui.widgets.get_mut(&scroll).unwrap().scroll(0.0);
|
||||
render.update(&root, &mut rsc);
|
||||
assert_eq!(rsc.ui.widgets.get(&scroll).unwrap().amt(), 0.0);
|
||||
@@ -302,7 +254,6 @@ fn a_finger_drag_over_a_scroll_area_pans_it() {
|
||||
"the touch-down alone must not move anything"
|
||||
);
|
||||
|
||||
// Inside the slop: still a tap as far as anything can tell.
|
||||
let mut nudge = cursor_at((50.0, 80.0 - (DRAG_SLOP - 1.0)).into());
|
||||
nudge.buttons.left = ActivationState::On;
|
||||
render.run_sensors(&mut rsc, &mut state, nudge, (100.0, 100.0).into());
|
||||
@@ -313,8 +264,6 @@ fn a_finger_drag_over_a_scroll_area_pans_it() {
|
||||
"a press inside DRAG_SLOP must not scroll"
|
||||
);
|
||||
|
||||
// Past it, upward: the content follows the finger up, which for this
|
||||
// widget means more `amt`.
|
||||
let mut drag = cursor_at((50.0, 80.0 - (DRAG_SLOP + 40.0)).into());
|
||||
drag.buttons.left = ActivationState::On;
|
||||
render.run_sensors(&mut rsc, &mut state, drag, (100.0, 100.0).into());
|
||||
@@ -325,23 +274,13 @@ fn a_finger_drag_over_a_scroll_area_pans_it() {
|
||||
"expected the 40px past the slop to pan it, got {after}"
|
||||
);
|
||||
|
||||
// And the gesture holds the pointer, so the rest of it reaches this
|
||||
// widget even once the finger leaves its box.
|
||||
assert_eq!(pointer_input(&mut rsc).holder(), Some(scroll.id()));
|
||||
}
|
||||
|
||||
/// A defect found in review, 2026-09-07. The first `MotionEvent` a view
|
||||
/// sees can be a `Move` -- the `Down` went to another view, or the view was attached
|
||||
/// mid-gesture -- and its batched samples are older than its own
|
||||
/// timestamp. Anchoring on that timestamp clamped every one of them onto
|
||||
/// the anchor, so the tracker saw three samples at one instant, the Lsq2
|
||||
/// fit went degenerate, and the flick read 0 px/s.
|
||||
#[test]
|
||||
fn the_first_events_batched_samples_are_dated_apart() {
|
||||
const MS: i64 = 1_000_000;
|
||||
let now = Instant::now();
|
||||
// A 120Hz batch: three historical samples at 0/4/8ms and the event's
|
||||
// own at 12ms.
|
||||
let clock = DeviceClock::anchored(now, 12 * MS, 0);
|
||||
|
||||
assert_eq!(
|
||||
@@ -361,9 +300,6 @@ fn the_first_events_batched_samples_are_dated_apart() {
|
||||
assert_eq!(clock.ms_since_anchor(8 * MS), 8);
|
||||
}
|
||||
|
||||
/// The same clock has to keep ordering *across* events: the sample it
|
||||
/// compares a new event's first sample against is the previous event's
|
||||
/// last one, never the anchor.
|
||||
#[test]
|
||||
fn the_clock_orders_samples_across_events() {
|
||||
const MS: i64 = 1_000_000;
|
||||
@@ -377,18 +313,6 @@ fn the_clock_orders_samples_across_events() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Iris's 2026-09-08 phone report, first half: "it keeps snapping back to
|
||||
/// some position when horizontally scrolling."
|
||||
///
|
||||
/// A `ScrollArea` that has committed to a pan holds the pointer, so the
|
||||
/// gesture's end arrives as `CursorSense::Drop` -- and `scrollable`
|
||||
/// used to register `click_or_drag | unclick` only, which `should_run`
|
||||
/// never matches a `Drop` against. So the widget never learned its own
|
||||
/// gesture had ended: its `DragArbiter` stayed `Panning` at the position
|
||||
/// the finger left, and the *next* drag's first frame was measured from
|
||||
/// there and applied in one step. The registration is
|
||||
/// `CursorSense::drag_senses()` now, which is the rule for every widget
|
||||
/// driving a `DragGesture` rather than a fact about this one.
|
||||
#[test]
|
||||
fn a_scroll_area_that_captured_the_pointer_learns_its_gesture_ended() {
|
||||
let mut rsc = SenseRsc {
|
||||
@@ -417,8 +341,6 @@ fn a_scroll_area_that_captured_the_pointer_learns_its_gesture_ended() {
|
||||
render.update(&root, rsc);
|
||||
};
|
||||
|
||||
// One pan of 40px past the slop, then a release well outside the
|
||||
// widget -- the ordinary shape of a flick.
|
||||
send(&mut render, &mut rsc, 80.0, ActivationState::Start);
|
||||
send(
|
||||
&mut render,
|
||||
@@ -435,9 +357,6 @@ fn a_scroll_area_that_captured_the_pointer_learns_its_gesture_ended() {
|
||||
"the release must give the pointer back"
|
||||
);
|
||||
|
||||
// A second gesture, starting where the first one did. If the arbiter
|
||||
// were still panning from the release position, this first frame
|
||||
// would apply the whole distance between the two at once.
|
||||
send(&mut render, &mut rsc, 80.0, ActivationState::Start);
|
||||
let after_second = rsc.ui.widgets.get(&scroll).unwrap().amt();
|
||||
assert!(
|
||||
@@ -448,16 +367,6 @@ fn a_scroll_area_that_captured_the_pointer_learns_its_gesture_ended() {
|
||||
);
|
||||
}
|
||||
|
||||
/// The second half of the same report: "tapping sometimes seems to make
|
||||
/// the scrolling jump, particularly when tapping on things that have
|
||||
/// events like horizontal scrolling."
|
||||
///
|
||||
/// Two widgets see the same press -- a scroll area and, under it,
|
||||
/// something tracking the gesture for a list. When the scroll area
|
||||
/// captures, the other one is cut off completely: no `PressEnd`, no
|
||||
/// `Drop`. It has to be told, or its gesture stays open at an origin
|
||||
/// belonging to a finger that has long gone, and the next unrelated touch
|
||||
/// is measured from it.
|
||||
#[test]
|
||||
fn taking_the_pointer_cancels_everyone_else_tracking_the_press() {
|
||||
let mut rsc = SenseRsc {
|
||||
@@ -465,10 +374,6 @@ fn taking_the_pointer_cancels_everyone_else_tracking_the_press() {
|
||||
events: EventManager::default(),
|
||||
};
|
||||
|
||||
// The bystander contains the capturer on a lower visual layer: the
|
||||
// shape of a vertical transcript scroller with a higher horizontal
|
||||
// scroller inside one row. Both observe the undecided press, then only
|
||||
// the recognizer matching its direction may consume it.
|
||||
let capturer = rsc.ui.widgets.add_strong(Rect::new(UiColor::WHITE));
|
||||
let capturer_weak = capturer.weak();
|
||||
let bystander = rsc.ui.widgets.add_strong(Stack {
|
||||
@@ -535,8 +440,6 @@ fn taking_the_pointer_cancels_everyone_else_tracking_the_press() {
|
||||
"the widget that lost the gesture must be told exactly once"
|
||||
);
|
||||
|
||||
// And exactly once: the frames after the capture reach the capturer
|
||||
// alone, so there is nothing left to cancel.
|
||||
let mut more = cursor_at((50.0, 10.0).into());
|
||||
more.buttons.left = ActivationState::On;
|
||||
render.run_sensors(&mut rsc, &mut state, more, win);
|
||||
@@ -554,16 +457,6 @@ fn taking_the_pointer_cancels_everyone_else_tracking_the_press() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Iris's rule for nested scrolling, 2026-09-08: "it should only trigger
|
||||
/// horizontal if you drag left or right, and vertical should fall through
|
||||
/// if you drag up or down."
|
||||
///
|
||||
/// One mechanism does both, and it is `DragArbiter`'s existing axis test:
|
||||
/// each scroll area's gesture commits only on its own axis, so a drag
|
||||
/// along the other one is never claimed and the enclosing area's gesture
|
||||
/// -- which sees the same press, being an ancestor rather than a sibling
|
||||
/// layer -- is the one that commits and captures. This pins the pair,
|
||||
/// including the direction the change had no reason to touch.
|
||||
#[test]
|
||||
fn a_drag_pans_whichever_nested_scroll_area_owns_its_axis() {
|
||||
for (name, to, pans, still) in [
|
||||
@@ -579,23 +472,16 @@ fn a_drag_pans_whichever_nested_scroll_area_owns_its_axis() {
|
||||
ui: UiData::default(),
|
||||
events: EventManager::default(),
|
||||
};
|
||||
// 1000px square of content in a 100px window: room to pan either
|
||||
// way, in an X area inside a Y one.
|
||||
let seen = Rc::new(Cell::new(None));
|
||||
let record = seen.clone();
|
||||
let outer_strong = rect(UiColor::WHITE)
|
||||
.width(Len::abs(1000.0))
|
||||
.height(Len::abs(1000.0))
|
||||
.scrollable(Axis::X, Pin::Start)
|
||||
// The inner area's own handle, taken as the chain is built --
|
||||
// the whole point is to exercise `scrollable`'s real
|
||||
// registration on both, so neither is assembled by hand.
|
||||
.with_id(move |_rsc, id| {
|
||||
record.set(Some(id));
|
||||
id
|
||||
})
|
||||
// The horizontal area is visually above the vertical one, as
|
||||
// it is when a raised transcript row contains sideways content.
|
||||
.layer_offset(1)
|
||||
.scrollable(Axis::Y, Pin::Start)
|
||||
.add_strong(&mut rsc);
|
||||
@@ -607,8 +493,6 @@ fn a_drag_pans_whichever_nested_scroll_area_owns_its_axis() {
|
||||
let mut render = UiRenderState::new();
|
||||
render.resize((100.0, 100.0));
|
||||
render.update(&root, &mut rsc);
|
||||
// The second frame, where each area knows its content length --
|
||||
// LAYOUT.md section 4's one-frame lag, and what drops `snap_end`.
|
||||
for a in areas {
|
||||
rsc.ui.widgets.get_mut(&a).unwrap().scroll(0.0);
|
||||
}
|
||||
@@ -639,24 +523,6 @@ fn a_drag_pans_whichever_nested_scroll_area_owns_its_axis() {
|
||||
}
|
||||
}
|
||||
|
||||
/// Iris's 2026-09-08 report: "if I try to scroll vertically while a
|
||||
/// horizontal scroll animation is still active, it stays locked to the
|
||||
/// horizontal scroll", with her own diagnosis -- "tapping outside of
|
||||
/// something that a fling is currently active for should have no code in
|
||||
/// common with the fling that could influence it."
|
||||
///
|
||||
/// She was right that it was global state, and this is where it lived.
|
||||
/// `run_sensors` runs a widget one more frame *after* the pointer has
|
||||
/// left it, so a `HoverEnd` can fire ([`ActivationState::End`], which is
|
||||
/// not `Off`) -- and `should_run` derived a press from the button alone,
|
||||
/// so that farewell frame also carried a `PressStart`. A widget nowhere
|
||||
/// near the finger therefore opened a gesture, and a `ScrollArea` catching
|
||||
/// its own fling commits with no slop, so it captured the pointer and the
|
||||
/// whole gesture went to it.
|
||||
///
|
||||
/// Two areas side by side here rather than one, because "the press went
|
||||
/// to the wrong widget" and "the press went nowhere" are different
|
||||
/// failures and only the second area can tell them apart.
|
||||
#[test]
|
||||
fn a_press_does_not_reach_a_widget_the_pointer_has_just_left() {
|
||||
let mut rsc = SenseRsc {
|
||||
@@ -664,11 +530,6 @@ fn a_press_does_not_reach_a_widget_the_pointer_has_just_left() {
|
||||
events: EventManager::default(),
|
||||
};
|
||||
|
||||
// Two 1000px-tall scroll areas, stacked: the top half of the window
|
||||
// is the first, the bottom half the second. Each area's own handle is
|
||||
// taken as its chain is built (`with_id`, the same way the nested-axes
|
||||
// test above does it), since what is under test is `scrollable()`'s
|
||||
// real registration rather than a `ScrollArea` assembled by hand.
|
||||
let seen: [Rc<Cell<Option<WeakWidget<ScrollArea>>>>; 2] = Default::default();
|
||||
let half = |slot: &Rc<Cell<Option<WeakWidget<ScrollArea>>>>| {
|
||||
let record = slot.clone();
|
||||
@@ -691,19 +552,12 @@ fn a_press_does_not_reach_a_widget_the_pointer_has_just_left() {
|
||||
let mut render = UiRenderState::new();
|
||||
render.resize((win.x, win.y));
|
||||
render.update(&root, &mut rsc);
|
||||
// The second frame is the first that knows how long the content is --
|
||||
// see `a_finger_drag_over_a_scroll_area_pans_it`.
|
||||
for w in [&top_w, &bottom_w] {
|
||||
rsc.ui.widgets.get_mut(w).unwrap().scroll(0.0);
|
||||
}
|
||||
render.update(&root, &mut rsc);
|
||||
|
||||
let mut state = ();
|
||||
// Flick the top area and let go: it is left flinging, and -- because
|
||||
// the release goes through `run_sensors`' capture branch, which
|
||||
// returns before the loop that would have updated anybody's hover --
|
||||
// its sensor is left `On` with the pointer no longer on it. Both
|
||||
// halves of the real gesture, since both are what the bug needs.
|
||||
let base = Instant::now();
|
||||
let mut t = 0;
|
||||
let sample = |render: &mut UiRenderState,
|
||||
@@ -746,9 +600,6 @@ fn a_press_does_not_reach_a_widget_the_pointer_has_just_left() {
|
||||
);
|
||||
let flung_to = rsc.ui.widgets.get(&top_w).unwrap().amt();
|
||||
|
||||
// Now press and drag in the *bottom* area: the top area's hover
|
||||
// decays to `End` on this very sample, which is the frame that used
|
||||
// to carry a `PressStart` to it.
|
||||
t += 8;
|
||||
sample(
|
||||
&mut render,
|
||||
|
||||
Reference in new issue
Block a user