Skip to main content

retroglyph_ui/widget/
text.rs

1//! [`Text`]: a single line of plain text in one [`Style`].
2use retroglyph_core::color::Style;
3
4use super::Widget;
5use crate::Align;
6use crate::Surface;
7use crate::text::draw_clipped;
8
9/// A single line of text in one [`Style`], clipped (not wrapped) to
10/// `area.width()` columns. Only the first row of `area` is used.
11///
12/// The plain-content cousin of [`PrintLine`](super::PrintLine) (which
13/// prints a multi-span [`Line`](retroglyph_core::text::Line), for mixed
14/// styling within one line) and [`Paragraph`](super::Paragraph) (which
15/// word-wraps across multiple lines): reach
16/// for `Text` for a single already-one-line label or readout in a single
17/// style, with no wrapping and no per-span styling. `style` defaults to
18/// [`Style::new()`] and `align` to [`Align::Left`]; set them with
19/// [`Text::style`]/[`Text::align`].
20///
21/// Unlike [`super::BoxBorder`], [`super::Gauge`], [`super::StatBar`],
22/// [`super::Table`], and [`super::Button`], `Text` has no `theme()`/
23/// `theme_on()` pair: a line of plain text has no single semantic
24/// [`Theme`](crate::Theme) role to map onto, so callers set `style` directly.
25///
26/// # Examples
27///
28/// ```
29/// use retroglyph_core::grid::{Grid, Rect};
30/// use retroglyph_ui::{Align, Surface, Text, Widget};
31///
32/// let area = Rect::new(0, 0, 10, 1);
33/// let mut grid = Grid::new(10, 1);
34/// Text::new("OK")
35///     .align(Align::Right)
36///     .render(&mut Surface::new(&mut grid, area, 0));
37/// ```
38#[derive(Clone, Copy, Debug)]
39pub struct Text<'a> {
40    content: &'a str,
41    style: Style,
42    align: Align,
43}
44
45impl<'a> Text<'a> {
46    /// A line of `content` in the default style, left-aligned.
47    #[must_use]
48    pub fn new(content: &'a str) -> Self {
49        Self {
50            content,
51            style: Style::new(),
52            align: Align::Left,
53        }
54    }
55
56    /// Set the text's style.
57    #[must_use]
58    pub const fn style(mut self, style: Style) -> Self {
59        self.style = style;
60        self
61    }
62
63    /// Set how the line is aligned within `area.width()` columns.
64    #[must_use]
65    pub const fn align(mut self, align: Align) -> Self {
66        self.align = align;
67        self
68    }
69}
70
71impl Widget for Text<'_> {
72    fn render(&self, surface: &mut Surface<'_>) {
73        let width = surface.width();
74        if width == 0 {
75            return;
76        }
77        let _ = draw_clipped(surface, (0, 0), width, self.content, self.align, self.style);
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use retroglyph_core::color::Color;
84    use retroglyph_core::grid::{Grid, Pos, Rect};
85
86    use super::*;
87
88    #[test]
89    fn prints_the_content_in_the_given_style() {
90        let area = Rect::new(0, 0, 10, 1);
91        let mut grid = Grid::new(10, 1);
92        Text::new("hi")
93            .style(Style::new().fg(Color::WHITE))
94            .render(&mut Surface::new(&mut grid, area, 0));
95
96        assert_eq!(grid[Pos::new(0, 0)].glyph(), 'h');
97        assert_eq!(grid[Pos::new(1, 0)].glyph(), 'i');
98        assert_eq!(grid[Pos::new(0, 0)].style().foreground(), Color::WHITE);
99    }
100
101    #[test]
102    fn clips_to_area_width() {
103        let area = Rect::new(0, 0, 5, 1);
104        let mut grid = Grid::new(5, 1);
105        Text::new("a much longer message than fits").render(&mut Surface::new(&mut grid, area, 0));
106
107        assert_eq!(grid[Pos::new(4, 0)].glyph(), 'c'); // "a muc"
108    }
109
110    #[test]
111    fn right_align_places_text_against_the_right_edge() {
112        let area = Rect::new(0, 0, 10, 1);
113        let mut grid = Grid::new(10, 1);
114        Text::new("hi")
115            .align(Align::Right)
116            .render(&mut Surface::new(&mut grid, area, 0));
117
118        // "hi" (2 cols) in 10 cols, right-aligned: starts at column 8.
119        assert_eq!(grid[Pos::new(8, 0)].glyph(), 'h');
120        assert_eq!(grid[Pos::new(9, 0)].glyph(), 'i');
121        assert_eq!(grid[Pos::new(7, 0)].glyph(), ' ');
122    }
123
124    #[test]
125    fn center_align_centers_text() {
126        let area = Rect::new(0, 0, 10, 1);
127        let mut grid = Grid::new(10, 1);
128        Text::new("hi")
129            .align(Align::Center)
130            .render(&mut Surface::new(&mut grid, area, 0));
131
132        // 8 cols slack, 4 on the left: "hi" starts at column 4.
133        assert_eq!(grid[Pos::new(4, 0)].glyph(), 'h');
134        assert_eq!(grid[Pos::new(5, 0)].glyph(), 'i');
135    }
136
137    #[test]
138    fn zero_width_is_a_no_op() {
139        let area = Rect::new(0, 0, 0, 1);
140        let mut grid = Grid::new(1, 1);
141        Text::new("hi").render(&mut Surface::new(&mut grid, area, 0));
142        assert_eq!(grid[Pos::new(0, 0)].glyph(), ' ');
143    }
144}