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::Surface;
6use crate::align::Align;
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::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::Align;
31/// use retroglyph_ui::widget::{Text, Widget};
32/// use retroglyph_ui::Surface;
33///
34/// let area = Rect::new(0, 0, 10, 1);
35/// let mut grid = Grid::new(10, 1);
36/// Text::new("OK")
37///     .align(Align::Right)
38///     .render(&mut Surface::new(&mut grid, area, 0));
39/// ```
40#[derive(Clone, Copy, Debug)]
41pub struct Text<'a> {
42    content: &'a str,
43    style: Style,
44    align: Align,
45}
46
47impl<'a> Text<'a> {
48    /// A line of `content` in the default style, left-aligned.
49    #[must_use]
50    pub fn new(content: &'a str) -> Self {
51        Self {
52            content,
53            style: Style::new(),
54            align: Align::Left,
55        }
56    }
57
58    /// Set the text's style.
59    #[must_use]
60    pub const fn style(mut self, style: Style) -> Self {
61        self.style = style;
62        self
63    }
64
65    /// Set how the line is aligned within `area.width()` columns.
66    #[must_use]
67    pub const fn align(mut self, align: Align) -> Self {
68        self.align = align;
69        self
70    }
71}
72
73impl Widget for Text<'_> {
74    fn render(&self, surface: &mut Surface<'_>) {
75        let width = surface.width();
76        if width == 0 {
77            return;
78        }
79        let _ = draw_clipped(surface, (0, 0), width, self.content, self.align, self.style);
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use retroglyph_core::color::Color;
86    use retroglyph_core::grid::{Grid, Pos, Rect};
87
88    use super::*;
89
90    #[test]
91    fn prints_the_content_in_the_given_style() {
92        let area = Rect::new(0, 0, 10, 1);
93        let mut grid = Grid::new(10, 1);
94        Text::new("hi")
95            .style(Style::new().fg(Color::WHITE))
96            .render(&mut Surface::new(&mut grid, area, 0));
97
98        assert_eq!(grid[Pos::new(0, 0)].glyph(), 'h');
99        assert_eq!(grid[Pos::new(1, 0)].glyph(), 'i');
100        assert_eq!(grid[Pos::new(0, 0)].style().foreground(), Color::WHITE);
101    }
102
103    #[test]
104    fn clips_to_area_width() {
105        let area = Rect::new(0, 0, 5, 1);
106        let mut grid = Grid::new(5, 1);
107        Text::new("a much longer message than fits").render(&mut Surface::new(&mut grid, area, 0));
108
109        assert_eq!(grid[Pos::new(4, 0)].glyph(), 'c'); // "a muc"
110    }
111
112    #[test]
113    fn right_align_places_text_against_the_right_edge() {
114        let area = Rect::new(0, 0, 10, 1);
115        let mut grid = Grid::new(10, 1);
116        Text::new("hi")
117            .align(Align::Right)
118            .render(&mut Surface::new(&mut grid, area, 0));
119
120        // "hi" (2 cols) in 10 cols, right-aligned: starts at column 8.
121        assert_eq!(grid[Pos::new(8, 0)].glyph(), 'h');
122        assert_eq!(grid[Pos::new(9, 0)].glyph(), 'i');
123        assert_eq!(grid[Pos::new(7, 0)].glyph(), ' ');
124    }
125
126    #[test]
127    fn center_align_centers_text() {
128        let area = Rect::new(0, 0, 10, 1);
129        let mut grid = Grid::new(10, 1);
130        Text::new("hi")
131            .align(Align::Center)
132            .render(&mut Surface::new(&mut grid, area, 0));
133
134        // 8 cols slack, 4 on the left: "hi" starts at column 4.
135        assert_eq!(grid[Pos::new(4, 0)].glyph(), 'h');
136        assert_eq!(grid[Pos::new(5, 0)].glyph(), 'i');
137    }
138
139    #[test]
140    fn zero_width_is_a_no_op() {
141        let area = Rect::new(0, 0, 0, 1);
142        let mut grid = Grid::new(1, 1);
143        Text::new("hi").render(&mut Surface::new(&mut grid, area, 0));
144        assert_eq!(grid[Pos::new(0, 0)].glyph(), ' ');
145    }
146}