Skip to main content

agpu/layout/
scroll.rs

1//! Scroll container — overflow handling with virtual scroll.
2
3use crate::core::Rect;
4use crate::layout::{DesiredSize, Layout, SizeConstraints};
5
6/// Which axes can scroll.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum ScrollDirection {
9    Vertical,
10    Horizontal,
11    Both,
12}
13
14/// A scroll container that offsets content and supports virtual scrolling.
15pub struct ScrollContainer {
16    direction: ScrollDirection,
17    offset_x: f32,
18    offset_y: f32,
19}
20
21impl ScrollContainer {
22    pub fn new(direction: ScrollDirection) -> Self {
23        Self {
24            direction,
25            offset_x: 0.0,
26            offset_y: 0.0,
27        }
28    }
29
30    /// Set scroll offset (combined x/y based on direction).
31    pub fn offset(mut self, offset: f32) -> Self {
32        match self.direction {
33            ScrollDirection::Vertical => self.offset_y = offset,
34            ScrollDirection::Horizontal => self.offset_x = offset,
35            ScrollDirection::Both => {
36                self.offset_x = offset;
37                self.offset_y = offset;
38            }
39        }
40        self
41    }
42
43    pub fn offset_xy(mut self, x: f32, y: f32) -> Self {
44        self.offset_x = x;
45        self.offset_y = y;
46        self
47    }
48
49    /// The viewport rect (visible portion) given the area.
50    pub fn viewport(&self, area: Rect) -> Rect {
51        area
52    }
53
54    /// Maximum scroll offset given content and viewport sizes.
55    pub fn max_offset(&self, content: DesiredSize, viewport: Rect) -> (f32, f32) {
56        let max_x = (content.width - viewport.width).max(0.0);
57        let max_y = (content.height - viewport.height).max(0.0);
58        (max_x, max_y)
59    }
60}
61
62impl Layout for ScrollContainer {
63    fn measure(&self, children: &[DesiredSize], constraints: SizeConstraints) -> DesiredSize {
64        // Content size = union of all children (stacked vertically)
65        if children.is_empty() {
66            return DesiredSize::zero();
67        }
68        let width = children.iter().map(|c| c.width).fold(0.0f32, f32::max);
69        let height: f32 = children.iter().map(|c| c.height).sum();
70        let (w, h) = constraints.clamp(width, height);
71        DesiredSize::new(w, h)
72    }
73
74    fn arrange(&self, children: &[DesiredSize], area: Rect) -> Vec<Rect> {
75        let mut rects = Vec::with_capacity(children.len());
76        let mut y = area.y - self.offset_y;
77        for child in children {
78            let x = area.x - self.offset_x;
79            let w = match self.direction {
80                ScrollDirection::Horizontal | ScrollDirection::Both => child.width,
81                ScrollDirection::Vertical => child.width.max(area.width),
82            };
83            rects.push(Rect::new(x, y, w, child.height));
84            y += child.height;
85        }
86        rects
87    }
88}