Skip to main content

agpu/layout/
responsive.rs

1//! Responsive layout — adapt layout to window size via breakpoints.
2
3use crate::core::Rect;
4use crate::layout::flex::FlexLayout;
5use crate::layout::{DesiredSize, Layout, SizeConstraints};
6
7/// A breakpoint defines a minimum width at which a layout activates.
8pub struct Breakpoint {
9    pub min_width: f32,
10    pub layout: FlexLayout,
11}
12
13impl Breakpoint {
14    pub fn new(min_width: f32, layout: FlexLayout) -> Self {
15        Self { min_width, layout }
16    }
17}
18
19/// Selects a layout based on the available width compared to breakpoints.
20///
21/// Breakpoints are checked from largest `min_width` to smallest.
22/// The first breakpoint whose `min_width <= area.width` is used.
23pub struct ResponsiveLayout {
24    breakpoints: Vec<Breakpoint>,
25}
26
27impl ResponsiveLayout {
28    pub fn new() -> Self {
29        Self {
30            breakpoints: Vec::new(),
31        }
32    }
33
34    pub fn breakpoint(mut self, bp: Breakpoint) -> Self {
35        self.breakpoints.push(bp);
36        // Keep sorted descending by min_width for matching
37        self.breakpoints
38            .sort_by(|a, b| b.min_width.partial_cmp(&a.min_width).unwrap());
39        self
40    }
41
42    fn active_layout(&self, width: f32) -> Option<&FlexLayout> {
43        self.breakpoints
44            .iter()
45            .find(|bp| width >= bp.min_width)
46            .map(|bp| &bp.layout)
47    }
48}
49
50impl Default for ResponsiveLayout {
51    fn default() -> Self {
52        Self::new()
53    }
54}
55
56impl Layout for ResponsiveLayout {
57    fn measure(&self, children: &[DesiredSize], constraints: SizeConstraints) -> DesiredSize {
58        // Use the smallest breakpoint's layout for measurement
59        if let Some(bp) = self.breakpoints.last() {
60            bp.layout.measure(children, constraints)
61        } else {
62            DesiredSize::zero()
63        }
64    }
65
66    fn arrange(&self, children: &[DesiredSize], area: Rect) -> Vec<Rect> {
67        if let Some(layout) = self.active_layout(area.width) {
68            layout.arrange(children, area)
69        } else {
70            // Fallback: stack vertically
71            let mut rects = Vec::with_capacity(children.len());
72            let mut y = area.y;
73            for child in children {
74                rects.push(Rect::new(area.x, y, child.width, child.height));
75                y += child.height;
76            }
77            rects
78        }
79    }
80}