Skip to main content

rich/
screen.rs

1//! A full-screen renderable.
2//!
3//! Port of `rich/screen.py`. [`Screen`] fills the entire console region
4//! (width × height), rendering its child into it and cropping/padding to an
5//! exact rectangle, optionally under a background [`Style`]. Used with the
6//! alternate screen buffer (see [`Control::alt_screen`](crate::control::Control)).
7
8use crate::console::{Console, ConsoleOptions};
9use crate::protocol::Renderable;
10use crate::segment::Segment;
11use crate::style::Style;
12
13/// A renderable that fills the screen and crops excess. Mirrors
14/// `rich.screen.Screen`.
15pub struct Screen {
16    renderable: Option<Box<dyn Renderable>>,
17    style: Option<Style>,
18}
19
20impl Screen {
21    /// A screen filled with `renderable`.
22    pub fn new(renderable: Box<dyn Renderable>) -> Self {
23        Screen {
24            renderable: Some(renderable),
25            style: None,
26        }
27    }
28
29    /// An empty screen (blank fill).
30    pub fn empty() -> Self {
31        Screen {
32            renderable: None,
33            style: None,
34        }
35    }
36
37    /// Set a background style applied under the whole screen.
38    pub fn style(mut self, style: Style) -> Self {
39        self.style = Some(style);
40        self
41    }
42}
43
44impl Renderable for Screen {
45    fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
46        let width = options.max_width;
47        let height = options.height.unwrap_or_else(|| console.height());
48
49        let lines = match &self.renderable {
50            Some(renderable) => {
51                let child_options = options.update_dimensions(width, height);
52                console.render_lines(renderable.as_ref(), &child_options, true)
53            }
54            None => Vec::new(),
55        };
56        let mut lines = Segment::set_shape(lines, width, height);
57
58        // Lay the background style under every cell (content and blank fill).
59        if let Some(style) = &self.style {
60            for line in &mut lines {
61                *line = Segment::apply_style(line, style);
62            }
63        }
64
65        let mut segments = Vec::new();
66        let last = lines.len().saturating_sub(1);
67        for (index, line) in lines.into_iter().enumerate() {
68            segments.extend(line);
69            if index != last {
70                segments.push(Segment::line());
71            }
72        }
73        segments
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80    use crate::color::ColorSystem;
81    use crate::text::Text;
82
83    fn console(width: usize, height: usize) -> Console {
84        Console::builder()
85            .force_terminal(true)
86            .color_system(Some(ColorSystem::Truecolor))
87            .width(width)
88            .height(height)
89            .build()
90    }
91
92    #[test]
93    fn fills_to_width_and_height() {
94        let screen = Screen::new(Box::new(Text::new("hi")));
95        let out = console(6, 3).capture(|c| c.print(&screen));
96        // 3 rows of 6 cells: "hi" then two blank rows. (Upstream's print omits
97        // the final row separator for a full-height Screen — DIVERGENCES #12.)
98        assert_eq!(out, "hi    \n      \n      \n");
99    }
100
101    #[test]
102    fn empty_screen_is_blank_rectangle() {
103        let out = console(4, 2).capture(|c| c.print(&Screen::empty()));
104        assert_eq!(out, "    \n    \n");
105    }
106}