`UiScalar` was `Len` without the `leftover` weight, which is the separation canonical `main` already had as `Len` beside `LayoutLen` and this branch collapsed. It is needed back for the queued clamp: a cap may not contain a share, because a cap has to read the report a rule otherwise makes moot, and a share puts the container's division into the same equation -- two self-consistent assignments, which is the multiple-fixed-point failure generated seed 13 punished for orthogonal sizing. `min(report, cap)` is not a `LayoutLen` either: it is a sum of parts, and the smaller of two of them is not one. So `UiScalar` is `Len`, what was `Len` is `LayoutLen`, and the two say in their docs which is which: a `Len` is pixels plus a fraction of a box -- a position being the length from the box's start, which is why a span is two of them -- and a `LayoutLen` is a `Len` plus a claim only a container dividing its room can answer. `From<Len> for LayoutLen` is the one-way step between them. Names only; the shader's `UiScalar` is renamed with them. Checked: fmt, clippy, 105 tests, and `tabs`, `minimal`, `view`, `text` and `random` byte-identical at 1920x1200. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
101 lines
4.0 KiB
Rust
101 lines
4.0 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);
|
|
// Draw in the whole container only when its scrolling-axis length is
|
|
// not already known, then draw it at the scrolled offset.
|
|
let answer_len = match painter.known_len(&self.inner, self.axis, UiRegion::FULL) {
|
|
Some(len) => len,
|
|
None => painter.widget(&self.inner).size().axis(self.axis),
|
|
};
|
|
let content = answer_len.apply_leftover();
|
|
self.container_len = container_len;
|
|
self.content_len = content.to_px(container_len);
|
|
|
|
if self.snap_end {
|
|
self.amt = self.content_len - self.container_len;
|
|
}
|
|
self.update_amt();
|
|
let align = painter.alignment().axis(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 fixed_len = content.rel == Rel::ZERO;
|
|
if fixed_len && self.content_len <= self.container_len && align == AxisAlign::NEG {
|
|
painter.holds(self.axis, self.content_len..=Px::MAX);
|
|
} else if fixed_len && !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());
|
|
let mut region = UiRegion::FULL;
|
|
// 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;
|
|
if moved || self.content_len != self.container_len {
|
|
let offset = UiVec2::from_axis(
|
|
self.axis,
|
|
Len::from_parts(Rel::ZERO, anchor - self.amt),
|
|
Len::ZERO,
|
|
);
|
|
region = region.offset(offset);
|
|
region.axis_mut(self.axis).end = region.axis(self.axis).start.offset(self.content_len);
|
|
}
|
|
painter.widget_aligned(&self.inner, region, RegionAlign::NEAR);
|
|
// 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
|
|
}
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|