1use crossterm::style::{Attribute, Color, ContentStyle, StyledContent, Stylize};
2
3use crate::State;
4
5#[derive(Clone, Debug)]
7pub enum StatusBarLayoutItem {
8 Text(String),
10 Persentage,
12 LineCount,
14 CurrentLine,
16 Title,
20}
21
22#[derive(Clone, Debug)]
24pub struct StatusBarLayout {
25 pub left: Vec<StatusBarLayoutItem>,
27
28 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 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#[derive(Clone, Debug)]
75pub struct StatusBar {
76 pub line_layouts: Vec<StatusBarLayout>,
78 pub title: String,
80 pub theme: ContentStyle,
84}
85
86impl StatusBar {
87 pub fn new(title: String) -> Self {
89 Self {
90 title,
91 ..Default::default()
92 }
93 }
94
95 pub fn with_theme(title: String, theme: ContentStyle) -> Self {
97 Self {
98 title,
99 theme,
100 ..Default::default()
101 }
102 }
103
104 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}