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(); // Reading the box in pixels above holds this drawing to that one // length, so these two say where it holds more widely. // // Content of a fixed length that fits is handed the whole box below, // and nothing here reads the box again, so every longer box gives the // same drawing: it holds from the length the content needs upwards, // and shrinking past that is what changes it. Where it sits in a box // longer than itself is not this widget's to say -- placing its // answer in the whole box is its own alignment, and that placement is // a fraction of the box, so it holds at every length too. // // 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 { 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 that fills the viewport is the viewport, and is handed back // as it came -- it has nothing to scroll through, so the clamp above // has already put `amt` at zero. 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 content = match self.content_len > self.container_len { true => { let start = Len::from_parts(Rel::ZERO, -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 { 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(); } }