Skip to main content

matrix_gui/widgets/
plaintext.rs

1//! Plain text widget for displaying multi-line text.
2//!
3//! This module provides a text widget that displays multi-line text
4//! with support for paragraph spacing, padding, and borders.
5
6use embedded_text::{
7    TextBox,
8    alignment::HorizontalAlignment,
9    style::{HeightMode, TextBoxStyleBuilder},
10};
11
12use crate::prelude::*;
13
14/// Multi-line text widget with advanced formatting.
15///
16/// This widget displays multi-line text with support for paragraph spacing,
17/// inner and outer padding, and optional borders. It uses the
18/// `embedded_text` crate for text rendering with automatic line wrapping.
19///
20/// # Type Parameters
21///
22/// * `'a` - The lifetime of the text string reference
23/// * `ID` - The widget ID type implementing [`WidgetId`]
24/// * `COL` - The pixel color type implementing [`PixelColor`]
25pub struct PlainText<'a, ID, COL: PixelColor> {
26    /// The region defining the text's position and size.
27    region: &'a Region<ID>,
28    /// The text string to display (may contain newlines).
29    text: &'a str,
30    /// Optional font for rendering the text.
31    font: OptionFont<'a>,
32    /// Horizontal alignment for the text.
33    align: HorizontalAlign,
34    /// Optional color for the text.
35    color: OptionColor<COL>,
36    /// Spacing between paragraphs (in pixels).
37    paragraph_spacing: u8,
38    /// Inner padding around the text content (in pixels).
39    padding_inner: u8,
40    /// Outer padding around the entire widget (in pixels).
41    padding_outer: u8,
42    /// Border width (in pixels, 0 for no border).
43    border_width: u8,
44}
45
46impl<'a, ID: WidgetId, COL: PixelColor> PlainText<'a, ID, COL> {
47    pub const fn new(region: &'a Region<ID>, text: &'a str) -> PlainText<'a, ID, COL> {
48        PlainText {
49            region,
50            text,
51            font: OptionFont::none(),
52            align: HorizontalAlign::Left,
53            color: OptionColor::none(),
54            paragraph_spacing: 0,
55            padding_inner: 0,
56            padding_outer: 0,
57            border_width: 0,
58        }
59    }
60
61    pub const fn with_font(mut self, font: UiFont<'a>) -> Self {
62        self.font.set_font(font);
63        self
64    }
65
66    pub const fn with_align(mut self, align: HorizontalAlign) -> Self {
67        self.align = align;
68        self
69    }
70
71    pub const fn with_color(mut self, color: COL) -> Self {
72        self.color.set_color(color);
73        self
74    }
75
76    pub const fn with_paragraph_spacing(mut self, paragraph_spacing: u8) -> Self {
77        self.paragraph_spacing = paragraph_spacing;
78        self
79    }
80
81    pub const fn with_padding(mut self, inner: u8, outer: u8) -> Self {
82        self.padding_inner = inner;
83        self.padding_outer = outer;
84        self
85    }
86
87    pub const fn with_border(mut self, border_width: u8) -> Self {
88        self.border_width = border_width;
89        self
90    }
91}
92
93impl<DRAW: DrawTarget<Color = COL>, ID: WidgetId, COL: PixelColor> Widget<DRAW, COL>
94    for PlainText<'_, ID, COL>
95{
96    fn draw(&mut self, ui: &mut Ui<DRAW, COL>) -> GuiResult<Response> {
97        let widget_state = ui.get_widget_state(self.region.id())?;
98        if widget_state.compare_set(RenderStatus::Rendered) {
99            return Ok(Response::Idle);
100        }
101
102        let mut area = self.region.rectangle();
103        if self.padding_outer > 0 {
104            area = area.offset(-(self.padding_outer as i32));
105        }
106        if self.border_width > 0 {
107            let rect_style = PrimitiveStyleBuilder::new()
108                .stroke_color(ui.style().border_color)
109                .stroke_width(self.border_width as u32)
110                .build();
111            ui.draw(&area.into_styled(rect_style)).ok();
112        }
113
114        area = matrix_utils::select(
115            self.padding_inner > 0,
116            area.offset(-(self.padding_inner as i32)),
117            area.offset(-(ui.style().default_padding.width as i32)),
118        );
119
120        let font = self.font.font(ui.style());
121        let color = self.color.text_color(ui.style());
122
123        let character_style = UiTextStyle::new(font, color);
124        let textbox_style = TextBoxStyleBuilder::new()
125            .height_mode(HeightMode::FitToText)
126            .alignment(self.align.into())
127            .paragraph_spacing(self.paragraph_spacing as u32)
128            .build();
129
130        let text_box = TextBox::with_textbox_style(self.text, area, character_style, textbox_style);
131
132        ui.clear_area(&area)?;
133        ui.draw(&text_box)?;
134
135        Ok(Response::Idle)
136    }
137}
138
139impl From<HorizontalAlign> for HorizontalAlignment {
140    fn from(value: HorizontalAlign) -> Self {
141        match value {
142            HorizontalAlign::Left => Self::Left,
143            HorizontalAlign::Center => Self::Center,
144            HorizontalAlign::Right => Self::Right,
145            HorizontalAlign::Justify => Self::Justified,
146        }
147    }
148}