diff options
| author | Nathan Reiner <nathan@nathanreiner.xyz> | 2024-07-28 12:50:02 +0200 |
|---|---|---|
| committer | Nathan Reiner <nathan@nathanreiner.xyz> | 2024-07-28 12:50:02 +0200 |
| commit | 417cee4eeeaf7516dfeb59cdbe34fed18f30e0f7 (patch) | |
| tree | 74354a7b14a65f4dc514b6ed0367945f69349bc4 | |
| parent | 6ca07d6af8a338e76817d06c6c6c6f13e64fba9c (diff) | |
add statusbar widget
| -rw-r--r-- | src/app.rs | 13 | ||||
| -rw-r--r-- | src/widgets/logview.rs | 27 | ||||
| -rw-r--r-- | src/widgets/luaeditor/buffer.rs | 8 | ||||
| -rw-r--r-- | src/widgets/luaeditor/cursor.rs | 2 | ||||
| -rw-r--r-- | src/widgets/luaeditor/mod.rs | 71 | ||||
| -rw-r--r-- | src/widgets/mod.rs | 1 | ||||
| -rw-r--r-- | src/widgets/sheetview/mod.rs | 229 | ||||
| -rw-r--r-- | src/widgets/statusbar.rs | 156 |
8 files changed, 367 insertions, 140 deletions
@@ -10,16 +10,15 @@ use crate::{ use ratatui::{ crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers}, prelude::*, - widgets::Block, }; -pub struct App<'a> { +pub struct App { exit: bool, - view: SheetView<'a>, - logview: Option<LogView<'a>>, + view: SheetView, + logview: Option<LogView>, } -impl App<'_> { +impl App { pub fn new() -> Self { let sheet_id = Register::create(200, 1000); let sheet = Register::get(sheet_id).unwrap(); @@ -81,7 +80,7 @@ impl App<'_> { match self.logview { Some(_) => self.logview = None, None => { - self.logview = Some(LogView::new().block(Block::bordered().title(" Log "))) + self.logview = Some(LogView::new()) } } } @@ -94,7 +93,7 @@ impl App<'_> { } } -impl Widget for &mut App<'_> { +impl Widget for &mut App { fn render(self, area: Rect, buf: &mut Buffer) where Self: Sized, diff --git a/src/widgets/logview.rs b/src/widgets/logview.rs index 4259e35..c5b6432 100644 --- a/src/widgets/logview.rs +++ b/src/widgets/logview.rs @@ -1,29 +1,28 @@ -use ratatui::{text::ToSpan, widgets::{block::BlockExt, Block, Widget}}; +use ratatui::{style::{Style, Stylize}, text::ToSpan, widgets::Widget}; use crate::lua::iobuffer::iobuffer; +use super::statusbar::StatusBar; -pub struct LogView<'a> { - block: Option<Block<'a>> +pub struct LogView { + bar: StatusBar, } -impl<'a> LogView<'a> { +impl LogView { pub fn new() -> Self { - Self { block: None } - } - - pub fn block(mut self, block: Block<'a>) -> Self { - self.block = Some(block); - self + Self { + bar: StatusBar::new().left(" Log ").left_style(Style::default().on_magenta()), + } } } -impl Widget for &mut LogView<'_> { +impl Widget for &mut LogView { fn render(self, area: ratatui::prelude::Rect, buf: &mut ratatui::prelude::Buffer) where - Self: Sized { - self.block.render(area, buf); - let inner_area = self.block.inner_if_some(area); + Self: Sized, + { + self.bar.render(area, buf); + let inner_area = self.bar.area(area); let lines = { let buffer = iobuffer().read().unwrap(); diff --git a/src/widgets/luaeditor/buffer.rs b/src/widgets/luaeditor/buffer.rs index 905950e..3f59b6c 100644 --- a/src/widgets/luaeditor/buffer.rs +++ b/src/widgets/luaeditor/buffer.rs @@ -2,7 +2,7 @@ use std::str::FromStr; use super::cursor::{Cursor, CursorMove}; -#[derive(Default, Debug)] +#[derive(Default, Debug, Clone)] pub struct Buffer { lines: Vec<String>, cursor: Cursor, @@ -157,6 +157,12 @@ impl Buffer { pub fn lines(&self) -> &Vec<String> { &self.lines } + + pub fn set_lines(&mut self, lines: Vec<String>) { + self.lines = lines; + self.cursor.move_checked(CursorMove::Jump((0, 0))); + self.refresh_max(); + } } impl FromStr for Buffer { diff --git a/src/widgets/luaeditor/cursor.rs b/src/widgets/luaeditor/cursor.rs index c4466d0..e051231 100644 --- a/src/widgets/luaeditor/cursor.rs +++ b/src/widgets/luaeditor/cursor.rs @@ -11,7 +11,7 @@ pub enum CursorMove { Jump((usize, usize)), } -#[derive(Default, Debug)] +#[derive(Default, Debug, Clone)] pub struct Cursor { x: isize, y: isize, diff --git a/src/widgets/luaeditor/mod.rs b/src/widgets/luaeditor/mod.rs index 63e3d65..98af9e5 100644 --- a/src/widgets/luaeditor/mod.rs +++ b/src/widgets/luaeditor/mod.rs @@ -1,11 +1,8 @@ -use std::str::FromStr; - use ratatui::{ crossterm::event::{KeyCode, KeyEvent}, - prelude::BlockExt, - style::Stylize, - text::ToSpan, - widgets::{Block, Widget}, + style::{Style, Stylize}, + text::{ToLine, ToSpan}, + widgets::Widget, }; pub mod buffer; @@ -18,31 +15,27 @@ use tree_sitter_highlight::HighlightConfiguration; use self::cursor::CursorMove; -pub struct LuaEditor<'a> { - block: Option<Block<'a>>, +use super::statusbar::StatusBar; + +pub struct LuaEditor { + bar: StatusBar, scroll: usize, buffer: Buffer, highlight_config: HighlightConfiguration, } -impl<'a> LuaEditor<'a> { - pub fn new<S>(content: S) -> Self - where - S: AsRef<str>, - { +impl LuaEditor { + pub fn new() -> Self { Self { - block: None, + bar: StatusBar::new() + .left(" [No Name] ") + .left_style(Style::default().on_magenta()), scroll: 0, - buffer: Buffer::from_str(content.as_ref()).unwrap(), + buffer: Buffer::new(), highlight_config: treesitter::new_highlight_configuration(), } } - pub fn block(mut self, block: Option<Block<'a>>) -> Self { - self.block = block; - self - } - pub fn handle_key_event(&mut self, event: KeyEvent) { match event.code { KeyCode::Char(c) => self.buffer.insert(c), @@ -79,30 +72,42 @@ impl<'a> LuaEditor<'a> { } } + pub fn set_text<S>(&mut self, content: S) + where + S: AsRef<str>, + { + self.buffer + .set_lines(content.as_ref().lines().map(|s| s.to_string()).collect()) + } + pub fn text(&self) -> String { self.buffer.lines().join("\n") } } -impl Widget for &mut LuaEditor<'_> { +impl Widget for &mut LuaEditor { fn render(self, area: ratatui::prelude::Rect, buf: &mut ratatui::prelude::Buffer) where Self: Sized, { - self.block.render(area, buf); - let inner_area = self.block.inner_if_some(area); + self.bar.render(area, buf); + let inner_area = self.bar.area(area); let text = self.text(); let highlights = treesitter::highlighter_split(text.as_bytes(), &self.highlight_config); - let mut span_area = inner_area.clone(); + let nr_width = (self.buffer.lines().len().to_string().len() + 1).max(4) as u16; + let mut text_area = inner_area.clone(); + text_area.x += nr_width; + + let mut span_area = text_area.clone(); let mut current_line = 0; for (hl, group) in highlights.iter() { if *group == "\n" { if current_line >= self.scroll { span_area.y += 1; - span_area.x = inner_area.x; + span_area.x = text_area.x; } current_line += 1; @@ -118,7 +123,21 @@ impl Widget for &mut LuaEditor<'_> { } } - let mut cursor_area = inner_area.clone(); + for i in self.scroll..self.buffer.lines().len() { + let mut nr_area = span_area.clone(); + nr_area.x = inner_area.x; + nr_area.width = nr_width - 1; + nr_area.y = inner_area.y + (i - self.scroll) as u16; + nr_area.height = 1; + + if !inner_area.contains(nr_area.into()) { + break; + } + + (i + 1).to_line().right_aligned().render(nr_area, buf); + } + + let mut cursor_area = text_area.clone(); cursor_area.width = 1; cursor_area.height = 1; cursor_area.y += self.buffer.cursor().y() as u16; diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 48e6854..d746972 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -1,3 +1,4 @@ pub mod sheetview; pub mod luaeditor; pub mod logview; +pub mod statusbar; diff --git a/src/widgets/sheetview/mod.rs b/src/widgets/sheetview/mod.rs index 37e3d16..40adfc7 100644 --- a/src/widgets/sheetview/mod.rs +++ b/src/widgets/sheetview/mod.rs @@ -8,46 +8,55 @@ use ratatui::{ widgets::{Block, Borders, Clear, Gauge, Paragraph, Widget, Wrap}, }; -use crate::{config::GlobalConfig, sheet::{ - eval::EvalFunction, - register::{Register, SheetId}, - Sheet, -}}; +use crate::{ + config::GlobalConfig, + sheet::{ + eval::EvalFunction, + register::{Register, SheetId}, + Sheet, + }, +}; -use super::luaeditor::LuaEditor; +use super::{luaeditor::LuaEditor, statusbar::StatusBar}; const DEFAULT_COLUMN_WIDTH: u16 = 10; -pub struct SheetView<'a> { - block: Block<'a>, - luaeditor: Option<LuaEditor<'a>>, +enum SheetViewMode { + Normal, + Insert, + Visual, + Command, + CommandError(String), + Script, + Processing(Option<JoinHandle<Result<Sheet, String>>>), + EditorError(String), +} + +pub struct SheetView { + bar: StatusBar, sheet: SheetId, - selection: Option<(u16, u16)>, cursor: (u16, u16), scroll: (u16, u16), - error_window: Option<String>, - process_handle: Option<JoinHandle<Result<Sheet, String>>>, + selection_anchor: Option<(u16, u16)>, + mode: SheetViewMode, + editor: LuaEditor, } -impl<'a> SheetView<'a> { +impl SheetView { pub fn new(sheet: SheetId) -> Self { Self { - block: Block::default() - .title_bottom(" Sheet ") - .title_style(Style::default().on_magenta().black()) - .borders(Borders::BOTTOM) - .border_style( - Style::default() - .bg(Color::Rgb(29, 32, 33)) - .fg(Color::Rgb(29, 32, 33)), - ), - luaeditor: None, + bar: StatusBar::new() + .left(" NORMAL ") + .left_style(Style::default().on_magenta()) + .middle("Sheet") + .right("") + .right_style(Style::default().on_red()), sheet, - selection: None, cursor: (0, 0), scroll: (0, 0), - error_window: None, - process_handle: None, + mode: SheetViewMode::Normal, + selection_anchor: None, + editor: LuaEditor::new(), } } @@ -68,22 +77,6 @@ impl<'a> SheetView<'a> { .min(sheet.width() as i32 - 1) as u16; } - pub fn select_mode(&mut self, flag: bool) { - if flag { - self.selection = Some(self.cursor); - } else { - self.selection = None; - } - } - - pub fn is_select_mode(&self) -> bool { - self.selection.is_some() - } - - pub fn toggle_select_mode(&mut self) { - self.select_mode(!self.is_select_mode()); - } - pub fn selection(&self) -> Vec<(u16, u16)> { let mut selection = Vec::new(); @@ -110,7 +103,7 @@ impl<'a> SheetView<'a> { } fn selection_range(&self) -> Option<((u16, u16), (u16, u16))> { - if let Some(selection) = self.selection { + if let Some(selection) = self.selection_anchor { let row = if selection.0 > self.cursor.0 { (self.cursor.0, selection.0) } else { @@ -130,32 +123,67 @@ impl<'a> SheetView<'a> { } fn join_process_handle_on_finished(&mut self) { - if let Some(handle) = self.process_handle.take() { + if let SheetViewMode::Processing(opt) = &mut self.mode { + let handle = opt.take().unwrap(); if handle.is_finished() { match handle.join().unwrap() { - Ok(sheet) => Register::get(self.sheet) - .unwrap() - .write() - .unwrap() - .apply(sheet), - Err(message) => self.error_window = Some(message), + Ok(sheet) => { + Register::get(self.sheet) + .unwrap() + .write() + .unwrap() + .apply(sheet); + self.mode = SheetViewMode::Script; + } + Err(message) => self.mode = SheetViewMode::EditorError(message), }; - self.process_handle = None; } else { - self.process_handle = Some(handle); + self.mode = SheetViewMode::Processing(Some(handle)); } } } pub fn handle_key_event(&mut self, event: KeyEvent) { - if self.process_handle.is_some() { - self.join_process_handle_on_finished() - } else if let Some(_) = &self.error_window { - self.error_window = None; - } else if let Some(textarea) = &mut self.luaeditor { - match event.code { + match self.mode { + SheetViewMode::Normal => match event.code { + KeyCode::Char('j') => self.move_cursor_by((1, 0)), + KeyCode::Char('k') => self.move_cursor_by((-1, 0)), + KeyCode::Char('h') => self.move_cursor_by((0, -1)), + KeyCode::Char('l') => self.move_cursor_by((0, 1)), + KeyCode::Char('v') => { + self.selection_anchor = Some(self.cursor.clone()); + self.mode = SheetViewMode::Visual + }, + KeyCode::Char('s') => { + self.editor.set_text("function(cell)\n\treturn \"\"\nend"); + self.mode = SheetViewMode::Script; + } + _ => {} + }, + SheetViewMode::Insert => {} + SheetViewMode::Visual => match event.code { + KeyCode::Char('j') => self.move_cursor_by((1, 0)), + KeyCode::Char('k') => self.move_cursor_by((-1, 0)), + KeyCode::Char('h') => self.move_cursor_by((0, -1)), + KeyCode::Char('l') => self.move_cursor_by((0, 1)), + KeyCode::Esc | KeyCode::Char('v') => { + self.selection_anchor = None; + self.mode = SheetViewMode::Normal; + }, + KeyCode::Char('s') => { + self.editor.set_text("function(cell)\n\treturn \"\"\nend"); + self.mode = SheetViewMode::Script; + } + _ => {} + }, + SheetViewMode::Command => {} + SheetViewMode::CommandError(_) => match event.code { + KeyCode::Esc => self.mode = SheetViewMode::Normal, + _ => {} + }, + SheetViewMode::Script => match event.code { KeyCode::Char('r') if event.modifiers == KeyModifiers::CONTROL => { - let script = textarea.text(); + let script = self.editor.text(); let (width, height) = { let lock = Register::get(self.sheet).unwrap(); @@ -165,7 +193,7 @@ impl<'a> SheetView<'a> { let mut cells = Vec::new(); - if let Some(_) = self.selection { + if let Some(_) = self.selection_anchor { cells = self .selection() .iter() @@ -179,32 +207,51 @@ impl<'a> SheetView<'a> { } } - self.process_handle = Some( + self.mode = SheetViewMode::Processing(Some( Register::get(self.sheet) .unwrap() .eval_function(script, cells), - ); + )); } KeyCode::Esc => { - self.luaeditor = None; - } - _ => { - textarea.handle_key_event(event); + if self.selection_anchor.is_some() { + self.mode = SheetViewMode::Visual; + } else { + self.mode = SheetViewMode::Normal; + } } + _ => self.editor.handle_key_event(event), + }, + SheetViewMode::Processing(_) => { + self.join_process_handle_on_finished(); } - } else { - match event.code { - KeyCode::Char('j') => self.move_cursor_by((1, 0)), - KeyCode::Char('k') => self.move_cursor_by((-1, 0)), - KeyCode::Char('h') => self.move_cursor_by((0, -1)), - KeyCode::Char('l') => self.move_cursor_by((0, 1)), - KeyCode::Char('v') => self.toggle_select_mode(), - KeyCode::Char('s') => { - let editor = LuaEditor::new("function(cell)\n\treturn \"\"\nend"); - self.luaeditor = Some(editor) - } + SheetViewMode::EditorError(_) => match event.code { + KeyCode::Esc => self.mode = SheetViewMode::Script, _ => {} - } + }, + } + } + + fn is_editor_visible(&self) -> bool { + match self.mode { + SheetViewMode::Script + | SheetViewMode::Processing(_) + | SheetViewMode::EditorError(_) => true, + _ => false, + } + } + + fn is_error_window_visible(&self) -> bool { + match self.mode { + SheetViewMode::EditorError(_) => true, + _ => false, + } + } + + fn is_progress_visible(&self) -> bool { + match self.mode { + SheetViewMode::Processing(_) => true, + _ => false, } } @@ -214,20 +261,20 @@ impl<'a> SheetView<'a> { let mut error = Rect::default(); let mut progress = Rect::default(); - if self.luaeditor.is_some() { + if self.is_editor_visible() { let layout = Layout::horizontal([Constraint::Min(1), Constraint::Length(50)]).split(sheet); sheet = layout[0]; editor = layout[1]; - if self.error_window.is_some() { + if self.is_error_window_visible() { let layout = Layout::vertical([Constraint::Min(1), Constraint::Length(10)]).split(editor); editor = layout[0]; error = layout[1]; } - if self.process_handle.is_some() { + if self.is_progress_visible() { let layout = Layout::vertical([Constraint::Min(1), Constraint::Length(1)]).split(editor); editor = layout[0]; @@ -239,7 +286,7 @@ impl<'a> SheetView<'a> { } } -impl Widget for &mut SheetView<'_> { +impl Widget for &mut SheetView { fn render(self, area: ratatui::prelude::Rect, buf: &mut ratatui::prelude::Buffer) where Self: Sized, @@ -257,13 +304,13 @@ impl Widget for &mut SheetView<'_> { let lock = Register::get(self.sheet).unwrap(); let sheet = lock.read().unwrap(); - let mut sheet_area_inner = self.block.inner(sheet_area); + let mut sheet_area_inner = self.bar.area(sheet_area); - if self.luaeditor.is_some() { + if self.is_editor_visible() { sheet_area_inner = separator.inner(sheet_area_inner); } - let viewport_rows = sheet.height().min(sheet_area.height as usize); + let viewport_rows = sheet.height().min(sheet_area_inner.height as usize); let viewport_columns = sheet .width() .min((sheet_area_inner.width / DEFAULT_COLUMN_WIDTH) as usize); @@ -310,14 +357,14 @@ impl Widget for &mut SheetView<'_> { } } - self.block.clone().render(sheet_area, buf); + self.bar.render(sheet_area, buf); - if let Some(textarea) = &mut self.luaeditor { - separator.render(self.block.inner(sheet_area), buf); - textarea.render(editor_area, buf) + if self.is_editor_visible() { + separator.render(sheet_area, buf); + self.editor.render(editor_area, buf) } - if let Some(error_msg) = &self.error_window { + if let SheetViewMode::EditorError(error_msg) = &self.mode { let lines = error_msg.lines().collect::<Vec<_>>(); let block = Block::default() @@ -341,7 +388,7 @@ impl Widget for &mut SheetView<'_> { self.join_process_handle_on_finished(); - if self.process_handle.is_some() { + if self.is_progress_visible() { let gauge = Gauge::default() .gauge_style( Style::default() diff --git a/src/widgets/statusbar.rs b/src/widgets/statusbar.rs new file mode 100644 index 0000000..97b514d --- /dev/null +++ b/src/widgets/statusbar.rs @@ -0,0 +1,156 @@ +use ratatui::{layout::{Alignment, Rect}, style::Style, text::ToLine, widgets::Widget}; + +#[derive(Clone)] +pub struct StatusBar { + left: String, + left_style: Style, + middle: String, + middle_style: Style, + middle_alignment: Alignment, + right: String, + right_style: Style, +} + +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(), + } + } + + pub fn left<S>(mut self, text: S) -> Self + where + S: AsRef<str>, + { + self.left = text.as_ref().to_string(); + self + } + + pub fn middle<S>(mut self, text: S) -> Self + where + S: AsRef<str>, + { + self.middle = text.as_ref().to_string(); + self + } + + pub fn right<S>(mut self, text: S) -> Self + where + S: AsRef<str>, + { + 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<S>(&mut self, text: S) + where + S: AsRef<str>, + { + self.left = text.as_ref().to_string(); + } + + pub fn set_middle<S>(&mut self, text: S) + where + S: AsRef<str>, + { + self.middle = text.as_ref().to_string(); + } + + pub fn set_right<S>(&mut self, text: S) + where + S: AsRef<str>, + { + 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 area(&self, rect: Rect) -> Rect { + let mut inner = rect.clone(); + inner.height -= 1; + inner + } + + fn layout(&self, mut area: Rect) -> (Rect, Rect, Rect) { + area.y += area.height - 1; + area.height = 1; + + let mut left = area.clone(); + let mut right = area.clone(); + let mut middle = area.clone(); + + 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); + 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); + } +} |