corner_solver.rs

  1use gpui::Pixels;
  2
  3/// Calculates corner radii for nested elements in both directions.
  4///
  5/// ## Forward calculation (parent → child)
  6/// Given a parent's corner radius, calculates the child's corner radius:
  7/// ```
  8/// child_radius = max(0, parent_radius - parent_border - parent_padding + child_border)
  9/// ```
 10///
 11/// ## Inverse calculation (child → parent)
 12/// Given a child's desired corner radius, calculates the required parent radius:
 13/// ```
 14/// parent_radius = child_radius + parent_border + parent_padding - child_border
 15/// ```
 16pub struct CornerSolver;
 17
 18impl CornerSolver {
 19    /// Calculates the child's corner radius given the parent's properties.
 20    ///
 21    /// # Arguments
 22    /// - `parent_radius`: Outer corner radius of the parent element
 23    /// - `parent_border`: Border width of the parent element
 24    /// - `parent_padding`: Padding of the parent element
 25    /// - `child_border`: Border width of the child element
 26    pub fn child_radius(
 27        parent_radius: Pixels,
 28        parent_border: Pixels,
 29        parent_padding: Pixels,
 30        child_border: Pixels,
 31    ) -> Pixels {
 32        (parent_radius - parent_border - parent_padding + child_border).max(Pixels::ZERO)
 33    }
 34
 35    /// Calculates the required parent radius to achieve a desired child radius.
 36    ///
 37    /// # Arguments
 38    /// - `child_radius`: Desired corner radius for the child element
 39    /// - `parent_border`: Border width of the parent element
 40    /// - `parent_padding`: Padding of the parent element
 41    /// - `child_border`: Border width of the child element
 42    pub fn parent_radius(
 43        child_radius: Pixels,
 44        parent_border: Pixels,
 45        parent_padding: Pixels,
 46        child_border: Pixels,
 47    ) -> Pixels {
 48        child_radius + parent_border + parent_padding - child_border
 49    }
 50}
 51
 52/// Builder for calculating corner radii across multiple nested levels.
 53pub struct NestedCornerSolver {
 54    levels: Vec<Level>,
 55}
 56
 57#[derive(Debug, Clone, Copy)]
 58struct Level {
 59    border: Pixels,
 60    padding: Pixels,
 61}
 62
 63impl NestedCornerSolver {
 64    /// Creates a new nested corner solver.
 65    pub fn new() -> Self {
 66        Self { levels: Vec::new() }
 67    }
 68
 69    /// Adds a level to the nesting hierarchy.
 70    ///
 71    /// Levels should be added from outermost to innermost.
 72    pub fn add_level(mut self, border: Pixels, padding: Pixels) -> Self {
 73        self.levels.push(Level { border, padding });
 74        self
 75    }
 76
 77    /// Calculates the corner radius at a specific nesting level given the root radius.
 78    ///
 79    /// # Arguments
 80    /// - `root_radius`: The outermost corner radius
 81    /// - `level`: The nesting level (0 = first child of root, 1 = grandchild, etc.)
 82    pub fn radius_at_level(&self, root_radius: Pixels, level: usize) -> Pixels {
 83        let mut radius = root_radius;
 84
 85        for i in 0..=level.min(self.levels.len().saturating_sub(1)) {
 86            let current_level = &self.levels[i];
 87            let next_border = if i < self.levels.len() - 1 {
 88                self.levels[i + 1].border
 89            } else {
 90                Pixels::ZERO
 91            };
 92
 93            radius = CornerSolver::child_radius(
 94                radius,
 95                current_level.border,
 96                current_level.padding,
 97                next_border,
 98            );
 99        }
100
101        radius
102    }
103
104    /// Calculates the required root radius to achieve a desired radius at a specific level.
105    ///
106    /// # Arguments
107    /// - `target_radius`: The desired corner radius at the target level
108    /// - `target_level`: The nesting level where the target radius should be achieved
109    pub fn root_radius_for_level(&self, target_radius: Pixels, target_level: usize) -> Pixels {
110        if target_level >= self.levels.len() {
111            return target_radius;
112        }
113
114        let mut radius = target_radius;
115
116        // Work backwards from the target level to the root
117        for i in (0..=target_level).rev() {
118            let current_level = &self.levels[i];
119            let child_border = if i < self.levels.len() - 1 {
120                self.levels[i + 1].border
121            } else {
122                Pixels::ZERO
123            };
124
125            radius = CornerSolver::parent_radius(
126                radius,
127                current_level.border,
128                current_level.padding,
129                child_border,
130            );
131        }
132
133        radius
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140
141    #[test]
142    fn test_forward_calculation() {
143        let parent_radius = Pixels(20.0);
144        let parent_border = Pixels(2.0);
145        let parent_padding = Pixels(8.0);
146        let child_border = Pixels(1.0);
147
148        let child_radius =
149            CornerSolver::child_radius(parent_radius, parent_border, parent_padding, child_border);
150
151        assert_eq!(child_radius, Pixels(11.0)); // 20 - 2 - 8 + 1 = 11
152    }
153
154    #[test]
155    fn test_inverse_calculation() {
156        let child_radius = Pixels(11.0);
157        let parent_border = Pixels(2.0);
158        let parent_padding = Pixels(8.0);
159        let child_border = Pixels(1.0);
160
161        let parent_radius =
162            CornerSolver::parent_radius(child_radius, parent_border, parent_padding, child_border);
163
164        assert_eq!(parent_radius, Pixels(20.0)); // 11 + 2 + 8 - 1 = 20
165    }
166
167    #[test]
168    fn test_nested_forward() {
169        let solver = NestedCornerSolver::new()
170            .add_level(Pixels(2.0), Pixels(8.0)) // Root level
171            .add_level(Pixels(1.0), Pixels(4.0)) // First child
172            .add_level(Pixels(1.0), Pixels(2.0)); // Second child
173
174        let root_radius = Pixels(20.0);
175
176        assert_eq!(solver.radius_at_level(root_radius, 0), Pixels(11.0)); // 20 - 2 - 8 + 1
177        assert_eq!(solver.radius_at_level(root_radius, 1), Pixels(7.0)); // 11 - 1 - 4 + 1
178        assert_eq!(solver.radius_at_level(root_radius, 2), Pixels(4.0)); // 7 - 1 - 2 + 0
179    }
180
181    #[test]
182    fn test_nested_inverse() {
183        let solver = NestedCornerSolver::new()
184            .add_level(Pixels(2.0), Pixels(8.0)) // Root level
185            .add_level(Pixels(1.0), Pixels(4.0)) // First child
186            .add_level(Pixels(1.0), Pixels(2.0)); // Second child
187
188        let target_radius = Pixels(4.0);
189        let root_radius = solver.root_radius_for_level(target_radius, 2);
190
191        assert_eq!(root_radius, Pixels(20.0));
192
193        // Verify by calculating forward
194        assert_eq!(solver.radius_at_level(root_radius, 2), target_radius);
195    }
196}