Skip to main content

zest_widget/widget/
text.rs

1//! Static text widget. Renders a `Cow<'a, str>` with a font + color.
2
3use alloc::borrow::Cow;
4use core::marker::PhantomData;
5use embedded_graphics::{
6    mono_font::MonoFont, pixelcolor::PixelColor, prelude::*, primitives::Rectangle,
7    text::Alignment as EgAlignment,
8};
9use zest_core::{Constraints, Horizontal, Length, RenderError, Renderer, TouchPhase, Vertical};
10use zest_theme::Theme;
11
12use super::Widget;
13
14/// Static text. Renders `content` using the theme's default font and
15/// foreground color unless overridden.
16pub struct Text<'a, C: PixelColor, M: Clone> {
17    rect: Rectangle,
18    content: Cow<'a, str>,
19    align_x: Horizontal,
20    align_y: Vertical,
21    color: Option<C>,
22    font: Option<&'a MonoFont<'a>>,
23    width: Length,
24    height: Length,
25    _phantom: PhantomData<M>,
26}
27
28impl<'a, C: PixelColor, M: Clone> Text<'a, C, M> {
29    /// New text widget. Position via `arrange` (or parent container).
30    pub fn new(content: impl Into<Cow<'a, str>>) -> Self {
31        Self {
32            rect: Rectangle::zero(),
33            content: content.into(),
34            align_x: Horizontal::Left,
35            align_y: Vertical::Center,
36            color: None,
37            font: None,
38            width: Length::Fill,
39            height: Length::Fill,
40            _phantom: PhantomData,
41        }
42    }
43
44    /// Width sizing intent.
45    #[must_use]
46    pub fn width(mut self, width: impl Into<Length>) -> Self {
47        self.width = width.into();
48        self
49    }
50
51    /// Height sizing intent.
52    #[must_use]
53    pub fn height(mut self, height: impl Into<Length>) -> Self {
54        self.height = height.into();
55        self
56    }
57
58    /// Horizontal alignment within the arranged rect.
59    #[must_use]
60    pub fn align_x(mut self, h: Horizontal) -> Self {
61        self.align_x = h;
62        self
63    }
64
65    /// Vertical alignment within the arranged rect.
66    #[must_use]
67    pub fn align_y(mut self, v: Vertical) -> Self {
68        self.align_y = v;
69        self
70    }
71
72    /// Override foreground color (default: `theme.background.on_base`).
73    #[must_use]
74    pub fn color(mut self, c: C) -> Self {
75        self.color = Some(c);
76        self
77    }
78
79    /// Override font (default: `theme.default_font`).
80    #[must_use]
81    pub fn font(mut self, font: &'a MonoFont<'a>) -> Self {
82        self.font = Some(font);
83        self
84    }
85}
86
87impl<'a, C: PixelColor, M: Clone> Widget<C, M> for Text<'a, C, M> {
88    fn measure(&mut self, constraints: Constraints) -> Size {
89        let intrinsic_w = self.font.map_or(0, |f| {
90            f.character_size
91                .width
92                .saturating_mul(self.content.chars().count() as u32)
93        });
94        let intrinsic_h = self.font.map_or(0, |f| f.character_size.height);
95        let w = self.width.resolve(intrinsic_w, constraints.max.width);
96        let h = self.height.resolve(intrinsic_h, constraints.max.height);
97        constraints.clamp(Size::new(w, h))
98    }
99
100    fn preferred_size(&self) -> (Length, Length) {
101        (self.width, self.height)
102    }
103
104    fn arrange(&mut self, rect: Rectangle) {
105        self.rect = rect;
106    }
107
108    fn rect(&self) -> Rectangle {
109        self.rect
110    }
111
112    fn handle_touch(&mut self, _point: Point, _phase: TouchPhase) -> Option<M> {
113        None
114    }
115
116    fn draw<'t>(
117        &self,
118        renderer: &mut dyn Renderer<C>,
119        theme: &Theme<'t, C>,
120    ) -> Result<(), RenderError> {
121        let color = self.color.unwrap_or(theme.background.on_base);
122        let font = self.font.unwrap_or(theme.default_font());
123
124        let (eg_align, x_anchor) = match self.align_x {
125            Horizontal::Left => (EgAlignment::Left, self.rect.top_left.x),
126            Horizontal::Center => (
127                EgAlignment::Center,
128                self.rect.top_left.x + (self.rect.size.width / 2) as i32,
129            ),
130            Horizontal::Right => (
131                EgAlignment::Right,
132                self.rect.top_left.x + self.rect.size.width as i32,
133            ),
134        };
135        let glyph_h = font.character_size.height as i32;
136        let baseline_y = match self.align_y {
137            Vertical::Top => self.rect.top_left.y + glyph_h,
138            Vertical::Center => {
139                self.rect.top_left.y + (self.rect.size.height as i32) / 2 + glyph_h / 3
140            }
141            Vertical::Bottom => self.rect.top_left.y + self.rect.size.height as i32,
142        };
143
144        renderer.draw_text(
145            &self.content,
146            Point::new(x_anchor, baseline_y),
147            font,
148            color,
149            eg_align,
150        )?;
151        Ok(())
152    }
153}