Skip to main content

zest_widget/widget/
divider.rs

1//! Thin horizontal or vertical rule. Reads color from
2//! `theme.background.divider` unless overridden.
3//!
4//! Construct via [`horizontal_divider`] / [`vertical_divider`] for
5//! the common cases, or build a custom [`Divider`] directly.
6
7use super::Widget;
8use core::marker::PhantomData;
9use embedded_graphics::{pixelcolor::PixelColor, prelude::*, primitives::Rectangle};
10use zest_core::{Constraints, Length, RenderError, Renderer, TouchPhase};
11use zest_theme::Theme;
12
13/// A thin solid rule. `width`/`height` follow the standard [`Length`]
14/// rules; the "thin" axis is set via [`Self::thickness`] (which is
15/// just a shortcut for `Fixed(n)` on that axis).
16pub struct Divider<C: PixelColor, M: Clone> {
17    rect: Rectangle,
18    width: Length,
19    height: Length,
20    color: Option<C>,
21    _phantom: PhantomData<M>,
22}
23
24impl<C: PixelColor, M: Clone> Divider<C, M> {
25    /// Construct a divider with explicit `width` and `height` intents.
26    pub fn new(width: impl Into<Length>, height: impl Into<Length>) -> Self {
27        Self {
28            rect: Rectangle::zero(),
29            width: width.into(),
30            height: height.into(),
31            color: None,
32            _phantom: PhantomData,
33        }
34    }
35
36    /// Width sizing intent.
37    #[must_use]
38    pub fn width(mut self, width: impl Into<Length>) -> Self {
39        self.width = width.into();
40        self
41    }
42
43    /// Height sizing intent.
44    #[must_use]
45    pub fn height(mut self, height: impl Into<Length>) -> Self {
46        self.height = height.into();
47        self
48    }
49
50    /// Explicit color (default: `theme.background.divider`).
51    #[must_use]
52    pub fn color(mut self, color: C) -> Self {
53        self.color = Some(color);
54        self
55    }
56
57    /// Set the thin-axis thickness. For a horizontal divider
58    /// this is the height; for a vertical divider, the width. Chooses
59    /// the smaller of the current two axes as the "thin" one.
60    #[must_use]
61    pub fn thickness(self, thickness: u32) -> Self {
62        let thin_is_height = matches!(
63            (self.width, self.height),
64            (Length::Fill | Length::FillPortion(_), _),
65        );
66        if thin_is_height {
67            self.height(Length::Fixed(thickness))
68        } else {
69            self.width(Length::Fixed(thickness))
70        }
71    }
72}
73
74/// Horizontal rule — fills container width, 1px tall by default.
75pub fn horizontal_divider<C: PixelColor, M: Clone>() -> Divider<C, M> {
76    Divider::new(Length::Fill, Length::Fixed(1))
77}
78
79/// Vertical rule — fills container height, 1px wide by default.
80pub fn vertical_divider<C: PixelColor, M: Clone>() -> Divider<C, M> {
81    Divider::new(Length::Fixed(1), Length::Fill)
82}
83
84impl<C: PixelColor, M: Clone> Widget<C, M> for Divider<C, M> {
85    fn measure(&mut self, constraints: Constraints) -> Size {
86        let w = self.width.resolve(1, constraints.max.width);
87        let h = self.height.resolve(1, constraints.max.height);
88        constraints.clamp(Size::new(w, h))
89    }
90
91    fn preferred_size(&self) -> (Length, Length) {
92        (self.width, self.height)
93    }
94
95    fn arrange(&mut self, rect: Rectangle) {
96        self.rect = rect;
97    }
98
99    fn rect(&self) -> Rectangle {
100        self.rect
101    }
102
103    fn handle_touch(&mut self, _point: Point, _phase: TouchPhase) -> Option<M> {
104        None
105    }
106
107    fn draw<'t>(
108        &self,
109        renderer: &mut dyn Renderer<C>,
110        theme: &Theme<'t, C>,
111    ) -> Result<(), RenderError> {
112        let color = self.color.unwrap_or(theme.background.divider);
113        renderer.fill_rect(self.rect, color)?;
114        Ok(())
115    }
116}