1use alloc::string::ToString as _;
2use core::{fmt::Display, marker::PhantomData};
3
4use embedded_graphics::mono_font::{MonoTextStyle, MonoTextStyleBuilder};
5use embedded_text::{style::TextBoxStyleBuilder, TextBox};
6
7use crate::{
8 align::{HorizontalAlign, VerticalAlign},
9 el::El,
10 event::Event,
11 font::Font,
12 layout::{Layout, Viewport},
13 render::Renderer,
14 size::{Length, Size},
15 state::StateNode,
16 ui::UiCtx,
17 value::Value,
18 widget::Widget,
19};
20
21use crate::color::UiColor;
22
23#[derive(Clone, Copy, Debug)]
24pub enum LineHeight {
25 Pixels(u32),
26 Percent(u32),
27}
28
29impl Into<embedded_graphics::text::LineHeight> for LineHeight {
30 fn into(self) -> embedded_graphics::text::LineHeight {
31 match self {
32 LineHeight::Pixels(pixels) => embedded_graphics::text::LineHeight::Pixels(pixels),
33 LineHeight::Percent(percent) => embedded_graphics::text::LineHeight::Percent(percent),
34 }
35 }
36}
37
38impl Default for LineHeight {
39 fn default() -> Self {
40 Self::Percent(100)
41 }
42}
43
44impl From<u32> for LineHeight {
45 fn from(value: u32) -> Self {
46 Self::Pixels(value)
47 }
48}
49
50impl From<f32> for LineHeight {
51 fn from(value: f32) -> Self {
52 Self::Percent((value.clamp(0.0, 1.0) * 100.0) as u32)
53 }
54}
55
56pub struct Text<'a, T, R>
57where
58 R: Renderer,
59 T: Display,
60{
61 content: Value<T>,
62 marker: PhantomData<&'a str>,
63
64 align: HorizontalAlign,
66 vertical_align: VerticalAlign,
67 line_height: LineHeight,
68 text_color: R::Color,
69 font: Font,
70
71 size: Size<Length>,
73}
74
75impl<'a, T: Display, R: Renderer> Text<'a, T, R> {
76 pub fn new(content: Value<T>) -> Self {
77 let font = Font::default();
78
79 Self {
80 content,
81 marker: PhantomData,
82 text_color: R::Color::default_foreground(),
83 line_height: LineHeight::default(),
84 align: HorizontalAlign::Center,
85 vertical_align: VerticalAlign::Center,
86 font,
87 size: Size::fill(),
88 }
89 }
90
91 pub fn text_color(mut self, text_color: R::Color) -> Self {
92 self.text_color = text_color;
93 self
94 }
95
96 pub fn align(mut self, align: HorizontalAlign) -> Self {
97 self.align = align;
98 self
99 }
100
101 pub fn vertical_align(mut self, vertical_align: VerticalAlign) -> Self {
102 self.vertical_align = vertical_align;
103 self
104 }
105
106 pub fn line_height(mut self, line_height: impl Into<LineHeight>) -> Self {
107 self.line_height = line_height.into();
108 self
109 }
110
111 pub fn update(&mut self, new_value: T) {
112 *self.content.get_mut() = new_value;
113 }
114
115 fn char_style(&self) -> MonoTextStyle<'static, R::Color> {
116 match &self.font {
117 Font::Mono(mono) => {
118 MonoTextStyleBuilder::new().font(&mono).text_color(self.text_color).build()
119 },
120 }
121 }
122}
123
124impl<'a, T, Message, R, E: Event, S> Widget<Message, R, E, S> for Text<'a, T, R>
125where
126 T: Display,
127 R: Renderer,
128{
129 fn id(&self) -> Option<crate::el::ElId> {
130 None
131 }
132
133 fn tree_ids(&self) -> alloc::vec::Vec<crate::el::ElId> {
134 vec![]
135 }
136
137 fn size(&self) -> Size<Length> {
138 self.size.into()
139 }
140
141 fn layout(
142 &self,
143 _ctx: &mut UiCtx<Message>,
144 _state_tree: &mut StateNode,
145 _styler: &S,
146 limits: &crate::layout::Limits,
147 viewport: &Viewport,
148 ) -> crate::layout::LayoutNode {
149 Layout::sized(limits, self.size, crate::layout::Position::Relative, viewport, |limits| {
150 let text_size = self.font.measure_text_size(&self.content.get().to_string());
151 limits.resolve_size(self.size.width, self.size.height, text_size)
152 })
153 }
154
155 fn draw(
156 &self,
157 _ctx: &mut UiCtx<Message>,
158 _state_tree: &mut StateNode,
159 renderer: &mut R,
160 _styler: &S,
161 layout: Layout,
162 ) {
163 renderer.mono_text(TextBox::with_textbox_style(
164 &self.content.get().to_string(),
165 layout.bounds().into(),
166 self.char_style(),
167 TextBoxStyleBuilder::new()
168 .alignment(self.align.into())
169 .vertical_alignment(self.vertical_align.into())
170 .line_height(self.line_height.into())
171 .build(),
172 ))
173 }
174}
175
176impl<'a, T, R> From<Value<T>> for Text<'a, T, R>
177where
178 T: Display + 'a,
179 R: Renderer,
180{
181 fn from(value: Value<T>) -> Self {
182 Text::new(value)
183 }
184}
185
186impl<'a, T, R: Renderer> From<T> for Text<'a, T, R>
193where
194 R: Renderer,
195 T: Display + 'a,
196{
197 fn from(value: T) -> Self {
198 Text::new(Value::new(value))
199 }
200}
201
202impl<'a, Message, R, E, S> From<&'a str> for El<'a, Message, R, E, S>
203where
204 Message: 'a,
205 R: Renderer + 'a,
206 E: Event + 'a,
207 S: 'a,
208{
209 fn from(value: &'a str) -> Self {
210 Text::new(Value::new(value)).into()
211 }
212}
213
214impl<'a, T, Message, R, E, S> From<Text<'a, T, R>> for El<'a, Message, R, E, S>
215where
216 T: Display + 'a,
217 Message: 'a,
218 R: Renderer + 'a,
219 E: Event + 'a,
220 S: 'a,
221{
222 fn from(value: Text<'a, T, R>) -> Self {
223 Self::new(value)
224 }
225}
226
227