Skip to main content

saorsa_core/widget/
static_widget.rs

1//! Static widget — displays pre-rendered content.
2
3use crate::buffer::ScreenBuffer;
4use crate::cell::Cell;
5use crate::geometry::Rect;
6use crate::segment::Segment;
7
8use super::Widget;
9
10/// A widget that displays pre-rendered segments without interaction.
11///
12/// Each segment becomes one "line" of content, rendered on successive rows.
13#[derive(Clone, Debug)]
14pub struct StaticWidget {
15    lines: Vec<Segment>,
16}
17
18impl StaticWidget {
19    /// Create a new static widget from a list of segments (one per line).
20    pub fn new(lines: Vec<Segment>) -> Self {
21        Self { lines }
22    }
23
24    /// Create a static widget from a single segment.
25    pub fn from_segment(segment: Segment) -> Self {
26        Self {
27            lines: vec![segment],
28        }
29    }
30
31    /// Get the lines.
32    pub fn lines(&self) -> &[Segment] {
33        &self.lines
34    }
35}
36
37impl Widget for StaticWidget {
38    fn render(&self, area: Rect, buf: &mut ScreenBuffer) {
39        if area.size.width == 0 || area.size.height == 0 {
40            return;
41        }
42
43        for (row, segment) in self.lines.iter().enumerate() {
44            let y = area.position.y + row as u16;
45            if y >= area.position.y + area.size.height {
46                break;
47            }
48
49            let mut col: u16 = 0;
50            for ch in segment.text.chars() {
51                let x = area.position.x + col;
52                if x >= area.position.x + area.size.width {
53                    break;
54                }
55                let ch_width = unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0);
56                buf.set(x, y, Cell::new(ch.to_string(), segment.style.clone()));
57                col += ch_width as u16;
58            }
59        }
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66    use crate::color::{Color, NamedColor};
67    use crate::geometry::Size;
68    use crate::style::Style;
69
70    #[test]
71    fn single_line() {
72        let seg = Segment::new("hello");
73        let w = StaticWidget::from_segment(seg);
74        let mut buf = ScreenBuffer::new(Size::new(10, 3));
75        w.render(Rect::new(0, 0, 10, 3), &mut buf);
76        assert_eq!(buf.get(0, 0).map(|c| c.grapheme.as_str()), Some("h"));
77        assert_eq!(buf.get(4, 0).map(|c| c.grapheme.as_str()), Some("o"));
78    }
79
80    #[test]
81    fn multi_line() {
82        let lines = vec![Segment::new("line1"), Segment::new("line2")];
83        let w = StaticWidget::new(lines);
84        let mut buf = ScreenBuffer::new(Size::new(10, 5));
85        w.render(Rect::new(0, 0, 10, 5), &mut buf);
86        assert_eq!(buf.get(0, 0).map(|c| c.grapheme.as_str()), Some("l"));
87        assert_eq!(buf.get(0, 1).map(|c| c.grapheme.as_str()), Some("l"));
88        assert_eq!(buf.get(4, 1).map(|c| c.grapheme.as_str()), Some("2"));
89    }
90
91    #[test]
92    fn styled_segment() {
93        let style = Style::new().fg(Color::Named(NamedColor::Green));
94        let seg = Segment::styled("X", style.clone());
95        let w = StaticWidget::from_segment(seg);
96        let mut buf = ScreenBuffer::new(Size::new(5, 1));
97        w.render(Rect::new(0, 0, 5, 1), &mut buf);
98        assert_eq!(buf.get(0, 0).map(|c| &c.style), Some(&style));
99    }
100
101    #[test]
102    fn truncates_to_area_width() {
103        let seg = Segment::new("very long text here");
104        let w = StaticWidget::from_segment(seg);
105        let mut buf = ScreenBuffer::new(Size::new(5, 1));
106        w.render(Rect::new(0, 0, 5, 1), &mut buf);
107        // Only first 5 chars should be written
108        assert_eq!(buf.get(4, 0).map(|c| c.grapheme.as_str()), Some(" "));
109    }
110
111    #[test]
112    fn truncates_to_area_height() {
113        let lines = vec![Segment::new("a"), Segment::new("b"), Segment::new("c")];
114        let w = StaticWidget::new(lines);
115        let mut buf = ScreenBuffer::new(Size::new(5, 2));
116        w.render(Rect::new(0, 0, 5, 2), &mut buf);
117        assert_eq!(buf.get(0, 0).map(|c| c.grapheme.as_str()), Some("a"));
118        assert_eq!(buf.get(0, 1).map(|c| c.grapheme.as_str()), Some("b"));
119        // Row 2 should not be written (area height is 2)
120    }
121
122    #[test]
123    fn empty_area() {
124        let w = StaticWidget::from_segment(Segment::new("x"));
125        let mut buf = ScreenBuffer::new(Size::new(10, 10));
126        w.render(Rect::new(0, 0, 0, 0), &mut buf);
127        // No crash
128    }
129}