use ratatui::{ crossterm::event::{KeyCode, KeyEvent}, layout::{Alignment, Rect}, style::{Style, Stylize}, text::{ToLine, ToSpan}, widgets::Widget, }; #[derive(Clone, Default)] pub struct StatusBar { left: String, left_style: Style, middle: String, middle_style: Style, middle_alignment: Alignment, right: String, right_style: Style, input: Option, cursor: u16, } impl StatusBar { pub fn new() -> Self { Self { left: String::new(), left_style: Style::default(), middle: String::new(), middle_style: Style::default(), middle_alignment: Alignment::Center, right: String::new(), right_style: Style::default(), input: None, cursor: 0, } } pub fn left(mut self, text: S) -> Self where S: AsRef, { self.left = text.as_ref().to_string(); self } pub fn middle(mut self, text: S) -> Self where S: AsRef, { self.middle = text.as_ref().to_string(); self } pub fn right(mut self, text: S) -> Self where S: AsRef, { self.right = text.as_ref().to_string(); self } pub fn left_style(mut self, style: Style) -> Self { self.left_style = style; self } pub fn middle_style(mut self, style: Style) -> Self { self.left_style = style; self } pub fn middle_alignment(mut self, alignment: Alignment) -> Self { self.middle_alignment = alignment; self } pub fn right_style(mut self, style: Style) -> Self { self.right_style = style; self } pub fn set_left(&mut self, text: S) where S: AsRef, { self.left = text.as_ref().to_string(); } pub fn set_middle(&mut self, text: S) where S: AsRef, { self.middle = text.as_ref().to_string(); } pub fn set_right(&mut self, text: S) where S: AsRef, { self.right = text.as_ref().to_string(); } pub fn set_left_style(&mut self, style: Style) { self.left_style = style; } pub fn set_middle_style(&mut self, style: Style) { self.left_style = style; } pub fn set_right_style(&mut self, style: Style) { self.right_style = style; } pub fn set_middle_alignment(&mut self, alignment: Alignment) { self.middle_alignment = alignment; } pub fn set_input_mode(&mut self, input: bool) { if input { self.input = Some(String::new()); self.cursor = 0; } else { self.input = None; } } pub fn input(&self) -> Option<&str> { self.input.as_deref() } pub fn area(&self, rect: Rect) -> Rect { let mut inner = rect; inner.height -= 1; inner } pub fn handle_keyevent(&mut self, event: KeyEvent) { if let Some(input) = &mut self.input { match event.code { KeyCode::Char(c) => { input.insert(self.cursor as usize, c); self.cursor += 1; } KeyCode::Backspace => { if self.cursor > 0 { self.cursor -= 1; input.remove(self.cursor as usize).to_string(); } } KeyCode::Left => { self.cursor = (self.cursor as i32 - 1).max(0) as u16; } KeyCode::Right => { self.cursor = (self.cursor + 1).min(input.len() as u16); } _ => {} } } } fn layout(&self, mut area: Rect) -> (Rect, Rect, Rect) { area.y += area.height - 1; area.height = 1; let mut left = area; let mut right = area; let mut middle = area; left.width = self.left.len() as u16; right.width = self.right.len() as u16; right.x = area.x + area.width - right.width; middle.x = left.x + left.width; middle.width = area.width - left.width - right.width; (left, middle, right) } } impl Widget for &mut StatusBar { fn render(self, area: Rect, buf: &mut ratatui::prelude::Buffer) where Self: Sized, { let (left, middle, right) = self.layout(area); self.left .to_line() .left_aligned() .style(self.left_style) .render(left, buf); if let Some(input) = &self.input { (":".to_owned() + input) .to_line() .alignment(Alignment::Left) .style(self.middle_style) .render(middle, buf); input .chars() .nth(self.cursor as usize) .unwrap_or(' ') .to_span() .reversed() .render( Rect::new( middle.x + 1 + self.cursor, middle.y, middle.width, middle.height, ), buf, ); } else { self.middle .to_line() .alignment(self.middle_alignment) .style(self.middle_style) .render(middle, buf); } self.right .to_line() .right_aligned() .style(self.right_style) .render(right, buf); } }