embedded_ui/kit/
text.rs

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    // style: TextStyle<R::Color>,
65    align: HorizontalAlign,
66    vertical_align: VerticalAlign,
67    line_height: LineHeight,
68    text_color: R::Color,
69    font: Font,
70
71    /// Precomputed size, does not need to be set by user
72    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
186// impl<'a, R: Renderer> From<&'a str> for Text<'a, &'a str, R> {
187//     fn from(value: &'a str) -> Self {
188//         Self::new(Value::new(value))
189//     }
190// }
191
192impl<'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// impl<'a, T, Message, R, E, S> From<T> for El<'a, Message, R, E, S>
228// where
229//     Message: 'a,
230//     R: Renderer + 'a,
231//     E: Event + 'a,
232//     S: 'a,
233//     T: ToString,
234// {
235//     fn from(value: T) -> Self {
236//         Text::from(value.to_string().as_str()).into()
237//     }
238// }
239
240// impl<'a, F, T, Message, R, E, S> From<F> for El<'a, Message, R, E, S>
241// where
242//     Message: 'a,
243//     R: Renderer + 'a,
244//     E: Event + 'a,
245//     S: 'a,
246//     F: FnMut() -> T,
247//     T: for<'b> Into<Text<'b, R>>,
248// {
249//     fn from(mut value: F) -> Self {
250//         Text::from((value)().into()).into()
251//     }
252// }
253
254// #[derive(Clone, Copy)]
255// pub struct TextStyle<C: UiColor> {
256//     pub font: Font,
257//     pub text_color: C,
258// }
259
260// #[derive(Clone, Copy)]
261// pub struct TextBox<'a, R: Renderer> {
262//     pub position: Point,
263//     pub align: HorizontalAlign,
264//     pub style: TextStyle<R::Color>,
265//     pub text: &'a str,
266// }