Skip to main content

sequoia_layout/
style.rs

1use crate::container::{Align, Axis};
2use crate::overflow::Overflow;
3use sequoia_core::{Edges, Length, Size};
4
5#[derive(Clone, Copy, Debug, Default, PartialEq)]
6#[non_exhaustive]
7pub enum Position {
8    #[default]
9    Flow,
10
11    Absolute(Inset),
12}
13
14#[derive(Clone, Copy, Debug, Default, PartialEq)]
15#[non_exhaustive]
16pub struct Inset {
17    pub left: Option<f32>,
18    pub top: Option<f32>,
19    pub right: Option<f32>,
20    pub bottom: Option<f32>,
21}
22
23impl Inset {
24    pub const AUTO: Self = Self {
25        left: None,
26        top: None,
27        right: None,
28        bottom: None,
29    };
30
31    pub fn xy(left: f32, top: f32) -> Self {
32        Self {
33            left: Some(left),
34            top: Some(top),
35            right: None,
36            bottom: None,
37        }
38    }
39
40    /// Builds an inset from each side, `None` meaning "auto".
41    pub const fn only(
42        left: Option<f32>,
43        top: Option<f32>,
44        right: Option<f32>,
45        bottom: Option<f32>,
46    ) -> Self {
47        Self {
48            left,
49            top,
50            right,
51            bottom,
52        }
53    }
54}
55
56#[derive(Clone, Copy, Debug, Default, PartialEq)]
57pub struct Indents {
58    pub left: Option<f32>,
59    pub top: Option<f32>,
60    pub right: Option<f32>,
61    pub bottom: Option<f32>,
62}
63
64impl Indents {
65    pub const NONE: Self = Self {
66        left: None,
67        top: None,
68        right: None,
69        bottom: None,
70    };
71
72    pub fn all(v: f32) -> Self {
73        let v = sanitize_indent(v);
74        Self {
75            left: Some(v),
76            top: Some(v),
77            right: Some(v),
78            bottom: Some(v),
79        }
80    }
81
82    pub fn xy(x: f32, y: f32) -> Self {
83        Self {
84            left: Some(sanitize_indent(x)),
85            top: Some(sanitize_indent(y)),
86            right: Some(sanitize_indent(x)),
87            bottom: Some(sanitize_indent(y)),
88        }
89    }
90
91    pub fn axes(horizontal: f32, vertical: f32) -> Self {
92        Self::xy(horizontal, vertical)
93    }
94
95    pub fn horizontal(v: f32) -> Self {
96        let v = sanitize_indent(v);
97        Self {
98            left: Some(v),
99            right: Some(v),
100            top: None,
101            bottom: None,
102        }
103    }
104
105    pub fn vertical(v: f32) -> Self {
106        let v = sanitize_indent(v);
107        Self {
108            left: None,
109            right: None,
110            top: Some(v),
111            bottom: Some(v),
112        }
113    }
114
115    pub const fn only(
116        left: Option<f32>,
117        top: Option<f32>,
118        right: Option<f32>,
119        bottom: Option<f32>,
120    ) -> Self {
121        Self {
122            left,
123            top,
124            right,
125            bottom,
126        }
127    }
128
129    pub fn is_none(self) -> bool {
130        self.left.is_none() && self.top.is_none() && self.right.is_none() && self.bottom.is_none()
131    }
132}
133
134#[derive(Clone, Copy, Debug, PartialEq)]
135pub struct LayoutStyle {
136    pub width: Length,
137    pub height: Length,
138    pub padding: Edges,
139
140    pub margin: Edges,
141    pub min_width: f32,
142    pub max_width: f32,
143    pub min_height: f32,
144    pub max_height: f32,
145
146    pub flex: f32,
147
148    pub flex_shrink: f32,
149
150    pub flex_basis: Option<Length>,
151
152    pub align_self: Option<Align>,
153
154    pub aspect_ratio: Option<f32>,
155
156    pub indent_top: Option<f32>,
157    pub indent_bottom: Option<f32>,
158    pub indent_left: Option<f32>,
159    pub indent_right: Option<f32>,
160
161    pub position: Position,
162    pub overflow: Overflow,
163
164    pub hit_testable: bool,
165}
166
167impl Default for LayoutStyle {
168    fn default() -> Self {
169        Self {
170            width: Length::Fit,
171            height: Length::Fit,
172            padding: Edges::ZERO,
173            margin: Edges::ZERO,
174            min_width: 0.0,
175            max_width: f32::INFINITY,
176            min_height: 0.0,
177            max_height: f32::INFINITY,
178            flex: 0.0,
179            flex_shrink: 1.0,
180            flex_basis: None,
181            align_self: None,
182            aspect_ratio: None,
183            indent_top: None,
184            indent_bottom: None,
185            indent_left: None,
186            indent_right: None,
187            position: Position::Flow,
188            overflow: Overflow::Visible,
189            hit_testable: true,
190        }
191    }
192}
193
194impl LayoutStyle {
195    pub fn width(mut self, width: Length) -> Self {
196        self.width = width;
197        self
198    }
199
200    pub fn height(mut self, height: Length) -> Self {
201        self.height = height;
202        self
203    }
204
205    pub fn fit(mut self) -> Self {
206        self.width = Length::Fit;
207        self.height = Length::Fit;
208        self
209    }
210
211    pub fn hit_testable(mut self, on: bool) -> Self {
212        self.hit_testable = on;
213        self
214    }
215
216    pub fn padding(mut self, padding: impl Into<Edges>) -> Self {
217        self.padding = padding.into();
218        self
219    }
220
221    pub fn pad(self, padding: impl Into<Edges>) -> Self {
222        self.padding(padding)
223    }
224
225    pub fn padding_xy(self, x: f32, y: f32) -> Self {
226        self.padding(Edges::xy(x, y))
227    }
228
229    pub fn padding_x(mut self, x: f32) -> Self {
230        self.padding.left = x;
231        self.padding.right = x;
232        self
233    }
234
235    pub fn padding_y(mut self, y: f32) -> Self {
236        self.padding.top = y;
237        self.padding.bottom = y;
238        self
239    }
240
241    pub fn padding_left(mut self, v: f32) -> Self {
242        self.padding.left = v;
243        self
244    }
245
246    pub fn padding_right(mut self, v: f32) -> Self {
247        self.padding.right = v;
248        self
249    }
250
251    pub fn padding_top(mut self, v: f32) -> Self {
252        self.padding.top = v;
253        self
254    }
255
256    pub fn padding_bottom(mut self, v: f32) -> Self {
257        self.padding.bottom = v;
258        self
259    }
260
261    pub fn min_width(mut self, v: f32) -> Self {
262        self.min_width = sanitize_min(v);
263        self
264    }
265
266    pub fn max_width(mut self, v: f32) -> Self {
267        self.max_width = sanitize_max(v);
268        self
269    }
270
271    pub fn min_height(mut self, v: f32) -> Self {
272        self.min_height = sanitize_min(v);
273        self
274    }
275
276    pub fn max_height(mut self, v: f32) -> Self {
277        self.max_height = sanitize_max(v);
278        self
279    }
280
281    pub fn clamp_width(self, w: f32) -> f32 {
282        clamp_axis(w, self.min_width, self.max_width)
283    }
284
285    pub fn clamp_height(self, h: f32) -> f32 {
286        clamp_axis(h, self.min_height, self.max_height)
287    }
288
289    pub fn clamp_size(self, size: sequoia_core::Size) -> sequoia_core::Size {
290        sequoia_core::Size::new(self.clamp_width(size.width), self.clamp_height(size.height))
291    }
292
293    pub fn margin(mut self, margin: impl Into<Edges>) -> Self {
294        let m = margin.into();
295        self.margin = Edges::only(
296            sanitize_edge(m.left),
297            sanitize_edge(m.top),
298            sanitize_edge(m.right),
299            sanitize_edge(m.bottom),
300        );
301        self
302    }
303
304    pub fn margin_xy(self, x: f32, y: f32) -> Self {
305        self.margin(Edges::xy(x, y))
306    }
307
308    pub fn margin_x(mut self, x: f32) -> Self {
309        let x = sanitize_edge(x);
310        self.margin.left = x;
311        self.margin.right = x;
312        self
313    }
314
315    pub fn margin_y(mut self, y: f32) -> Self {
316        let y = sanitize_edge(y);
317        self.margin.top = y;
318        self.margin.bottom = y;
319        self
320    }
321
322    pub fn margin_left(mut self, v: f32) -> Self {
323        self.margin.left = sanitize_edge(v);
324        self
325    }
326
327    pub fn margin_right(mut self, v: f32) -> Self {
328        self.margin.right = sanitize_edge(v);
329        self
330    }
331
332    pub fn margin_top(mut self, v: f32) -> Self {
333        self.margin.top = sanitize_edge(v);
334        self
335    }
336
337    pub fn margin_bottom(mut self, v: f32) -> Self {
338        self.margin.bottom = sanitize_edge(v);
339        self
340    }
341
342    pub fn flex(mut self, weight: f32) -> Self {
343        self.flex = sanitize_flex(weight);
344        self
345    }
346
347    pub fn flex_grow(mut self, weight: f32) -> Self {
348        self.flex = sanitize_flex(weight);
349        self
350    }
351
352    pub fn flex_shrink(mut self, shrink: f32) -> Self {
353        self.flex_shrink = sanitize_shrink(shrink);
354        self
355    }
356
357    pub fn flex_full(mut self, grow: f32, shrink: f32, basis: Option<Length>) -> Self {
358        self.flex = sanitize_flex(grow);
359        self.flex_shrink = sanitize_shrink(shrink);
360        self.flex_basis = basis;
361        self
362    }
363
364    pub fn flex_basis(mut self, basis: Option<Length>) -> Self {
365        self.flex_basis = basis;
366        self
367    }
368
369    pub fn no_basis(mut self) -> Self {
370        self.flex_basis = None;
371        self
372    }
373
374    pub fn expand(self) -> Self {
375        self.flex(1.0)
376    }
377
378    pub fn flex_weight(self) -> f32 {
379        sanitize_flex(self.flex)
380    }
381
382    pub fn flex_grow_weight(self) -> f32 {
383        sanitize_flex(self.flex)
384    }
385
386    pub fn flex_shrink_weight(self) -> f32 {
387        sanitize_shrink(self.flex_shrink)
388    }
389
390    pub fn align_self(mut self, align: Option<Align>) -> Self {
391        self.align_self = align;
392        self
393    }
394
395    pub fn align_self_value(mut self, align: Align) -> Self {
396        self.align_self = Some(align);
397        self
398    }
399
400    pub fn auto_align(mut self) -> Self {
401        self.align_self = None;
402        self
403    }
404
405    pub fn effective_align(self, container: Align) -> Align {
406        Align::resolve(self.align_self, container)
407    }
408
409    pub fn aspect_ratio(mut self, ratio: Option<f32>) -> Self {
410        self.aspect_ratio = sanitize_ratio(ratio);
411        self
412    }
413
414    pub fn aspect(mut self, ratio: f32) -> Self {
415        self.aspect_ratio = sanitize_ratio(Some(ratio));
416        self
417    }
418
419    pub fn no_aspect(mut self) -> Self {
420        self.aspect_ratio = None;
421        self
422    }
423
424    pub fn position(mut self, position: Position) -> Self {
425        self.position = position;
426        self
427    }
428
429    pub fn absolute(mut self) -> Self {
430        self.position = Position::Absolute(Inset::AUTO);
431        self
432    }
433
434    pub fn absolute_at(mut self, left: f32, top: f32) -> Self {
435        self.position = Position::Absolute(Inset::xy(left, top));
436        self
437    }
438
439    pub fn absolute_inset(mut self, inset: Inset) -> Self {
440        self.position = Position::Absolute(inset);
441        self
442    }
443
444    pub fn is_absolute(self) -> bool {
445        matches!(self.position, Position::Absolute(_))
446    }
447
448    pub fn absolute_inset_of(self) -> Option<Inset> {
449        match self.position {
450            Position::Absolute(inset) => Some(inset),
451            _ => None,
452        }
453    }
454
455    pub fn margin_main(self, axis: Axis) -> f32 {
456        match axis {
457            Axis::Horizontal => self.margin.left + self.margin.right,
458            Axis::Vertical => self.margin.top + self.margin.bottom,
459        }
460    }
461
462    pub fn margin_cross(self, axis: Axis) -> f32 {
463        match axis {
464            Axis::Horizontal => self.margin.top + self.margin.bottom,
465            Axis::Vertical => self.margin.left + self.margin.right,
466        }
467    }
468
469    pub fn apply_aspect(self, mut w: f32, mut h: f32, w_auto: bool, h_auto: bool) -> (f32, f32) {
470        let Some(r) = sanitize_ratio(self.aspect_ratio) else {
471            return (w, h);
472        };
473        if r <= 0.0 {
474            return (w, h);
475        }
476        match (w_auto, h_auto) {
477            (true, false) => {
478                w = (h * r).max(0.0);
479            }
480            (false, true) => {
481                h = if r != 0.0 { (w / r).max(0.0) } else { h };
482            }
483            (true, true) => {
484                if w <= 0.0 && h > 0.0 {
485                    w = (h * r).max(0.0);
486                } else if h <= 0.0 && w > 0.0 {
487                    h = (w / r).max(0.0);
488                }
489            }
490            (false, false) => {}
491        }
492        (w, h)
493    }
494
495    pub fn outer_size(self, inner: Size) -> Size {
496        Size::new(
497            inner.width + self.margin.horizontal_sum(),
498            inner.height + self.margin.vertical_sum(),
499        )
500    }
501
502    pub fn min_main(self, axis: Axis) -> f32 {
503        sanitize_min(match axis {
504            Axis::Horizontal => self.min_width,
505            Axis::Vertical => self.min_height,
506        })
507    }
508
509    pub fn max_main(self, axis: Axis) -> f32 {
510        sanitize_max(match axis {
511            Axis::Horizontal => self.max_width,
512            Axis::Vertical => self.max_height,
513        })
514    }
515
516    pub fn clamp_main(self, value: f32, axis: Axis) -> f32 {
517        clamp_axis(value, self.min_main(axis), self.max_main(axis))
518    }
519
520    pub fn overflow(mut self, overflow: Overflow) -> Self {
521        self.overflow = overflow;
522        self
523    }
524
525    pub fn indent_top(mut self, v: f32) -> Self {
526        self.indent_top = Some(sanitize_indent(v));
527        self
528    }
529
530    pub fn indent_bottom(mut self, v: f32) -> Self {
531        self.indent_bottom = Some(sanitize_indent(v));
532        self
533    }
534
535    pub fn indent_left(mut self, v: f32) -> Self {
536        self.indent_left = Some(sanitize_indent(v));
537        self
538    }
539
540    pub fn indent_right(mut self, v: f32) -> Self {
541        self.indent_right = Some(sanitize_indent(v));
542        self
543    }
544
545    pub fn clear_indent_top(mut self) -> Self {
546        self.indent_top = None;
547        self
548    }
549
550    pub fn clear_indent_bottom(mut self) -> Self {
551        self.indent_bottom = None;
552        self
553    }
554
555    pub fn clear_indent_left(mut self) -> Self {
556        self.indent_left = None;
557        self
558    }
559
560    pub fn clear_indent_right(mut self) -> Self {
561        self.indent_right = None;
562        self
563    }
564
565    pub fn clear_indents(mut self) -> Self {
566        self.indent_top = None;
567        self.indent_bottom = None;
568        self.indent_left = None;
569        self.indent_right = None;
570        self
571    }
572
573    pub fn indent(mut self, indents: Indents) -> Self {
574        if let Some(v) = indents.top {
575            self.indent_top = Some(sanitize_indent(v));
576        }
577        if let Some(v) = indents.bottom {
578            self.indent_bottom = Some(sanitize_indent(v));
579        }
580        if let Some(v) = indents.left {
581            self.indent_left = Some(sanitize_indent(v));
582        }
583        if let Some(v) = indents.right {
584            self.indent_right = Some(sanitize_indent(v));
585        }
586        self
587    }
588
589    pub fn indent_all(mut self, v: f32) -> Self {
590        let v = sanitize_indent(v);
591        self.indent_top = Some(v);
592        self.indent_bottom = Some(v);
593        self.indent_left = Some(v);
594        self.indent_right = Some(v);
595        self
596    }
597
598    pub fn indent_xy(mut self, x: f32, y: f32) -> Self {
599        let x = sanitize_indent(x);
600        let y = sanitize_indent(y);
601        self.indent_left = Some(x);
602        self.indent_right = Some(x);
603        self.indent_top = Some(y);
604        self.indent_bottom = Some(y);
605        self
606    }
607
608    pub fn indent_axes(self, horizontal: f32, vertical: f32) -> Self {
609        self.indent_xy(horizontal, vertical)
610    }
611
612    pub fn indent_main_start(self, axis: Axis) -> Option<f32> {
613        match axis {
614            Axis::Horizontal => self.indent_left,
615            Axis::Vertical => self.indent_top,
616        }
617    }
618
619    pub fn indent_main_end(self, axis: Axis) -> Option<f32> {
620        match axis {
621            Axis::Horizontal => self.indent_right,
622            Axis::Vertical => self.indent_bottom,
623        }
624    }
625
626    pub fn indent_cross_start(self, axis: Axis) -> Option<f32> {
627        match axis {
628            Axis::Horizontal => self.indent_top,
629            Axis::Vertical => self.indent_left,
630        }
631    }
632
633    pub fn indent_cross_end(self, axis: Axis) -> Option<f32> {
634        match axis {
635            Axis::Horizontal => self.indent_bottom,
636            Axis::Vertical => self.indent_right,
637        }
638    }
639
640    pub fn indents(self) -> Indents {
641        Indents {
642            left: self.indent_left,
643            top: self.indent_top,
644            right: self.indent_right,
645            bottom: self.indent_bottom,
646        }
647    }
648}
649
650pub fn sanitize_flex(v: f32) -> f32 {
651    if v.is_finite() && v > 0.0 { v } else { 0.0 }
652}
653
654pub fn sanitize_shrink(v: f32) -> f32 {
655    if v.is_finite() && v >= 0.0 {
656        v
657    } else if v.is_nan() {
658        1.0
659    } else {
660        0.0
661    }
662}
663
664pub fn sanitize_ratio(v: Option<f32>) -> Option<f32> {
665    match v {
666        Some(r) if r.is_finite() && r > 0.0 => Some(r),
667        _ => None,
668    }
669}
670
671fn sanitize_edge(v: f32) -> f32 {
672    if v.is_finite() { v.max(0.0) } else { 0.0 }
673}
674
675pub fn sanitize_indent(v: f32) -> f32 {
676    if v.is_finite() { v.max(0.0) } else { 0.0 }
677}
678
679fn sanitize_min(v: f32) -> f32 {
680    if !v.is_finite() || v < 0.0 { 0.0 } else { v }
681}
682
683fn sanitize_max(v: f32) -> f32 {
684    if v.is_nan() {
685        f32::INFINITY
686    } else if v.is_infinite() {
687        if v > 0.0 { f32::INFINITY } else { 0.0 }
688    } else {
689        v.max(0.0)
690    }
691}
692
693fn clamp_axis(value: f32, min: f32, max: f32) -> f32 {
694    let (min, max) = if min > max { (max, max) } else { (min, max) };
695    if value < min {
696        min
697    } else if value > max {
698        max
699    } else {
700        value
701    }
702}