shellshot/
window_decoration.rs

1use crate::{
2    image_renderer::{ImageRendererError, canvas::Canvas, render_size::Size},
3    window_decoration::no_decoration::NoDecoration,
4};
5
6mod classic;
7pub mod common;
8mod no_decoration;
9
10use ab_glyph::FontArc;
11use clap::ValueEnum;
12pub use classic::Classic;
13use image::Rgba;
14use termwiz::cell::Cell;
15
16/// Type of window decoration to apply around the rendered content
17#[derive(Clone, Debug, ValueEnum)]
18pub enum WindowDecorationType {
19    /// Classic window decoration
20    Classic,
21}
22
23#[derive(Clone, Debug)]
24pub struct WindowMetrics {
25    pub padding: u32,
26    pub border_width: u32,
27    pub title_bar_height: u32,
28}
29
30#[derive(Debug, Clone)]
31pub struct Fonts {
32    pub regular: FontArc,
33    pub bold: FontArc,
34    pub italic: FontArc,
35    pub bold_italic: FontArc,
36}
37
38pub trait WindowDecoration: std::fmt::Debug {
39    fn build_command_line(&self, command: &str) -> Vec<Cell>;
40
41    fn compute_metrics(&self, char_size: Size) -> WindowMetrics;
42
43    fn get_color_palette(&self) -> [Rgba<u8>; 256];
44
45    fn font(&self) -> Result<Fonts, ImageRendererError>;
46
47    fn draw_window(
48        &self,
49        canvas: &mut Canvas,
50        metrics: &WindowMetrics,
51    ) -> Result<(), ImageRendererError>;
52}
53
54pub fn create_window_decoration(
55    decoration_type: Option<&WindowDecorationType>,
56) -> Box<dyn WindowDecoration> {
57    match decoration_type {
58        Some(WindowDecorationType::Classic) => Box::new(Classic),
59        None => Box::new(NoDecoration),
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use ab_glyph::PxScale;
66
67    use crate::image_renderer::render_size::calculate_char_size;
68
69    use super::*;
70
71    fn all_window_decorations() -> Vec<Option<WindowDecorationType>> {
72        let mut types = WindowDecorationType::value_variants()
73            .iter()
74            .cloned()
75            .map(Some)
76            .collect::<Vec<_>>();
77        types.push(None);
78        types
79    }
80
81    #[test]
82    fn test_all_window_decorations_command_line() {
83        for decoration_type in all_window_decorations() {
84            let window_decoration = create_window_decoration(decoration_type.as_ref());
85            let command_line = window_decoration.build_command_line("echo test");
86
87            assert!(
88                !command_line.is_empty(),
89                "Unexpected number of cells for {decoration_type:?}",
90            );
91        }
92    }
93
94    #[test]
95    fn test_all_window_decorations_colors() {
96        for decoration_type in all_window_decorations() {
97            let window_decoration = create_window_decoration(decoration_type.as_ref());
98
99            let fg = window_decoration.get_color_palette()[7];
100            assert!(fg.0[3] > 0, "Alpha channel must be > 0");
101
102            let palette = window_decoration.get_color_palette();
103            assert_eq!(palette.len(), 256, "Palette must have 256 colors");
104        }
105    }
106
107    #[test]
108    fn test_all_window_decorations_draw() {
109        let canvas_width = 200;
110        let canvas_height = 100;
111        let scale = PxScale::from(1.0);
112
113        for decoration_type in all_window_decorations() {
114            let window_decoration = create_window_decoration(decoration_type.as_ref());
115
116            let font = window_decoration.font().expect("Font should be available");
117
118            let char_size = calculate_char_size(&font.regular, scale);
119            let metrics = window_decoration.compute_metrics(char_size);
120
121            let mut canvas = Canvas::new(canvas_width, canvas_height, font.clone(), scale)
122                .expect("Failed to create Canvas");
123
124            let result = window_decoration.draw_window(&mut canvas, &metrics);
125            assert!(
126                result.is_ok(),
127                "draw_window failed for {decoration_type:?}: {result:?}",
128            );
129        }
130    }
131}