pager_rs/
status_bar.rs

1use crossterm::style::{Attribute, Color, ContentStyle, StyledContent, Stylize};
2
3use crate::State;
4
5/// Layout items for StatusBar.
6#[derive(Clone, Debug)]
7pub enum StatusBarLayoutItem {
8    /// Display static text.
9    Text(String),
10    /// Display pesrsentage of current position to bottom.
11    Persentage,
12    /// Display total line count.
13    LineCount,
14    /// Display current line
15    CurrentLine,
16    /// Display title of [`StatusBar`]
17    ///
18    /// See: [`StatusBar::title`]
19    Title,
20}
21
22/// Layout for a [`StatusBar`] line.
23#[derive(Clone, Debug)]
24pub struct StatusBarLayout {
25    /// Items that sticked to the left.
26    pub left: Vec<StatusBarLayoutItem>,
27
28    /// Items that sticked to the left.
29    pub right: Vec<StatusBarLayoutItem>,
30}
31
32impl Default for StatusBarLayout {
33    fn default() -> Self {
34        use StatusBarLayoutItem::*;
35        Self {
36            left: vec![Title],
37            right: vec![
38                CurrentLine,
39                Text("/".to_string()),
40                LineCount,
41                Text(" (".to_string()),
42                Persentage,
43                Text("%)".to_string()),
44            ],
45        }
46    }
47}
48impl StatusBarLayout {
49    /// Get left and right parts as string.
50    fn get_parts(&self, state: &State) -> [String; 2] {
51        let content_line_count = state.content.lines().count();
52        [self.left.clone(), self.right.clone()].map(|part| {
53            let mut output = String::new();
54            for item in part {
55                output += &match item {
56                    StatusBarLayoutItem::Text(s) => s.clone(),
57                    StatusBarLayoutItem::Persentage => {
58                        format!(
59                            "{:.0}",
60                            ((state.pos.1 + 1) as f32 / content_line_count as f32) * 100.0
61                        )
62                    }
63                    StatusBarLayoutItem::LineCount => content_line_count.to_string(),
64                    StatusBarLayoutItem::CurrentLine => (state.pos.1 + 1).to_string(),
65                    StatusBarLayoutItem::Title => state.status_bar.title.clone(),
66                };
67            }
68            output
69        })
70    }
71}
72
73/// StatusBar defination
74#[derive(Clone, Debug)]
75pub struct StatusBar {
76    /// `Vec` of [`StatusBarLayout`] for each StatusBar line.
77    pub line_layouts: Vec<StatusBarLayout>,
78    /// Title of StatusBar. It will be used as [`StatusBarLayoutItem::Title`]
79    pub title: String,
80    /// Theme for StatusBar.
81    ///
82    /// See: [`ContentStyle`]
83    pub theme: ContentStyle,
84}
85
86impl StatusBar {
87    /// Create a [`StatusBar`] with title.
88    pub fn new(title: String) -> Self {
89        Self {
90            title,
91            ..Default::default()
92        }
93    }
94
95    /// Create a [`StatusBar`] with title and theme.
96    pub fn with_theme(title: String, theme: ContentStyle) -> Self {
97        Self {
98            title,
99            theme,
100            ..Default::default()
101        }
102    }
103
104    /// Get status bar text to be printed on terminal.
105    pub fn get_visible(&self, state: &State) -> StyledContent<String> {
106        let bar = self
107            .line_layouts
108            .iter()
109            .map(|layout| {
110                let parts = layout.get_parts(state);
111                let width = state.size.0 as usize;
112                if parts[0].len() > width {
113                    parts[0].chars().take(width).collect()
114                } else if parts[0].len() + parts[1].len() > width {
115                    format!(
116                        "{left}{gap}",
117                        left = parts[0],
118                        gap = " ".repeat(width - parts[0].len())
119                    )
120                } else {
121                    format!(
122                        "{left}{gap}{right}",
123                        left = parts[0].clone(),
124                        gap = " ".repeat(width - parts[0].len() - parts[1].len()),
125                        right = parts[1]
126                    )
127                }
128            })
129            .collect::<Vec<String>>()
130            .join("\n");
131        self.theme.apply(bar)
132    }
133}
134
135impl Default for StatusBar {
136    fn default() -> Self {
137        let theme = ContentStyle::new()
138            .with(Color::Black)
139            .on(Color::White)
140            .attribute(Attribute::Bold);
141        Self {
142            line_layouts: vec![StatusBarLayout::default()],
143            title: "***".to_string(),
144            theme,
145        }
146    }
147}