Skip to main content

agpu/layout/
constraint.rs

1//! Constraint-based layout — auto-layout with min/max size constraints per child.
2
3use crate::core::Rect;
4use crate::layout::{DesiredSize, Layout, SizeConstraints};
5
6/// A min/max range for a single dimension.
7#[derive(Debug, Clone, Copy)]
8pub struct MinMax {
9    pub min: f32,
10    pub max: f32,
11}
12
13impl MinMax {
14    pub fn new(min: f32, max: f32) -> Self {
15        Self { min, max }
16    }
17
18    pub fn clamp(&self, value: f32) -> f32 {
19        value.clamp(self.min, self.max)
20    }
21}
22
23/// Constraint rule for a specific child by index.
24#[derive(Debug, Clone)]
25pub struct ConstraintRule {
26    pub index: usize,
27    pub width: Option<MinMax>,
28    pub height: Option<MinMax>,
29}
30
31impl ConstraintRule {
32    pub fn new(index: usize) -> Self {
33        Self {
34            index,
35            width: None,
36            height: None,
37        }
38    }
39
40    pub fn width(mut self, constraint: MinMax) -> Self {
41        self.width = Some(constraint);
42        self
43    }
44
45    pub fn height(mut self, constraint: MinMax) -> Self {
46        self.height = Some(constraint);
47        self
48    }
49}
50
51/// Layout that applies min/max constraints to each child.
52pub struct ConstraintLayout {
53    rules: Vec<ConstraintRule>,
54}
55
56impl ConstraintLayout {
57    pub fn new() -> Self {
58        Self { rules: Vec::new() }
59    }
60
61    pub fn rule(mut self, rule: ConstraintRule) -> Self {
62        self.rules.push(rule);
63        self
64    }
65
66    fn constrain(&self, index: usize, width: f32, height: f32) -> (f32, f32) {
67        if let Some(rule) = self.rules.iter().find(|r| r.index == index) {
68            let w = rule.width.as_ref().map_or(width, |c| c.clamp(width));
69            let h = rule.height.as_ref().map_or(height, |c| c.clamp(height));
70            (w, h)
71        } else {
72            (width, height)
73        }
74    }
75}
76
77impl Default for ConstraintLayout {
78    fn default() -> Self {
79        Self::new()
80    }
81}
82
83impl Layout for ConstraintLayout {
84    fn measure(&self, children: &[DesiredSize], constraints: SizeConstraints) -> DesiredSize {
85        if children.is_empty() {
86            return DesiredSize::zero();
87        }
88        // Stack vertically, apply constraints
89        let mut total_h = 0.0f32;
90        let mut max_w = 0.0f32;
91        for (i, child) in children.iter().enumerate() {
92            let (w, h) = self.constrain(i, child.width, child.height);
93            max_w = max_w.max(w);
94            total_h += h;
95        }
96        let (w, h) = constraints.clamp(max_w, total_h);
97        DesiredSize::new(w, h)
98    }
99
100    fn arrange(&self, children: &[DesiredSize], area: Rect) -> Vec<Rect> {
101        let mut rects = Vec::with_capacity(children.len());
102        let mut y = area.y;
103        for (i, child) in children.iter().enumerate() {
104            let (w, h) = self.constrain(i, child.width, child.height);
105            rects.push(Rect::new(area.x, y, w, h));
106            y += h;
107        }
108        rects
109    }
110}