Files
iris/src/widget/position/scroll.rs
T
iris-ai 3da1c71870 Name the values layout carries, and say what a span's slot is
`along` said nothing about what it did. It is `Span::slot` now: the
stretch of the row between two distances from where the span starts
laying out, as a span of its own box, with the mirror for a negative
direction in one place. `far` is `row`, which is what the comment above
it already called it, and `shares` is `has_room` beside the
`any_leftover` it was folded into. `reached` now guards on the leftover
weight it divides by rather than on the numerator that happened to be
zero with it.

The pairs layout returns are named rather than positional: `Answer`
{size, holds} and `Drawn` {answer, drawing_holds} replace
`(Size, LayoutHolds)` and a three-tuple with two `LayoutHolds` in it,
which was the one shape the cold dump exists to catch. `try_reuse`
answers `bool` rather than `Option<()>`, and the four hand-written
copies of `move_idx != parent_move` are `ActiveData::is_region_node`.

`AXES` was declared in three modules; it is `Axis::BOTH`. `rel_min`,
`rel_max` and the unused `select_len` are gone -- `ZERO` and `FULL`
already said those. Three doc comments sat on `impl` blocks instead of
the single method inside them. `reposition` and `redepth` walked their
children by index, looking the parent up again per child; both take the
list and put it back. `Scroll`'s `fixed` and `fixed_len` are
`answer_px` and `answer_is_px`, which says which one is the length.

fmt, workspace clippy under `-D warnings` with and without
`layout-diagnostics`, and the workspace tests are clean. The cold dump
over 400 depth-5 trees is byte-identical to `6c84b6f`: 34,492 boxes,
no seed moved.
2026-09-19 23:24:55 -04:00

104 lines
4.2 KiB
Rust

use crate::prelude::*;
pub struct Scroll {
inner: StrongWidget,
axis: Axis,
amt: Px,
snap_end: bool,
container_len: Px,
content_len: Px,
}
impl Widget for Scroll {
fn draw(&mut self, painter: &mut Painter) -> Size {
let container_len = painter.px_len(self.axis);
// Asked in the whole viewport, then put at the scrolled offset.
let answer_len = painter
.widget_at(&self.inner, PlaceDesc::WHOLE.fills())
.len(self.axis);
let answer_px = painter.to_px(answer_len.without_leftover(), self.axis);
self.container_len = container_len;
self.content_len = answer_px.max(container_len);
if self.snap_end {
self.amt = self.content_len - self.container_len;
}
self.update_amt();
let align = painter.alignment()[self.axis];
// Content of a fixed length that fits sits at the start of any box it
// fits in -- but only anchored there. Anywhere else it is a part of
// the room left over, so it moves with every length the box takes and
// the drawing holds for that length alone. One scrolled part way sits
// where it is until the box shrinks past what is left of it. Kept to
// the end, it moves with every length.
let answer_is_px = answer_len.is_px();
if answer_is_px && self.content_len <= self.container_len && align == AxisAlign::NEG {
painter.holds(self.axis, answer_px..=Px::MAX);
} else if answer_is_px && !self.snap_end {
let left = self.content_len - self.amt;
painter.holds(self.axis, Px::MIN..=left);
}
// Content shorter than the viewport has room to sit in, and where it
// sits is this widget's own alignment -- the same property that would
// have placed the whole scroll in a box longer than it.
let slack = (self.container_len - self.content_len).max(Px::ZERO);
let anchor = slack.mul(align.rel());
// Content that fills the viewport and has not been scrolled is the
// viewport, and is handed back as it came. Writing the same box as
// its own length in pixels is the same box in another form, and the
// two do not round alike: a part centred in `rel 1` lands a step from
// one centred in `px 900`, since halving a difference is not halving
// each part of it.
let moved = anchor != Px::ZERO || self.amt != Px::ZERO;
let content = match moved || self.content_len != self.container_len {
true => {
let start = Len::from_parts(Rel::ZERO, anchor - self.amt);
UiSpan::new(start, start.offset(self.content_len)).shifted_desc()
}
false => PlaceDescAxis::WHOLE,
};
// The viewport is the inner's rel base, so a fraction it declares or
// reports is a fraction of what is on screen rather than of the
// content box its own answer decided. Where it goes is the content
// box, scrolled: its drawing moved there, not made again there.
painter.place_at(&self.inner, content.on_axis(self.axis).fills());
// What it occupies is its box, on both axes: it clips its content to
// that box, so it can neither take less of one nor honestly ask for
// more. The content's length is what it scrolls through, not what it
// is.
Size::LEFTOVER
}
fn size_hint(&self, _: Axis) -> Option<LayoutLen> {
Some(LayoutLen::LEFTOVER)
}
}
impl Scroll {
pub fn new(inner: StrongWidget, axis: Axis) -> Self {
Self {
inner,
axis,
amt: Px::ZERO,
snap_end: true,
container_len: Px::ZERO,
content_len: Px::ZERO,
}
}
pub fn update_amt(&mut self) {
self.amt = self.amt.max(Px::ZERO);
let len = (self.content_len - self.container_len).max(Px::ZERO);
self.amt = self.amt.min(len);
self.snap_end = self.amt == len;
}
/// Scrolled by a distance the platform measures, which is the last place
/// a wheel notch or a finger is a float.
pub fn scroll(&mut self, amt: f32) {
self.amt -= Px::from_f32(amt);
self.update_amt();
}
}