use crossterm::{
cursor::MoveTo,
queue,
style::{Color, Print, SetForegroundColor},
};
use super::error::RendererError;
use super::scroll::ScrollState;
#[derive(Debug)]
pub struct StatusBar {
width: u16,
height: u16,
current_theme: String,
current_pattern: String,
fps: f64,
show_fps: bool,
}
impl StatusBar {
pub fn new(term_size: (u16, u16)) -> Self {
Self {
width: term_size.0,
height: term_size.1,
current_theme: String::from("rainbow"),
current_pattern: String::from("diagonal"),
fps: 0.0,
show_fps: true,
}
}
pub fn set_theme(&mut self, theme: &str) {
self.current_theme = theme.to_string();
}
pub fn set_pattern(&mut self, pattern: &str) {
self.current_pattern = pattern.to_string();
}
pub fn set_fps(&mut self, fps: f64) {
if (self.fps - fps).abs() > 0.5 {
self.fps = fps;
}
}
pub fn show_fps(&mut self, show: bool) {
self.show_fps = show;
}
pub fn render(
&mut self,
stdout: &mut std::io::StdoutLock,
scroll: &ScrollState,
) -> Result<(), RendererError> {
let separator_color = Color::Rgb {
r: 40,
g: 44,
b: 52,
};
let accent_color = Color::Rgb {
r: 97,
g: 175,
b: 239,
};
let text_color = Color::Rgb {
r: 171,
g: 178,
b: 191,
};
let muted_color = Color::Rgb {
r: 92,
g: 99,
b: 112,
};
queue!(
stdout,
MoveTo(0, self.height - 2),
Print("\x1b[K"),
SetForegroundColor(separator_color),
Print("─".repeat(self.width as usize))
)?;
let (start, end) = scroll.get_visible_range();
let mut left_section = format!(" {} • {}", self.current_theme, self.current_pattern);
if self.show_fps {
left_section.push_str(&format!(" • {:.1} FPS", self.fps));
}
let middle_section = "[T]heme [P]attern";
let right_section = format!(
"Lines {}-{}/{} [Q]uit ",
start + 1,
end,
scroll.total_lines()
);
let total_width = self.width as usize;
let left_width = left_section.chars().count();
let middle_width = middle_section.chars().count();
let right_width = right_section.chars().count();
queue!(stdout, MoveTo(0, self.height - 1), Print("\x1b[K"))?;
let available_width = total_width.saturating_sub(2);
if left_width + middle_width + right_width <= available_width {
queue!(
stdout,
SetForegroundColor(accent_color),
Print(&left_section),
SetForegroundColor(text_color),
MoveTo(
(self.width as usize / 2 - middle_width / 2) as u16,
self.height - 1
),
Print(middle_section),
SetForegroundColor(muted_color),
MoveTo(
self.width.saturating_sub(right_width as u16),
self.height - 1
),
Print(right_section),
)?;
} else if left_width + right_width <= available_width {
queue!(
stdout,
SetForegroundColor(accent_color),
Print(&left_section),
SetForegroundColor(muted_color),
MoveTo(
self.width.saturating_sub(right_width as u16),
self.height - 1
),
Print(right_section),
)?;
} else {
let max_width = available_width.saturating_sub(3);
let mut minimal_info = format!(" {}…", self.current_theme);
if minimal_info.chars().count() > max_width {
minimal_info = format!(
" {}…",
self.current_theme
.chars()
.take(max_width - 2)
.collect::<String>()
);
}
queue!(
stdout,
SetForegroundColor(accent_color),
Print(minimal_info)
)?;
}
queue!(stdout, SetForegroundColor(Color::Reset))?;
Ok(())
}
pub fn resize(&mut self, new_size: (u16, u16)) {
self.width = new_size.0;
self.height = new_size.1;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_status_bar_creation() {
let status_bar = StatusBar::new((80, 24));
assert_eq!(status_bar.width, 80);
assert_eq!(status_bar.height, 24);
}
#[test]
fn test_theme_pattern_update() {
let mut status_bar = StatusBar::new((80, 24));
status_bar.set_theme("ocean");
assert_eq!(status_bar.current_theme, "ocean");
status_bar.set_pattern("wave");
assert_eq!(status_bar.current_pattern, "wave");
}
#[test]
fn test_fps_update() {
let mut status_bar = StatusBar::new((80, 24));
status_bar.set_fps(60.0);
assert_eq!(status_bar.fps, 60.0);
status_bar.set_fps(60.2);
assert_eq!(status_bar.fps, 60.0);
status_bar.show_fps(false);
assert!(!status_bar.show_fps);
}
#[test]
fn test_resize() {
let mut status_bar = StatusBar::new((80, 24));
status_bar.resize((100, 30));
assert_eq!(status_bar.width, 100);
assert_eq!(status_bar.height, 30);
}
}
impl Default for StatusBar {
fn default() -> Self {
Self::new((80, 24)) }
}