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); // Measured in the whole viewport, then drawn at the scrolled offset. let whole = UiRegion::FULL; let answer_len = painter .widget_at(&self.inner, whole, [Place::Fill(Part::All); 2]) .len(self.axis); let fixed = Len::from_parts(answer_len.rel, answer_len.px).to_px(container_len); self.container_len = container_len; self.content_len = fixed.max(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 = answer_len.rel == Rel::ZERO && answer_len.leftover == Weight::ZERO; if fixed_len && self.content_len <= self.container_len && align == AxisAlign::NEG { painter.holds(self.axis, fixed..=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 content = UiSpan::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 start = Len::from_parts(Rel::ZERO, anchor - self.amt); content = UiSpan::new(start, start.offset(self.content_len)); } // The viewport is the inner's frame, 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 is drawn is the // content box, scrolled. painter.widget_at( &self.inner, whole, self.axis .pair(Place::Fill(Part::From(content)), Place::Fill(Part::All)), ); // 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(); } }