Skip to main content

ui/scroll/
scroller.rs

1//! One scroll position, whichever element owns it.
2
3use gpui::{Bounds, ListState, Pixels, Point, ScrollHandle, point, px};
4
5/// What a bar, a follow or a drag region reads and moves: a scrolling div's
6/// [`ScrollHandle`] or a [`gpui::list`]'s [`ListState`], in the handle's terms
7/// — `offset` negative going down, `max_offset` the overflow.
8///
9/// A list counts only the items it has measured: one never laid out adds
10/// nothing to `max_offset`, so both readings grow as the list is scrolled
11/// through. A list's offset is vertical only.
12///
13/// `offset` never reads past `max_offset`. A div's wheel handler moves its
14/// handle past the end and the div clamps it when next laid out, so a raw
15/// read before that layout is where the content will not be drawn.
16#[derive(Clone)]
17pub enum Scroller {
18    Pane(ScrollHandle),
19    List(ListState),
20}
21
22impl Scroller {
23    /// The viewport as last laid out, in window coordinates.
24    pub fn bounds(&self) -> Bounds<Pixels> {
25        match self {
26            Self::Pane(handle) => handle.bounds(),
27            Self::List(state) => state.viewport_bounds(),
28        }
29    }
30
31    pub fn offset(&self) -> Point<Pixels> {
32        match self {
33            Self::Pane(handle) => {
34                let (offset, max) = (handle.offset(), handle.max_offset());
35                point(
36                    offset.x.clamp(-max.x, px(0.)),
37                    offset.y.clamp(-max.y, px(0.)),
38                )
39            }
40            Self::List(state) => {
41                let offset = state.scroll_px_offset_for_scrollbar();
42                point(offset.x, offset.y.max(-state.max_offset_for_scrollbar().y))
43            }
44        }
45    }
46
47    pub fn max_offset(&self) -> Point<Pixels> {
48        match self {
49            Self::Pane(handle) => handle.max_offset(),
50            Self::List(state) => state.max_offset_for_scrollbar(),
51        }
52    }
53
54    /// Takes effect at the next layout. A list clamps to what it has measured.
55    pub fn set_offset(&self, offset: Point<Pixels>) {
56        match self {
57            Self::Pane(handle) => handle.set_offset(offset),
58            Self::List(state) => state.set_offset_from_scrollbar(offset),
59        }
60    }
61
62    /// Past the last of the content. A list anchors on its last item and lays
63    /// out backwards from it, so the end is reached in one frame however much
64    /// of it is unmeasured.
65    pub fn scroll_to_end(&self) {
66        match self {
67            Self::Pane(handle) => {
68                handle.set_offset(point(handle.offset().x, -handle.max_offset().y))
69            }
70            Self::List(state) => state.scroll_to_end(),
71        }
72    }
73}
74
75impl From<ScrollHandle> for Scroller {
76    fn from(handle: ScrollHandle) -> Self {
77        Self::Pane(handle)
78    }
79}
80
81impl From<&ScrollHandle> for Scroller {
82    fn from(handle: &ScrollHandle) -> Self {
83        Self::Pane(handle.clone())
84    }
85}
86
87impl From<ListState> for Scroller {
88    fn from(state: ListState) -> Self {
89        Self::List(state)
90    }
91}
92
93impl From<&ListState> for Scroller {
94    fn from(state: &ListState) -> Self {
95        Self::List(state.clone())
96    }
97}