1#[derive(Clone, Copy, Debug, Default, PartialEq)]
16pub struct CornerInset {
17 pub width: f64,
18 pub height: f64,
19}
20
21impl CornerInset {
22 pub const ZERO: CornerInset = CornerInset {
24 width: 0.0,
25 height: 0.0,
26 };
27
28 pub const fn new(width: f64, height: f64) -> Self {
29 Self { width, height }
30 }
31}
32
33#[derive(Clone, Copy, Debug, Default, PartialEq)]
46pub struct CornerInsets {
47 pub top_left: CornerInset,
48 pub top_right: CornerInset,
49 pub bottom_left: CornerInset,
50 pub bottom_right: CornerInset,
51}
52
53impl CornerInsets {
54 pub const ZERO: CornerInsets = CornerInsets {
56 top_left: CornerInset::ZERO,
57 top_right: CornerInset::ZERO,
58 bottom_left: CornerInset::ZERO,
59 bottom_right: CornerInset::ZERO,
60 };
61
62 pub const fn new(
63 top_left: CornerInset,
64 top_right: CornerInset,
65 bottom_left: CornerInset,
66 bottom_right: CornerInset,
67 ) -> Self {
68 Self {
69 top_left,
70 top_right,
71 bottom_left,
72 bottom_right,
73 }
74 }
75}
76
77#[derive(Clone, Copy, Debug, Default, PartialEq)]
80pub struct EdgeInsets {
81 pub left: f64,
82 pub top: f64,
83 pub right: f64,
84 pub bottom: f64,
85}
86
87impl EdgeInsets {
88 pub const ZERO: EdgeInsets = EdgeInsets {
90 left: 0.0,
91 top: 0.0,
92 right: 0.0,
93 bottom: 0.0,
94 };
95
96 pub fn new(left: f64, top: f64, right: f64, bottom: f64) -> Self {
97 Self {
98 left,
99 top,
100 right,
101 bottom,
102 }
103 }
104
105 #[must_use]
108 pub fn max(self, other: EdgeInsets) -> EdgeInsets {
109 EdgeInsets {
110 left: self.left.max(other.left),
111 top: self.top.max(other.top),
112 right: self.right.max(other.right),
113 bottom: self.bottom.max(other.bottom),
114 }
115 }
116
117 #[must_use]
119 pub fn saturating_sub(self, other: EdgeInsets) -> EdgeInsets {
120 EdgeInsets {
121 left: (self.left - other.left).max(0.0),
122 top: (self.top - other.top).max(0.0),
123 right: (self.right - other.right).max(0.0),
124 bottom: (self.bottom - other.bottom).max(0.0),
125 }
126 }
127}
128
129#[derive(Clone, Copy, Debug, Default, PartialEq)]
132pub struct WindowInsets {
133 pub view_padding: EdgeInsets,
135 pub view_insets: EdgeInsets,
137 pub corner_insets: CornerInsets,
138}
139
140impl WindowInsets {
141 pub fn new(view_padding: EdgeInsets, view_insets: EdgeInsets) -> Self {
143 Self {
144 view_padding,
145 view_insets,
146 corner_insets: CornerInsets::ZERO,
147 }
148 }
149
150 #[must_use]
151 pub fn with_corner_insets(mut self, corner_insets: CornerInsets) -> Self {
152 self.corner_insets = corner_insets;
153 self
154 }
155
156 pub fn padding(&self) -> EdgeInsets {
159 self.view_padding.saturating_sub(self.view_insets)
160 }
161
162 #[must_use]
166 pub fn consuming(self, left: bool, top: bool, right: bool, bottom: bool) -> WindowInsets {
167 let padding = self.padding();
168 let consume = |enabled: bool, view_padding: f64, padding: f64| {
169 if enabled {
170 (view_padding - padding).max(0.0)
171 } else {
172 view_padding
173 }
174 };
175 WindowInsets {
176 view_padding: EdgeInsets {
177 left: consume(left, self.view_padding.left, padding.left),
178 top: consume(top, self.view_padding.top, padding.top),
179 right: consume(right, self.view_padding.right, padding.right),
180 bottom: consume(bottom, self.view_padding.bottom, padding.bottom),
181 },
182 view_insets: self.view_insets,
183 corner_insets: self.corner_insets,
184 }
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191
192 fn sample_corners() -> CornerInsets {
193 CornerInsets::new(
194 CornerInset::new(0.0, 0.0),
195 CornerInset::new(72.0, 24.0),
196 CornerInset::new(1.0, 2.0),
197 CornerInset::new(3.0, 4.0),
198 )
199 }
200
201 #[test]
202 fn corner_insets_default_is_zero() {
203 assert_eq!(CornerInsets::default(), CornerInsets::ZERO);
204 assert_eq!(CornerInset::default(), CornerInset::ZERO);
205 assert_eq!(CornerInset::ZERO, CornerInset::new(0.0, 0.0));
206 }
207
208 #[test]
209 fn window_insets_new_has_zero_corners() {
210 let w = WindowInsets::new(EdgeInsets::new(0.0, 24.0, 0.0, 0.0), EdgeInsets::ZERO);
211 assert_eq!(w.corner_insets, CornerInsets::ZERO);
212 assert_eq!(WindowInsets::default().corner_insets, CornerInsets::ZERO);
213 }
214
215 #[test]
216 fn with_corner_insets_sets_corners_and_keeps_padding() {
217 let base = WindowInsets::new(
218 EdgeInsets::new(0.0, 24.0, 0.0, 34.0),
219 EdgeInsets::new(0.0, 0.0, 0.0, 10.0),
220 );
221 let with = base.with_corner_insets(sample_corners());
222 assert_eq!(with.corner_insets, sample_corners());
223 assert_eq!(with.padding(), base.padding());
224 assert_eq!(with.view_padding, base.view_padding);
225 assert_eq!(with.view_insets, base.view_insets);
226 }
227
228 #[test]
229 fn consuming_leaves_corner_insets_untouched() {
230 let w = WindowInsets::new(EdgeInsets::new(5.0, 24.0, 6.0, 34.0), EdgeInsets::ZERO)
231 .with_corner_insets(sample_corners());
232 let c = w.consuming(true, true, true, true);
233 assert_eq!(c.corner_insets, sample_corners());
234 assert_eq!(c.padding(), EdgeInsets::ZERO);
235 }
236
237 #[test]
238 fn edge_insets_zero_is_all_zero() {
239 assert_eq!(EdgeInsets::ZERO, EdgeInsets::new(0.0, 0.0, 0.0, 0.0));
240 assert_eq!(EdgeInsets::ZERO, EdgeInsets::default());
241 }
242
243 #[test]
244 fn edge_insets_max_is_per_edge() {
245 let a = EdgeInsets::new(10.0, 0.0, 5.0, 30.0);
246 let b = EdgeInsets::new(0.0, 24.0, 8.0, 20.0);
247 assert_eq!(a.max(b), EdgeInsets::new(10.0, 24.0, 8.0, 30.0));
248 }
249
250 #[test]
251 fn edge_insets_saturating_sub_clamps_to_zero() {
252 let padding = EdgeInsets::new(10.0, 24.0, 10.0, 34.0);
253 let ime = EdgeInsets::new(0.0, 0.0, 0.0, 300.0);
255 assert_eq!(
256 padding.saturating_sub(ime),
257 EdgeInsets::new(10.0, 24.0, 10.0, 0.0),
258 "bottom clamps to 0, others untouched"
259 );
260 }
261
262 #[test]
263 fn window_insets_padding_is_flutter_formula() {
264 let insets = WindowInsets::new(
266 EdgeInsets::new(0.0, 24.0, 0.0, 34.0),
267 EdgeInsets::new(0.0, 0.0, 0.0, 340.0),
268 );
269 assert_eq!(insets.padding(), EdgeInsets::new(0.0, 24.0, 0.0, 0.0));
272 }
273
274 #[test]
275 fn window_insets_default_is_zero_padding() {
276 assert_eq!(WindowInsets::default().padding(), EdgeInsets::ZERO);
277 }
278
279 #[test]
280 fn consuming_all_edges_zeroes_padding_and_keeps_view_insets() {
281 let insets = WindowInsets::new(
282 EdgeInsets::new(10.0, 20.0, 30.0, 40.0),
283 EdgeInsets::new(0.0, 0.0, 0.0, 15.0),
284 );
285 let consumed = insets.consuming(true, true, true, true);
286 assert_eq!(consumed.padding(), EdgeInsets::ZERO);
287 assert_eq!(consumed.view_padding, EdgeInsets::new(0.0, 0.0, 0.0, 15.0));
290 assert_eq!(consumed.view_insets, insets.view_insets);
291 }
292
293 #[test]
294 fn consuming_bottom_only_leaves_other_edges_visible() {
295 let insets = WindowInsets::new(EdgeInsets::new(10.0, 20.0, 30.0, 40.0), EdgeInsets::ZERO);
296 let consumed = insets.consuming(false, false, false, true);
297 assert_eq!(consumed.padding(), EdgeInsets::new(10.0, 20.0, 30.0, 0.0));
298 assert_eq!(
299 consumed.view_padding,
300 EdgeInsets::new(10.0, 20.0, 30.0, 0.0)
301 );
302 assert_eq!(consumed.view_insets, EdgeInsets::ZERO);
303 }
304
305 #[test]
306 fn consuming_an_ime_covered_edge_changes_nothing() {
307 let insets = WindowInsets::new(
310 EdgeInsets::new(0.0, 24.0, 0.0, 40.0),
311 EdgeInsets::new(0.0, 0.0, 0.0, 300.0),
312 );
313 assert_eq!(insets.padding().bottom, 0.0);
314 let consumed = insets.consuming(false, false, false, true);
315 assert_eq!(consumed, insets);
316 assert_eq!(consumed.padding().bottom, 0.0);
317 assert_eq!(consumed.view_insets.bottom, 300.0);
318 }
319
320 #[test]
321 fn consuming_is_idempotent() {
322 let insets = WindowInsets::new(
323 EdgeInsets::new(10.0, 20.0, 30.0, 40.0),
324 EdgeInsets::new(5.0, 0.0, 0.0, 60.0),
325 );
326 for edges in [
327 (true, true, true, true),
328 (true, false, true, false),
329 (false, true, false, true),
330 (false, false, false, false),
331 ] {
332 let (l, t, r, b) = edges;
333 let once = insets.consuming(l, t, r, b);
334 assert_eq!(once.consuming(l, t, r, b), once, "edges {edges:?}");
335 }
336 assert_eq!(insets.consuming(false, false, false, false), insets);
337 }
338}