1use crate::color::{Color, SetColor};
2use core::{
3 fmt,
4 sync::atomic::{AtomicU64, Ordering},
5};
6use embedded_graphics::{
7 geometry::Point,
8 mono_font::{self, MonoFont, MonoTextStyle},
9 pixelcolor::{self, RgbColor},
10 text::{self, Text},
11 Drawable,
12};
13use hal_core::framebuffer::{Draw, DrawTarget};
14#[derive(Debug)]
15pub struct MakeTextWriter<D> {
16 mk: fn() -> D,
17 settings: TextWriterBuilder,
18 next_point: AtomicU64,
19 line_len: u32,
20 char_height: u32,
21 last_line: i32,
22}
23
24#[derive(Debug, Clone, Copy)]
25pub struct TextWriterBuilder {
26 default_color: Color,
27 start_point: Point,
28}
29
30#[derive(Clone, Debug)]
31pub struct TextWriter<'mk, D> {
32 target: DrawTarget<D>,
33 color: Color,
34 mk: &'mk MakeTextWriter<D>,
35}
36
37const fn pack_point(Point { x, y }: Point) -> u64 {
38 ((x as u64) << 32) | y as u64
39}
40
41const fn unpack_point(u: u64) -> Point {
42 const Y_MASK: u64 = u32::MAX as u64;
43 let x = (u >> 32) as i32;
44 let y = (u & Y_MASK) as i32;
45 Point { x, y }
46}
47
48impl<D> fmt::Write for TextWriter<'_, D>
49where
50 D: Draw,
51{
52 fn write_str(&mut self, s: &str) -> fmt::Result {
53 let curr_packed = self.mk.next_point.load(Ordering::Relaxed);
54 let mut curr_point = unpack_point(curr_packed);
55
56 for mut line in s.split_inclusive('\n') {
78 let has_newline = line.ends_with('\n');
84 if has_newline {
85 line = &line[..line.len() - 1];
89 }
90
91 if curr_point.y > self.mk.last_line {
95 let ydiff = curr_point.y - self.mk.last_line;
96 curr_point = Point {
97 y: self.mk.last_line,
98 x: 10,
99 };
100 self.target.inner_mut().scroll_vert(ydiff as isize);
101 }
102
103 let next_point = if line.is_empty() {
104 curr_point
107 } else {
108 Text::with_alignment(s, curr_point, self.text_style(), text::Alignment::Left)
110 .draw(&mut self.target)
111 .map_err(|_| fmt::Error)?
112 };
113
114 if has_newline {
115 curr_point = Point {
117 y: curr_point.y + self.mk.char_height as i32,
118 x: 10,
119 };
120 } else {
121 curr_point = next_point;
122 }
123 }
124
125 match self.mk.next_point.compare_exchange(
126 curr_packed,
127 pack_point(curr_point),
128 Ordering::Relaxed,
129 Ordering::Relaxed,
130 ) {
131 Ok(_) => Ok(()),
132 Err(actual_point) => unsafe {
133 mycelium_util::unreachable_unchecked!(
134 "lock should guard this, could actually be totally unsync; curr_point={}; actual_point={}",
135 unpack_point(curr_packed),
136 unpack_point(actual_point)
137 );
138 },
139 }
140 }
141}
142
143impl<D> SetColor for TextWriter<'_, D>
144where
145 D: Draw,
146{
147 fn set_fg_color(&mut self, color: Color) {
148 let color = if color == Color::Default {
149 self.mk.settings.default_color
150 } else {
151 color
152 };
153 self.color = color;
154 }
155
156 fn fg_color(&self) -> Color {
157 self.color
158 }
159
160 fn set_bold(&mut self, bold: bool) {
161 use Color::*;
162 let next_color = if bold {
163 match self.color {
164 Black => BrightBlack,
165 Red => BrightRed,
166 Green => BrightGreen,
167 Yellow => BrightYellow,
168 Blue => BrightBlue,
169 Magenta => BrightMagenta,
170 Cyan => BrightCyan,
171 White => BrightWhite,
172 x => x,
173 }
174 } else {
175 match self.color {
176 BrightBlack => Black,
177 BrightRed => Red,
178 BrightGreen => Green,
179 BrightYellow => Yellow,
180 BrightBlue => Blue,
181 BrightMagenta => Magenta,
182 BrightCyan => Cyan,
183 BrightWhite => White,
184 x => x,
185 }
186 };
187 self.set_fg_color(next_color);
188 }
189}
190
191impl<D> TextWriter<'_, D>
192where
193 D: Draw,
194{
195 fn text_style(&self) -> MonoTextStyle<'static, pixelcolor::Rgb888> {
196 use pixelcolor::Rgb888;
197 const COLOR_TABLE: [Rgb888; 17] = [
198 Rgb888::BLACK, Rgb888::new(128, 0, 0), Rgb888::new(0, 128, 0), Rgb888::new(128, 128, 0), Rgb888::new(0, 0, 128), Rgb888::new(128, 0, 128), Rgb888::new(0, 128, 128), Rgb888::new(192, 192, 192), Rgb888::new(192, 192, 192), Rgb888::new(128, 128, 128), Rgb888::new(255, 0, 0), Rgb888::new(0, 255, 0), Rgb888::new(255, 255, 0), Rgb888::new(0, 0, 255), Rgb888::new(255, 0, 255), Rgb888::new(0, 255, 255), Rgb888::new(255, 255, 255), ];
216 MonoTextStyle::new(
217 self.mk.settings.get_font(),
218 COLOR_TABLE[self.color as usize],
219 )
220 }
221}
222
223impl<D> MakeTextWriter<D> {
225 #[must_use]
226 pub const fn builder() -> TextWriterBuilder {
227 TextWriterBuilder::new()
228 }
229}
230
231impl<D: Draw> MakeTextWriter<D> {
232 #[must_use]
233 pub fn new(mk: fn() -> D) -> Self {
234 Self::build(mk, TextWriterBuilder::new())
235 }
236
237 fn build(mk: fn() -> D, settings: TextWriterBuilder) -> Self {
238 let (pixel_width, pixel_height) = {
239 let buf = (mk)();
240 (buf.width() as u32, buf.height() as u32)
241 };
242 let text_style = MonoTextStyle::new(settings.get_font(), pixelcolor::Rgb888::WHITE);
243 let line_len = Self::line_len(pixel_width, &text_style);
244 let char_height = text_style.font.character_size.height;
245 let last_line = (pixel_height - char_height - 10) as i32;
246 Self {
247 settings,
248 next_point: AtomicU64::new(pack_point(settings.start_point)),
249 char_height,
250 mk,
251 line_len,
252 last_line,
253 }
254 }
255
256 fn line_len(pixel_width: u32, text_style: &MonoTextStyle<'static, pixelcolor::Rgb888>) -> u32 {
257 pixel_width / text_style.font.character_size.width
258 }
259}
260
261impl<'a, D> crate::writer::MakeWriter<'a> for MakeTextWriter<D>
262where
263 D: Draw + 'a,
264{
265 type Writer = TextWriter<'a, D>;
266
267 fn make_writer(&'a self) -> Self::Writer {
268 TextWriter {
269 color: self.settings.default_color,
270 target: (self.mk)().into_draw_target(),
271 mk: self,
272 }
273 }
274
275 fn line_len(&self) -> usize {
276 self.line_len as usize
277 }
278}
279
280impl TextWriterBuilder {
281 #[must_use]
282 pub const fn new() -> Self {
283 Self {
284 default_color: Color::White,
290 start_point: Point { x: 10, y: 10 },
291 }
292 }
293
294 #[must_use]
300 pub fn default_color(self, default_color: Color) -> Self {
301 Self {
302 default_color,
303 ..self
304 }
305 }
306
307 #[must_use]
308 pub fn starting_point(self, start_point: Point) -> Self {
309 Self {
310 start_point,
311 ..self
312 }
313 }
314
315 #[must_use]
316 pub fn build<D: Draw>(self, mk: fn() -> D) -> MakeTextWriter<D> {
317 MakeTextWriter::build(mk, self)
318 }
319
320 fn get_font(&self) -> &'static MonoFont<'static> {
325 &mono_font::ascii::FONT_6X13
326 }
327}
328
329impl Default for TextWriterBuilder {
330 fn default() -> Self {
331 Self::new()
332 }
333}