Skip to main content

zest_widget/widget/
line.rs

1//! Passive polyline. Connects a borrowed slice of points with straight
2//! segments via [`zest_core::Renderer::stroke_line`].
3//!
4//! The point slice is borrowed (`&'a [Point]`) so the owner — typically a
5//! screen holding the data — keeps it alive for the frame. By default the
6//! points are interpreted **content-relative**: each point is offset by the
7//! widget's arranged top-left. Call [`Line::absolute`] to treat the points
8//! as already being in screen coordinates.
9
10use super::Widget;
11use core::marker::PhantomData;
12use embedded_graphics::{pixelcolor::PixelColor, prelude::*, primitives::Rectangle};
13use zest_core::{Constraints, Length, RenderError, Renderer, TouchPhase};
14use zest_theme::Theme;
15
16/// A multi-segment line through a borrowed point slice.
17pub struct Line<'a, C: PixelColor, M: Clone> {
18    rect: Rectangle,
19    points: &'a [Point],
20    color: Option<C>,
21    line_width: u32,
22    /// When `true`, points are screen-absolute; otherwise content-relative
23    /// to the arranged rect's top-left.
24    absolute: bool,
25    width: Length,
26    height: Length,
27    _phantom: PhantomData<M>,
28}
29
30impl<'a, C: PixelColor, M: Clone> Line<'a, C, M> {
31    /// New polyline through `points` (content-relative by default).
32    /// Position and size are assigned by the parent via `arrange`.
33    pub fn new(points: &'a [Point]) -> Self {
34        Self {
35            rect: Rectangle::zero(),
36            points,
37            color: None,
38            line_width: 1,
39            absolute: false,
40            width: Length::Fill,
41            height: Length::Fill,
42            _phantom: PhantomData,
43        }
44    }
45
46    /// Stroke color (default: `theme.background.on_base`).
47    #[must_use]
48    pub fn color(mut self, color: C) -> Self {
49        self.color = Some(color);
50        self
51    }
52
53    /// Stroke width in pixels (default: 1).
54    #[must_use]
55    pub fn width_px(mut self, width: u32) -> Self {
56        self.line_width = width;
57        self
58    }
59
60    /// Interpret the points as screen-absolute coordinates
61    /// instead of content-relative to the arranged rect.
62    #[must_use]
63    pub fn absolute(mut self) -> Self {
64        self.absolute = true;
65        self
66    }
67
68    /// Width sizing intent.
69    #[must_use]
70    pub fn width(mut self, width: impl Into<Length>) -> Self {
71        self.width = width.into();
72        self
73    }
74
75    /// Height sizing intent.
76    #[must_use]
77    pub fn height(mut self, height: impl Into<Length>) -> Self {
78        self.height = height.into();
79        self
80    }
81}
82
83impl<'a, C: PixelColor, M: Clone> Widget<C, M> for Line<'a, C, M> {
84    fn measure(&mut self, constraints: Constraints) -> Size {
85        let w = self
86            .width
87            .resolve(constraints.max.width, constraints.max.width);
88        let h = self
89            .height
90            .resolve(constraints.max.height, constraints.max.height);
91        constraints.clamp(Size::new(w, h))
92    }
93
94    fn preferred_size(&self) -> (Length, Length) {
95        (self.width, self.height)
96    }
97
98    fn arrange(&mut self, rect: Rectangle) {
99        self.rect = rect;
100    }
101
102    fn rect(&self) -> Rectangle {
103        self.rect
104    }
105
106    fn handle_touch(&mut self, _point: Point, _phase: TouchPhase) -> Option<M> {
107        None
108    }
109
110    fn draw<'t>(
111        &self,
112        renderer: &mut dyn Renderer<C>,
113        theme: &Theme<'t, C>,
114    ) -> Result<(), RenderError> {
115        if self.points.len() < 2 {
116            return Ok(());
117        }
118        let color = self.color.unwrap_or(theme.background.on_base);
119        let offset = if self.absolute {
120            Point::zero()
121        } else {
122            self.rect.top_left
123        };
124        for pair in self.points.windows(2) {
125            renderer.stroke_line(pair[0] + offset, pair[1] + offset, color, self.line_width)?;
126        }
127        Ok(())
128    }
129}