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}