zest_widget/widget/
text.rs1use 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
14pub 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 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 #[must_use]
46 pub fn width(mut self, width: impl Into<Length>) -> Self {
47 self.width = width.into();
48 self
49 }
50
51 #[must_use]
53 pub fn height(mut self, height: impl Into<Length>) -> Self {
54 self.height = height.into();
55 self
56 }
57
58 #[must_use]
60 pub fn align_x(mut self, h: Horizontal) -> Self {
61 self.align_x = h;
62 self
63 }
64
65 #[must_use]
67 pub fn align_y(mut self, v: Vertical) -> Self {
68 self.align_y = v;
69 self
70 }
71
72 #[must_use]
74 pub fn color(mut self, c: C) -> Self {
75 self.color = Some(c);
76 self
77 }
78
79 #[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}