Skip to main content

sequoia_elements/
divider.rs

1use sequoia_core::{Color, Edges, Length, Rect, Size};
2use sequoia_layout::{LayoutCtx, LayoutStyle};
3use sequoia_style::DividerStyle;
4use sequoia_view::{PaintCtx, Unit, View};
5
6const THICKNESS: f32 = 1.0;
7
8#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
9#[non_exhaustive]
10pub enum DividerAxis {
11    #[default]
12    Horizontal,
13
14    Vertical,
15}
16
17pub fn divider_line_rect(rect: Rect, axis: DividerAxis, thickness: f32) -> Rect {
18    let t = thickness.max(0.0);
19    match axis {
20        DividerAxis::Horizontal => {
21            let h = t.min(rect.size.height.max(0.0));
22            Rect::from_xywh(
23                rect.left(),
24                rect.top() + (rect.size.height - h) * 0.5,
25                rect.size.width.max(0.0),
26                h,
27            )
28        }
29        DividerAxis::Vertical => {
30            let w = t.min(rect.size.width.max(0.0));
31            Rect::from_xywh(
32                rect.left() + (rect.size.width - w) * 0.5,
33                rect.top(),
34                w,
35                rect.size.height.max(0.0),
36            )
37        }
38    }
39}
40
41pub struct Divider {
42    axis: DividerAxis,
43    style: Option<DividerStyle>,
44    color: Option<Color>,
45    thickness: Option<f32>,
46    margin: Edges,
47}
48
49impl Divider {
50    pub fn horizontal() -> Self {
51        Self {
52            axis: DividerAxis::Horizontal,
53            style: None,
54            color: None,
55            thickness: None,
56            margin: Edges::ZERO,
57        }
58    }
59
60    pub fn vertical() -> Self {
61        Self {
62            axis: DividerAxis::Vertical,
63            style: None,
64            color: None,
65            thickness: None,
66            margin: Edges::ZERO,
67        }
68    }
69
70    pub fn axis(mut self, axis: DividerAxis) -> Self {
71        self.axis = axis;
72        self
73    }
74
75    pub fn style(mut self, style: DividerStyle) -> Self {
76        self.style = Some(style);
77        self
78    }
79
80    pub fn color(mut self, color: Color) -> Self {
81        self.color = Some(color);
82        self
83    }
84
85    pub fn thickness(mut self, px: f32) -> Self {
86        self.thickness = Some(px);
87        self
88    }
89
90    pub fn margin(mut self, margin: impl Into<Edges>) -> Self {
91        let m = margin.into();
92        self.margin = Edges::only(
93            sanitize_margin(m.left),
94            sanitize_margin(m.top),
95            sanitize_margin(m.right),
96            sanitize_margin(m.bottom),
97        );
98        self
99    }
100
101    pub fn margin_xy(self, x: f32, y: f32) -> Self {
102        self.margin(Edges::xy(x, y))
103    }
104
105    pub fn margin_x(mut self, x: f32) -> Self {
106        let x = sanitize_margin(x);
107        self.margin.left = x;
108        self.margin.right = x;
109        self
110    }
111
112    pub fn margin_y(mut self, y: f32) -> Self {
113        let y = sanitize_margin(y);
114        self.margin.top = y;
115        self.margin.bottom = y;
116        self
117    }
118
119    pub fn margin_left(mut self, v: f32) -> Self {
120        self.margin.left = sanitize_margin(v);
121        self
122    }
123
124    pub fn margin_right(mut self, v: f32) -> Self {
125        self.margin.right = sanitize_margin(v);
126        self
127    }
128
129    pub fn margin_top(mut self, v: f32) -> Self {
130        self.margin.top = sanitize_margin(v);
131        self
132    }
133
134    pub fn margin_bottom(mut self, v: f32) -> Self {
135        self.margin.bottom = sanitize_margin(v);
136        self
137    }
138
139    fn resolved(&self, style: DividerStyle, theme: &sequoia_style::Theme) -> (Color, f32) {
140        let _ = theme;
141        let color = self.color.or(style.color).unwrap_or(Color::hex(0x1F1F1F));
142        let thickness = self
143            .thickness
144            .or(style.thickness)
145            .unwrap_or(THICKNESS)
146            .max(0.0);
147        (color, thickness)
148    }
149}
150
151impl Default for Divider {
152    fn default() -> Self {
153        Self::horizontal()
154    }
155}
156
157fn sanitize_margin(v: f32) -> f32 {
158    if v.is_finite() { v.max(0.0) } else { 0.0 }
159}
160
161impl View for Divider {
162    fn body(&self) -> impl View + 'static {
163        Unit
164    }
165    fn is_leaf(&self) -> bool {
166        true
167    }
168    fn hit_testable(&self) -> bool {
169        false
170    }
171    fn layout_style(&self) -> LayoutStyle {
172        match self.axis {
173            DividerAxis::Horizontal => LayoutStyle {
174                width: Length::Fill(1.0),
175                height: Length::Fit,
176                margin: self.margin,
177                ..LayoutStyle::default().hit_testable(false)
178            },
179            DividerAxis::Vertical => LayoutStyle {
180                width: Length::Fit,
181                height: Length::Fill(1.0),
182                margin: self.margin,
183                ..LayoutStyle::default().hit_testable(false)
184            },
185        }
186    }
187    fn intrinsic_size(&self, _ctx: &mut LayoutCtx) -> Option<Size> {
188        let style = self.style.unwrap_or_default();
189        let t = self
190            .thickness
191            .or(style.thickness)
192            .unwrap_or(THICKNESS)
193            .max(0.0);
194        Some(match self.axis {
195            DividerAxis::Horizontal => Size::new(0.0, t),
196            DividerAxis::Vertical => Size::new(t, 0.0),
197        })
198    }
199    fn paint_self(&self, ctx: &mut PaintCtx) {
200        let style = self.style.unwrap_or_default();
201        let theme = ctx.theme();
202        let (color, thickness) = self.resolved(style, &theme);
203        if thickness <= 0.0 || color.a <= 0.0 {
204            return;
205        }
206        let line = divider_line_rect(ctx.rect, self.axis, thickness);
207        if line.size.width <= 0.0 || line.size.height <= 0.0 {
208            return;
209        }
210        ctx.quad(line, color, 0.0);
211    }
212}
213
214#[cfg(test)]
215mod tests {
216    use super::*;
217
218    #[test]
219    fn horizontal_centers_thickness() {
220        let rect = Rect::from_xywh(0.0, 0.0, 200.0, 21.0);
221        assert_eq!(
222            divider_line_rect(rect, DividerAxis::Horizontal, 1.0),
223            Rect::from_xywh(0.0, 10.0, 200.0, 1.0)
224        );
225    }
226
227    #[test]
228    fn vertical_centers_thickness() {
229        let rect = Rect::from_xywh(0.0, 0.0, 21.0, 200.0);
230        assert_eq!(
231            divider_line_rect(rect, DividerAxis::Vertical, 1.0),
232            Rect::from_xywh(10.0, 0.0, 1.0, 200.0)
233        );
234    }
235
236    #[test]
237    fn thickness_never_overflows_rect() {
238        let rect = Rect::from_xywh(5.0, 5.0, 10.0, 2.0);
239        let line = divider_line_rect(rect, DividerAxis::Horizontal, 8.0);
240        assert_eq!(line.size.height, 2.0);
241        assert_eq!(line.top(), 5.0);
242    }
243}