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,13 +1,6 @@
|
||||
//! `ScrollArea`: a fixed child, slid about by a [`ScrollController`].
|
||||
//!
|
||||
//! **`docs/SCROLL.md` is the overview** -- the one sign convention, what
|
||||
//! `amt` means, and how this differs from a `LazySpan`, which scrolls
|
||||
//! itself. Read it first; this file is the detail.
|
||||
|
||||
use crate::prelude::*;
|
||||
use std::time::Instant;
|
||||
|
||||
/// A scrolling view that moves one fixed child as a subtree.
|
||||
pub struct ScrollArea {
|
||||
inner: StrongWidget,
|
||||
ctl: ScrollController,
|
||||
@@ -72,23 +65,15 @@ impl Widget for ScrollArea {
|
||||
}
|
||||
|
||||
impl ScrollArea {
|
||||
/// `pin` says which end this area opens at and clings to -- see
|
||||
/// [`Pin`], and `WidgetLike::scrollable`, which is how one of these is
|
||||
/// normally built.
|
||||
pub fn new(inner: StrongWidget, axis: Axis, pin: Pin) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
// A fixed child is laid out from the box's negative edge
|
||||
// onward, always, so the end of its content is the positive
|
||||
// one -- which is what makes `Pin::End` and `Pin::Pos` the
|
||||
// same pin here and different ones in a reversed `LazySpan`.
|
||||
ctl: ScrollController::new(Dir::new(axis, Sign::Pos), pin),
|
||||
container_len: 0.0,
|
||||
content_len: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// A content-sized box offset by the current scroll amount.
|
||||
fn child_region(&self, content_len: f32) -> UiRegion {
|
||||
let axis = self.ctl.axis();
|
||||
let mut region = UiRegion::FULL;
|
||||
@@ -105,22 +90,10 @@ mod tests {
|
||||
use iris_core::UiData;
|
||||
use std::time::Duration;
|
||||
|
||||
/// A scroll area with 1000px of content in a 100px box, drawn once and
|
||||
/// settled somewhere in the middle so a drag has room in both
|
||||
/// directions.
|
||||
///
|
||||
/// Built and rendered for real rather than assembled field by field,
|
||||
/// because a delta is spent in `draw` now (the controller banks it, and
|
||||
/// only a layout knows where the content ends) -- so a test that never
|
||||
/// draws would watch `amt` never move and read that as a broken
|
||||
/// gesture.
|
||||
fn area() -> (Fixture, WidgetId) {
|
||||
area_on(Axis::Y)
|
||||
}
|
||||
|
||||
/// The same fixture on either axis -- a code fence pans sideways
|
||||
/// through one of these exactly as a field pans down, and the pair of
|
||||
/// them is what caught a fling that only worked vertically.
|
||||
fn area_on(axis: Axis) -> (Fixture, WidgetId) {
|
||||
let mut rsc = TestRsc {
|
||||
ui: UiData::default(),
|
||||
@@ -149,17 +122,12 @@ mod tests {
|
||||
root,
|
||||
render,
|
||||
};
|
||||
// 400px in, which is the middle of the 900px of travel this
|
||||
// content has.
|
||||
fixture.get().scroll(-400.0);
|
||||
fixture.draw();
|
||||
assert!((fixture.amt() - 400.0).abs() < 0.01);
|
||||
(fixture, id)
|
||||
}
|
||||
|
||||
/// The area under test with everything needed to draw it -- the drag
|
||||
/// tests all do the same three things (reach the widget, draw, read
|
||||
/// `amt`) and each of the three is a line of arena plumbing.
|
||||
struct Fixture {
|
||||
rsc: TestRsc,
|
||||
area: WeakWidget<ScrollArea>,
|
||||
@@ -180,8 +148,6 @@ mod tests {
|
||||
self.rsc.ui.widgets.get(&self.area).unwrap().amt()
|
||||
}
|
||||
|
||||
/// One frame of a fling, the way `UiData::tick_animations` drives
|
||||
/// it: tick, then draw. Answers whether it is still going.
|
||||
fn fling_frame(&mut self, now: Instant) -> bool {
|
||||
let still = self.get().tick(now);
|
||||
self.draw();
|
||||
@@ -189,17 +155,13 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// One frame of a touch gesture, followed by the draw that spends it.
|
||||
fn press(f: &mut Fixture, id: WidgetId, sense: CursorSense, y: f32, t: Instant) {
|
||||
drag(f, id, sense, Vec2::new(0.0, y), t);
|
||||
}
|
||||
|
||||
/// The same, for a gesture whose position is not on the Y axis.
|
||||
fn drag(f: &mut Fixture, id: WidgetId, sense: CursorSense, pos: Vec2, t: Instant) {
|
||||
let pointer = PointerRequests::default();
|
||||
let flung = f.get().drag(&pointer, id, sense, pos, t);
|
||||
// What `WidgetLike::scrollable`'s own handler does with the
|
||||
// answer, and the half a fling does not move without.
|
||||
if flung {
|
||||
let id = f.area.id();
|
||||
f.rsc.ui.animate(id);
|
||||
@@ -218,8 +180,6 @@ mod tests {
|
||||
0.0,
|
||||
t,
|
||||
);
|
||||
// Finger down by well past the slop: the content follows it down,
|
||||
// which for this widget means *less* `amt`.
|
||||
press(
|
||||
&mut f,
|
||||
id,
|
||||
@@ -232,7 +192,6 @@ mod tests {
|
||||
"expected the 30px past the slop to be applied downward, got amt={}",
|
||||
f.amt()
|
||||
);
|
||||
// ...and the next frame's motion is a plain per-frame delta.
|
||||
press(
|
||||
&mut f,
|
||||
id,
|
||||
@@ -243,9 +202,6 @@ mod tests {
|
||||
assert!((f.amt() - 350.0).abs() < 0.01, "amt={}", f.amt());
|
||||
}
|
||||
|
||||
/// The half the change had no reason to touch: a press that never
|
||||
/// leaves the slop is a tap, and must move nothing at all -- otherwise
|
||||
/// every tap on a scrollable field nudges its text.
|
||||
#[test]
|
||||
fn a_press_that_stays_inside_the_slop_does_not_scroll() {
|
||||
let (mut f, id) = area();
|
||||
@@ -280,8 +236,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// A horizontal drag is not this widget's gesture: it must stay put
|
||||
/// rather than pick up the vertical noise in a sideways swipe.
|
||||
#[test]
|
||||
fn a_horizontal_drag_does_not_scroll() {
|
||||
let (mut f, id) = area();
|
||||
@@ -303,9 +257,6 @@ mod tests {
|
||||
assert!((f.amt() - 400.0).abs() < 0.01, "amt={}", f.amt());
|
||||
}
|
||||
|
||||
/// Panning stops at the ends of the content rather than running off,
|
||||
/// which is `update_amt`'s clamp -- checked through `drag` so the two
|
||||
/// cannot drift apart.
|
||||
#[test]
|
||||
fn a_pan_past_the_end_clamps_instead_of_running_off() {
|
||||
let (mut f, id) = area();
|
||||
@@ -327,10 +278,6 @@ mod tests {
|
||||
assert!((f.amt() - 0.0).abs() < 0.01, "amt={}", f.amt());
|
||||
}
|
||||
|
||||
/// Iris, 2026-09-08: "Flinging doesn't work in horizontal scroll
|
||||
/// areas. Flinging should be enabled by default in all scroll areas
|
||||
/// on android to match composes behavior." A release with real
|
||||
/// velocity coasts, decelerating, and settles on its own.
|
||||
#[test]
|
||||
fn a_released_pan_flings_and_settles() {
|
||||
for axis in [Axis::X, Axis::Y] {
|
||||
@@ -345,9 +292,6 @@ mod tests {
|
||||
at(0.0),
|
||||
t,
|
||||
);
|
||||
// Four samples 8ms apart, accelerating away from the start --
|
||||
// three is the fewest `VelocityTracker`'s quadratic fit can
|
||||
// use, so this is a gesture that genuinely has a velocity.
|
||||
for (i, d) in [-40.0, -100.0, -180.0, -280.0].into_iter().enumerate() {
|
||||
drag(
|
||||
&mut f,
|
||||
@@ -370,9 +314,6 @@ mod tests {
|
||||
"{axis:?}: a released pan with velocity must fling"
|
||||
);
|
||||
|
||||
// Frames at 8ms until it stops, with each step no longer than
|
||||
// the one before it -- a coast that does not decelerate is
|
||||
// the linear-spline bug this crate has had once already.
|
||||
let mut last_step = f32::INFINITY;
|
||||
let mut ticks = 0;
|
||||
let mut now = t + Duration::from_millis(32);
|
||||
@@ -397,15 +338,8 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// The wall: a fling must not spend its remaining distance on content
|
||||
/// that is not there. Released hard toward the start, it settles
|
||||
/// exactly on it.
|
||||
#[test]
|
||||
fn a_fling_stops_at_the_end_of_the_content() {
|
||||
// Both walls. A positive delta is applied as `amt -= delta`, so a
|
||||
// positive velocity runs toward the start of the content and a
|
||||
// negative one toward its end; 1000px of content in a 100px box
|
||||
// leaves `amt` in 0..=900.
|
||||
for (velocity, wall) in [(50_000.0f32, 0.0f32), (-50_000.0, 900.0)] {
|
||||
let (mut f, _id) = area();
|
||||
f.get().fling(velocity);
|
||||
@@ -429,10 +363,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// A finger on coasting content stops it there, from the first
|
||||
/// sample, with no `DRAG_SLOP` to wait out -- the catch
|
||||
/// `DragArbiter::press_start` describes, which a scroll area needs
|
||||
/// for the same reason a list does now that it can coast at all.
|
||||
#[test]
|
||||
fn a_press_on_a_coasting_area_catches_it() {
|
||||
let (mut f, id) = area();
|
||||
@@ -457,8 +387,6 @@ mod tests {
|
||||
"the down itself must not move the content, only stop it"
|
||||
);
|
||||
|
||||
// A move well under `DRAG_SLOP` still tracks the finger, because
|
||||
// this press caught something that was moving.
|
||||
drag(
|
||||
&mut f,
|
||||
id,
|
||||
|
||||
Reference in new issue
Block a user