1use embedded_graphics::{
2 draw_target::DrawTarget,
3 geometry::Point,
4 prelude::{PixelColor, Primitive},
5 primitives::{PrimitiveStyle, Rectangle},
6 text::{renderer::TextRenderer, Alignment, Baseline},
7 transform::Transform,
8 Drawable,
9};
10
11#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
17#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
18pub struct StaticText<'a, S> {
19 pub text: &'a str,
21
22 pub rectangle: Rectangle,
24
25 pub character_style: S,
27
28 pub alignment: Alignment,
30
31 pub baseline: Baseline,
33}
34
35impl<'a, S> StaticText<'a, S> {
36 pub const fn new(text: &'a str, rectangle: Rectangle, character_style: S) -> Self {
38 Self {
39 text,
40 rectangle,
41 character_style,
42 alignment: Alignment::Left,
43 baseline: Baseline::Alphabetic,
44 }
45 }
46
47 pub const fn with_style(
49 text: &'a str,
50 rectangle: Rectangle,
51 character_style: S,
52 alignment: Alignment,
53 baseline: Baseline,
54 ) -> Self {
55 Self {
56 text,
57 rectangle,
58 character_style,
59 alignment,
60 baseline,
61 }
62 }
63
64 pub fn fill_with_style<C, D>(
65 &self,
66 style: PrimitiveStyle<C>,
67 target: &mut D,
68 ) -> Result<Point, D::Error>
69 where
70 C: PixelColor,
71 D: DrawTarget<Color = C>,
72 {
73 let result = self.rectangle.into_styled(style).draw(target);
74 result.map(|_| {
75 Point::new(
76 self.rectangle.top_left.x + self.rectangle.size.width as i32,
77 self.rectangle.top_left.y,
78 )
79 })
80 }
81}
82
83impl<S: Clone> Transform for StaticText<'_, S> {
84 fn translate(&self, by: Point) -> Self {
85 Self {
86 rectangle: Rectangle::new(self.rectangle.top_left + by, self.rectangle.size),
87 ..self.clone()
88 }
89 }
90
91 fn translate_mut(&mut self, by: Point) -> &mut Self {
92 self.rectangle.top_left += by;
93
94 self
95 }
96}
97
98impl<S: TextRenderer> StaticText<'_, S> {
99 fn lines(&self) -> impl Iterator<Item = (&str, Point)> {
100 let line_feed = self.text.matches('\n').count() as i32;
101
102 let offset_y = self.character_style.line_height() as i32 * line_feed;
103 let mut position = self.rectangle.top_left;
104 let height = self.rectangle.size.height as i32;
105 match self.baseline {
106 Baseline::Top => {}
107 Baseline::Bottom | Baseline::Alphabetic => position.y += height - 1 - offset_y,
108 Baseline::Middle => position.y += (height - 1 - offset_y) / 2,
109 }
110
111 self.text.split('\n').map(move |line| {
112 let p = match self.alignment {
113 Alignment::Left => position,
114 Alignment::Right => {
115 let metrics =
116 self.character_style
117 .measure_string(line, Point::zero(), self.baseline);
118 position + Point::new(self.rectangle.size.width as i32, 0)
119 - (metrics.next_position - Point::new(1, 0))
120 }
121 Alignment::Center => {
122 let metrics =
123 self.character_style
124 .measure_string(line, Point::zero(), self.baseline);
125 position + Point::new(self.rectangle.size.width as i32 / 2, 0)
126 - metrics.next_position / 2
127 }
128 };
129
130 position.y += self.character_style.line_height() as i32;
131
132 let len = line.len();
134 if len > 0 && line.as_bytes()[len - 1] == b'\r' {
135 (&line[0..len - 1], p)
136 } else {
137 (line, p)
138 }
139 })
140 }
141
142 pub fn clear<D>(&self, target: &mut D) -> Result<Point, D::Error>
143 where
144 D: DrawTarget<Color = S::Color>,
145 {
146 self.character_style.draw_whitespace(
147 self.rectangle.size.width,
148 self.rectangle.top_left,
149 self.baseline,
150 target,
151 )
152 }
153
154 pub fn clear_by_color<C, D>(&self, color: C, target: &mut D) -> Result<Point, D::Error>
155 where
156 C: PixelColor,
157 D: DrawTarget<Color = C>,
158 {
159 target.fill_solid(&self.rectangle, color).map(|_| {
160 Point::new(
161 self.rectangle.top_left.x + self.rectangle.size.width as i32,
162 self.rectangle.top_left.y,
163 )
164 })
165 }
166}
167
168impl<S: TextRenderer> Drawable for StaticText<'_, S> {
169 type Color = S::Color;
170 type Output = Point;
171
172 fn draw<D>(&self, target: &mut D) -> Result<Point, D::Error>
173 where
174 D: DrawTarget<Color = Self::Color>,
175 {
176 let mut next_position = Point::zero();
177 let size = &self.rectangle.size;
178 let left_x = self.rectangle.top_left.x;
179 let right_x = left_x + size.width as i32;
180
181 for (line, position) in self.lines() {
182 if position.x > left_x {
183 self.character_style.draw_whitespace(
184 (position.x - left_x) as u32,
185 Point::new(left_x, position.y),
186 self.baseline,
187 target,
188 )?;
189 }
190
191 next_position =
192 self.character_style
193 .draw_string(line, position, self.baseline, target)?;
194
195 if next_position.x < right_x {
196 self.character_style.draw_whitespace(
197 (right_x - next_position.x) as u32,
198 next_position,
199 self.baseline,
200 target,
201 )?;
202 next_position.x = right_x
203 }
204 }
205
206 Ok(next_position)
207 }
208}