From fd2f5bc412a0f5c8e5df9279e622704c4ecde94f Mon Sep 17 00:00:00 2001 From: Joshua Thijssen Date: Sun, 31 Dec 2023 16:38:13 +0100 Subject: [PATCH] tried to move to appref with display objects and everything separated --- src/dive/app.rs | 170 ++++++++++----------- src/dive/display_object.rs | 27 ++-- src/dive/{ => display_objects}/help.rs | 13 +- src/dive/{ => display_objects}/inputbox.rs | 31 +--- src/dive/{ => display_objects}/menu.rs | 13 +- src/dive/display_objects/mod.rs | 6 + src/dive/{ => display_objects}/status.rs | 20 +-- src/dive/display_objects/tab_display.rs | 50 ++++++ src/dive/display_objects/test.rs | 47 ++++++ src/dive/mod.rs | 13 +- src/dive/tab.rs | 30 ---- src/dive/tab_manager.rs | 66 ++++++++ src/dive/test.rs | 38 ----- src/dive/ui.rs | 44 ++++++ src/main.rs | 17 +-- 15 files changed, 341 insertions(+), 244 deletions(-) rename src/dive/{ => display_objects}/help.rs (96%) rename src/dive/{ => display_objects}/inputbox.rs (68%) rename src/dive/{ => display_objects}/menu.rs (87%) create mode 100644 src/dive/display_objects/mod.rs rename src/dive/{ => display_objects}/status.rs (65%) create mode 100644 src/dive/display_objects/tab_display.rs create mode 100644 src/dive/display_objects/test.rs delete mode 100644 src/dive/tab.rs create mode 100644 src/dive/tab_manager.rs delete mode 100644 src/dive/test.rs create mode 100644 src/dive/ui.rs diff --git a/src/dive/app.rs b/src/dive/app.rs index 2795101..d1a3994 100644 --- a/src/dive/app.rs +++ b/src/dive/app.rs @@ -5,66 +5,68 @@ use crossterm::event::KeyCode::Char; use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use crossterm::event::Event::Key; use ratatui::Frame; -use ratatui::layout::{Constraint, Direction, Layout}; -use ratatui::prelude::Rect; -use crate::AppRef; -use crate::dive::tab::{Tab, tab_switch, tabs_render}; +use crate::dive; use crate::dive::display_object::DisplayObject; -use crate::dive::help::HelpDisplayObject; -use crate::dive::menu::MenuBar; -use crate::dive::status::StatusBar; -use crate::dive::test::TestDisplayObject; +use crate::dive::display_objects::menu::MenuBar; +use crate::dive::display_objects::status::StatusBar; +use crate::dive::tab_manager::TabManager; -pub struct App { - pub tabs: Vec, - pub current_tab: usize, +pub type AppRef = Rc>; +pub struct App { + /// True when the application should exit pub should_quit: bool, + /// List of display objects to render/handle pub display_objects: Vec, + /// Index of the active display object (which handles key strokes) pub active_display_object_index: usize, + /// Status bar object pub status_bar: Rc>, + /// Menu bar object pub menu_bar: Rc>, + /// TabLayout object + pub tab_manager: Rc>, } impl App { - pub fn new() -> Self { - let mut app = App { - tabs: vec![], + pub fn new() -> AppRef { + let status_bar = Rc::new(RefCell::new(StatusBar::new())); + let menu_bar = Rc::new(RefCell::new(MenuBar::new())); + let tab_manager = Rc::new(RefCell::new(TabManager::new())); + + let app = App { should_quit: false, - current_tab: 0, display_objects: vec![], active_display_object_index: 0, - status_bar: Rc::new(RefCell::new(StatusBar::new())), - menu_bar: Rc::new(RefCell::new(MenuBar::new())), + status_bar: status_bar.clone(), + menu_bar: menu_bar.clone(), + tab_manager: tab_manager.clone(), }; - let test = Rc::new(RefCell::new(TestDisplayObject::new())); - let help = Rc::new(RefCell::new(HelpDisplayObject::new())); + let app_ref = Rc::new(RefCell::new(app)); + + let tab_display_obj= Rc::new(RefCell::new(dive::display_objects::tab_display::TabDisplay::new(tab_manager.clone()))); // Add display objects - app.display_objects.push(DisplayObject::new("menu", 128, app.menu_bar.clone(), true)); - app.display_objects.push(DisplayObject::new("status", 128, app.status_bar.clone(), true)); - app.display_objects.push(DisplayObject::new("test", 64, test.clone(), true)); - app.display_objects.push(DisplayObject::new("help", 0, help.clone(), false)); + app_ref.borrow_mut().display_objects.push(DisplayObject::new("menu", 128, menu_bar.clone(), true)); + app_ref.borrow_mut().display_objects.push(DisplayObject::new("status", 128, status_bar.clone(), true)); + app_ref.borrow_mut().display_objects.push(DisplayObject::new("tabs", 0, tab_display_obj.clone(), true)); - app - } + let test = Rc::new(RefCell::new(dive::display_objects::test::TestDisplayObject::new())); + app_ref.borrow_mut().display_objects.push(DisplayObject::new("test", 64, test.clone(), false)); - pub fn add_tab(&mut self, name: &str, url: &str) { - let tab = Tab { - name: name.into(), - url: url.into(), - content: String::new(), - }; + let help = Rc::new(RefCell::new(dive::display_objects::help::HelpDisplayObject::new())); + app_ref.borrow_mut().display_objects.push(DisplayObject::new("help", 0, help.clone(), false)); - self.tabs.push(tab); + app_ref } /// Find the display object with the given id or None when not found + #[allow(dead_code)] pub(crate) fn find_display_object(&self, id: &str) -> Option<&DisplayObject> { for display_object in self.display_objects.iter() { if display_object.id == id { @@ -75,7 +77,7 @@ impl App { None } - pub(crate) fn find_display_object_mut(&mut self, id: &str) -> Option<&mut DisplayObject> { + pub fn find_display_object_mut(&mut self, id: &str) -> Option<&mut DisplayObject> { for display_object in self.display_objects.iter_mut() { if display_object.id == id { return Some(display_object); @@ -98,6 +100,12 @@ impl App { self.status_bar.borrow_mut().set_status(status); } + pub(crate) fn hide_display_object(&mut self, id: &str) { + if let Some(display_object) = self.find_display_object_mut(id) { + display_object.show = false; + } + } + pub(crate) fn handle_events(&mut self, app: AppRef) -> anyhow::Result<()> { if ! event::poll(std::time::Duration::from_millis(250))? { return Ok(()); @@ -106,47 +114,26 @@ impl App { if let Key(key) = event::read()? { if key.kind == event::KeyEventKind::Press { let display_obj = &mut self.display_objects[self.active_display_object_index]; - display_obj.object.borrow_mut().event_handler(app.clone(), key)?; - // also let main app handle the key - self.process_key(key)?; + let res = display_obj.object.borrow().event_handler(app, key)?; + if res.is_none() { + // The display object did not handle the key, so we should handle it + self.process_key(key)?; + } } } Ok(()) } - /// Returns the layout chunks for the main screen - pub fn get_layout_chunks(&self, f: &mut Frame) -> Rc<[Rect]> { - let size = f.size(); - let chunks = Layout::default() - .direction(Direction::Vertical) - .margin(1) - .constraints( - [ - Constraint::Length(1), // menu bar - Constraint::Min(0), // content - Constraint::Length(1), // status bar - ] - .as_ref(), - ) - .split(size); - - chunks - } - // Renders the screen, and all display objects - pub(crate) fn render(&mut self, app: AppRef, f: &mut Frame) { - let chunks = self.get_layout_chunks(f); - - let tabs = tabs_render(self); - f.render_widget(tabs, chunks[1]); - + pub(crate) fn render(&self, app: AppRef, f: &mut Frame) { // Iterate all display objects, and sort them by priority (0 == first) - self.display_objects.sort_by(|a, b| a.priority.cmp(&b.priority)); + let mut objs = self.display_objects.clone(); + objs.sort_by(|a, b| a.priority.cmp(&b.priority)); // Render all showable display objects - for display_object in self.display_objects.iter_mut() { + for display_object in objs.iter_mut() { if !display_object.show { continue; } @@ -156,17 +143,17 @@ impl App { /// Main key handling fn process_key(&mut self, key: KeyEvent) -> anyhow::Result<()> { + match key.code { - Char('0') if key.modifiers.contains(KeyModifiers::ALT) => tab_switch(self, 0), - Char('1') if key.modifiers.contains(KeyModifiers::ALT) => tab_switch(self, 1), - Char('2') if key.modifiers.contains(KeyModifiers::ALT) => tab_switch(self, 2), - Char('3') if key.modifiers.contains(KeyModifiers::ALT) => tab_switch(self, 3), - Char('4') if key.modifiers.contains(KeyModifiers::ALT) => tab_switch(self, 4), - Char('5') if key.modifiers.contains(KeyModifiers::ALT) => tab_switch(self, 5), - Char('6') if key.modifiers.contains(KeyModifiers::ALT) => tab_switch(self, 6), - Char('7') if key.modifiers.contains(KeyModifiers::ALT) => tab_switch(self, 7), - Char('8') if key.modifiers.contains(KeyModifiers::ALT) => tab_switch(self, 8), - Char('9') if key.modifiers.contains(KeyModifiers::ALT) => tab_switch(self, 9), + Char(c) if key.modifiers.contains(KeyModifiers::ALT) && c.is_digit(10) => { + if let Some(digit) = c.to_digit(10) { + { + let mut tab_manager = self.tab_manager.borrow_mut(); + tab_manager.switch(digit as usize); + } + self.status_bar.borrow_mut().set_status(format!("Switched to tab {}", digit).as_str()); + } + }, KeyCode::F(1) => { let obj = self.find_display_object_mut("help").unwrap(); @@ -182,8 +169,12 @@ impl App { } // KeyCode::F(9) => self.menu_active = !self.menu_active, KeyCode::Tab => { - self.current_tab = (self.current_tab + 1) % self.tabs.len(); - self.set_status(format!("Switched to tab {}", self.current_tab).as_str()); + let idx; + { + let mut tab_manager = self.tab_manager.borrow_mut(); + idx = tab_manager.next(); + } + self.set_status(format!("Switched to tab {}", idx).as_str()); }, // Char('i') if key.modifiers.contains(KeyModifiers::CONTROL) => { @@ -191,25 +182,30 @@ impl App { // self.popup = true; // } Char('w') if key.modifiers.contains(KeyModifiers::CONTROL) => { - if self.tabs.len() > 1 { - self.tabs.remove(self.current_tab); - self.set_status(format!("Closed tab {}", self.current_tab).as_str()); - if self.current_tab > 0 { - self.current_tab -= 1; + let tab_count = self.tab_manager.borrow().tab_count(); + + if tab_count > 1 { + let idx; + { + let mut tab_manager = self.tab_manager.borrow_mut(); + idx = tab_manager.current; + tab_manager.close(idx); } + self.set_status(format!("Closed tab {}", idx).as_str()); } else { self.set_status("Can't close last tab"); } }, Char('n') if key.modifiers.contains(KeyModifiers::CONTROL) => { - self.tabs.push(Tab { - name: "New Tab".to_string(), - url: "gosub://blank".to_string(), - content: String::new(), - }); - self.set_status(format!("Opened new tab {}", self.tabs.len() - 1).as_str()); - self.current_tab = self.tabs.len() - 1; + let idx; + { + let mut tab_manager = self.tab_manager.borrow_mut(); + idx = tab_manager.add_tab("New Tab", "gosub://blank"); + tab_manager.switch(idx); + } + self.set_status(format!("Opened new tab {}", idx).as_str()); }, + Char('q') if key.modifiers.contains(KeyModifiers::CONTROL) => self.should_quit = true, _ => {}, } diff --git a/src/dive/display_object.rs b/src/dive/display_object.rs index 0ac380e..116144c 100644 --- a/src/dive/display_object.rs +++ b/src/dive/display_object.rs @@ -2,20 +2,25 @@ use std::cell::RefCell; use std::rc::Rc; use crossterm::event::KeyEvent; use ratatui::Frame; -use crate::AppRef; +use crate::dive::app::AppRef; pub trait Displayable { fn render(&mut self, app: AppRef, f: &mut Frame); - fn event_handler(&mut self, app: AppRef, key: KeyEvent) -> anyhow::Result<()>; + fn event_handler(&mut self, app: AppRef, key: KeyEvent) -> anyhow::Result>; fn on_show(&mut self, app: AppRef); fn on_hide(&mut self, app: AppRef); } +#[derive(Clone)] pub struct DisplayObject { - pub id: String, // Unique identifier for this object - pub priority: u8, // 0 is the highest, 255 is the lowest - pub show: bool, // Does this object need to be rendered - pub object: Rc>, // Actual object with rendering and event handling + /// Unique identifier for this object + pub id: String, + /// 0 is the highest, 255 is the lowest + pub priority: u8, + /// Does this object need to be rendered + pub show: bool, + /// Actual object with rendering and event handling + pub object: Rc>, } impl DisplayObject { @@ -27,14 +32,4 @@ impl DisplayObject { object, } } - - pub fn show(&mut self, app: AppRef) { - self.show = true; - self.object.borrow_mut().on_show(app.clone()); - } - - pub fn hide(&mut self, app: AppRef) { - self.show = false; - self.object.borrow_mut().on_hide(app.clone()); - } } diff --git a/src/dive/help.rs b/src/dive/display_objects/help.rs similarity index 96% rename from src/dive/help.rs rename to src/dive/display_objects/help.rs index b117135..159afce 100644 --- a/src/dive/help.rs +++ b/src/dive/display_objects/help.rs @@ -3,7 +3,7 @@ use crossterm::event::KeyCode::Char; use ratatui::Frame; use ratatui::prelude::*; use ratatui::widgets::{Block, Borders, Padding, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState, Wrap}; -use crate::AppRef; +use crate::dive::app::AppRef; use crate::dive::display_object::Displayable; const HELPTEXT: &'static str = r#" @@ -106,7 +106,6 @@ fn generate_lines_from_helptext() -> Vec> { lines } - pub struct HelpDisplayObject { pub vertical_scroll_state: ScrollbarState, pub vertical_scroll: usize, @@ -175,12 +174,10 @@ impl Displayable for HelpDisplayObject { ); } - fn event_handler(&mut self, app: AppRef, key: KeyEvent) -> anyhow::Result<()> { + fn event_handler(&mut self, app: AppRef, key: KeyEvent) -> anyhow::Result> { match key.code { - KeyCode::F(1) => { - let mut app_ref = app.borrow_mut(); - let obj = app_ref.find_display_object_mut("help").unwrap(); - obj.hide(app.clone()); + KeyCode::Esc | KeyCode::F(1) => { + app.borrow_mut().hide_display_object("help"); } KeyCode::Down => { self.vertical_scroll = self.vertical_scroll.saturating_add(1).clamp(0, self.vertical_scroll_max - 1); @@ -194,7 +191,7 @@ impl Displayable for HelpDisplayObject { _ => {} } - Ok(()) + Ok(Some(key)) } fn on_show(&mut self, app: AppRef) { diff --git a/src/dive/inputbox.rs b/src/dive/display_objects/inputbox.rs similarity index 68% rename from src/dive/inputbox.rs rename to src/dive/display_objects/inputbox.rs index c9113c4..d03dae1 100644 --- a/src/dive/inputbox.rs +++ b/src/dive/display_objects/inputbox.rs @@ -1,12 +1,12 @@ use crossterm::event::{Event, KeyCode, KeyEvent}; use ratatui::Frame; -use ratatui::layout::{Constraint, Direction, Layout, Rect}; use ratatui::prelude::{Color, Style, Stylize}; use ratatui::widgets::{Block, Borders}; use tui_input::backend::crossterm::EventHandler; use tui_input::Input; -use crate::AppRef; +use crate::dive::app::AppRef; use crate::dive::display_object::Displayable; +use crate::dive::ui::centered_rect; pub struct InputBox { pub input: Input, @@ -16,6 +16,7 @@ pub struct InputBox { } impl InputBox { + #[allow(dead_code)] pub fn new( title: String, default_input: Option, @@ -42,7 +43,7 @@ impl Displayable for InputBox { f.render_widget(popup_block, area); } - fn event_handler(&mut self, _app: AppRef, key: KeyEvent) -> anyhow::Result<()> { + fn event_handler(&mut self, _app: AppRef, key: KeyEvent) -> anyhow::Result> { match key.code { KeyCode::Esc => { // app.popup = false; @@ -57,7 +58,7 @@ impl Displayable for InputBox { } } - Ok(()) + Ok(Some(key)) } fn on_show(&mut self, app: AppRef) { @@ -75,26 +76,4 @@ impl Displayable for InputBox { self.on_show_func.as_ref().unwrap()(app); } } -} - -fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect { - // Cut the given rectangle into three vertical pieces - let popup_layout = Layout::default() - .direction(Direction::Vertical) - .constraints([ - Constraint::Percentage((100 - percent_y) / 2), - Constraint::Percentage(percent_y), - Constraint::Percentage((100 - percent_y) / 2), - ]) - .split(r); - - // Then cut the middle vertical piece into three width-wise pieces - Layout::default() - .direction(Direction::Horizontal) - .constraints([ - Constraint::Percentage((100 - percent_x) / 2), - Constraint::Percentage(percent_x), - Constraint::Percentage((100 - percent_x) / 2), - ]) - .split(popup_layout[1])[1] // Return the middle chunk } \ No newline at end of file diff --git a/src/dive/menu.rs b/src/dive/display_objects/menu.rs similarity index 87% rename from src/dive/menu.rs rename to src/dive/display_objects/menu.rs index 5c60536..4af699a 100644 --- a/src/dive/menu.rs +++ b/src/dive/display_objects/menu.rs @@ -1,9 +1,9 @@ use ratatui::prelude::*; use ratatui::widgets::Paragraph; use crossterm::event::KeyEvent; -use crate::AppRef; +use crate::dive::app::AppRef; use crate::dive::display_object::Displayable; - +use crate::dive::ui::get_layout_chunks; pub struct MenuBar { pub active: bool, @@ -18,13 +18,14 @@ impl MenuBar { } } + #[allow(dead_code)] pub fn set_active(&mut self, active: bool) { self.active = active; } } impl Displayable for MenuBar { - fn render(&mut self, app: AppRef, f: &mut Frame) { + fn render(&mut self, _app: AppRef, f: &mut Frame) { let menu_items = vec![ "File", @@ -53,14 +54,14 @@ impl Displayable for MenuBar { } } - let chunks = app.borrow().get_layout_chunks(f); + let chunks = get_layout_chunks(f); let menu_bar = Paragraph::new(Line::from(menu_tiles)).style(Style::default().bg(Color::Blue).add_modifier(Modifier::BOLD)); f.render_widget(menu_bar, chunks[0]); } - fn event_handler(&mut self, _app: AppRef, _key: KeyEvent) -> anyhow::Result<()> { + fn event_handler(&mut self, _app: AppRef, _key: KeyEvent) -> anyhow::Result> { // We should handle left, right to change the menu item active - Ok(()) + Ok(None) } fn on_show(&mut self, _app: AppRef) { } diff --git a/src/dive/display_objects/mod.rs b/src/dive/display_objects/mod.rs new file mode 100644 index 0000000..f8e36b1 --- /dev/null +++ b/src/dive/display_objects/mod.rs @@ -0,0 +1,6 @@ +pub mod help; +pub mod menu; +pub mod status; +pub mod tab_display; +pub mod inputbox; +pub mod test; diff --git a/src/dive/status.rs b/src/dive/display_objects/status.rs similarity index 65% rename from src/dive/status.rs rename to src/dive/display_objects/status.rs index d5d47d9..e24a729 100644 --- a/src/dive/status.rs +++ b/src/dive/display_objects/status.rs @@ -1,16 +1,9 @@ use crossterm::event::KeyEvent; use ratatui::prelude::*; use ratatui::widgets::Paragraph; -use crate::AppRef; +use crate::dive::app::AppRef; use crate::dive::display_object::Displayable; - -// pub fn status_render(app: &mut App) -> Paragraph<'static> { -// Paragraph::new(Line::from(vec![ -// Span::styled(app.status.clone(), Style::default().add_modifier(Modifier::BOLD)), -// Span::raw(" | "), -// Span::raw("Line 1, Column 1"), -// ])).style(Style::default().bg(Color::Blue).bold()) -// } +use crate::dive::ui::get_layout_chunks; pub struct StatusBar { pub status: String, @@ -29,8 +22,8 @@ impl StatusBar { } impl Displayable for StatusBar { - fn render(&mut self, app: AppRef, f: &mut Frame) { - let chunks = app.borrow().get_layout_chunks(f); + fn render(&mut self, _app: AppRef, f: &mut Frame) { + let chunks = get_layout_chunks(f); let status_bar = Paragraph::new(Line::from(vec![ Span::styled(self.status.clone(), Style::default().add_modifier(Modifier::BOLD)), @@ -41,8 +34,9 @@ impl Displayable for StatusBar { f.render_widget(status_bar, chunks[2]); } - fn event_handler(&mut self, _app: AppRef, _key: KeyEvent) -> anyhow::Result<()> { - Ok(()) + fn event_handler(&mut self, _app: AppRef, _key: KeyEvent) -> anyhow::Result> { + // Status bar does not handle keys + Ok(None) } fn on_show(&mut self, _app: AppRef) { } diff --git a/src/dive/display_objects/tab_display.rs b/src/dive/display_objects/tab_display.rs new file mode 100644 index 0000000..4d32a03 --- /dev/null +++ b/src/dive/display_objects/tab_display.rs @@ -0,0 +1,50 @@ +use std::cell::RefCell; +use std::rc::Rc; +use crossterm::event::KeyEvent; +use ratatui::Frame; +use ratatui::widgets::{Block, Borders, Tabs}; +use crate::dive::app::AppRef; +use crate::dive::display_object::Displayable; +use crate::dive::tab_manager::TabManager; +use crate::dive::ui::get_layout_chunks; + +pub struct TabDisplay { + pub manager: Rc>, +} + +impl TabDisplay { + pub fn new(manager: Rc>) -> Self { + Self { + manager, + } + } +} + +impl Displayable for TabDisplay { + fn render(&mut self, _app: AppRef, f: &mut Frame) { + let mut tab_names = Vec::new(); + for (idx, tab) in self.manager.borrow().tabs.iter().enumerate() { + tab_names.push(format!(" {}:{} ", idx, tab.name.clone())); + } + + let tabs = Tabs::new(tab_names) + .block(Block::default().borders(Borders::NONE)) + // .style(Style::default().white()) + // .highlight_style(Style::default().yellow()) + .select(self.manager.borrow().current) + .divider("|") + .padding("", "") + ; + + let chunk = get_layout_chunks(f); + f.render_widget(tabs, chunk[1]); + } + + fn event_handler(&mut self, _app: AppRef, _key: KeyEvent) -> anyhow::Result> { + Ok(None) + } + + fn on_show(&mut self, _app: AppRef) {} + + fn on_hide(&mut self, _app: AppRef) {} +} \ No newline at end of file diff --git a/src/dive/display_objects/test.rs b/src/dive/display_objects/test.rs new file mode 100644 index 0000000..cc6173e --- /dev/null +++ b/src/dive/display_objects/test.rs @@ -0,0 +1,47 @@ +use crossterm::event::KeyEvent; +use ratatui::Frame; +use ratatui::prelude::*; +use ratatui::widgets::{Block, Borders, BorderType, Paragraph}; +use crate::dive::app::AppRef; +use crate::dive::display_object::Displayable; +use crate::dive::ui::centered_rect; + +pub struct TestDisplayObject; + +impl TestDisplayObject { + pub fn new() -> Self { + Self + } +} + +impl Displayable for TestDisplayObject { + fn render(&mut self, _app: AppRef, f: &mut Frame) { + let block = Block::new() + .title("Test") + .borders(Borders::ALL) + .style(Style::default().fg(Color::Yellow).bold().bg(Color::LightBlue)) + .border_type(BorderType::Rounded) + ; + + let paragraph = Paragraph::new("Hello Ratatui! (press 'q' to quit)") + .white() + .on_red() + .block(block) + ; + + let area = centered_rect(60, 25, f.size()); + f.render_widget(paragraph, area); + } + + fn event_handler(&mut self, _app: AppRef, _key: KeyEvent) -> anyhow::Result> { + Ok(None) + } + + fn on_show(&mut self, app: AppRef) { + app.borrow_mut().set_status("Opened test screen"); + } + + fn on_hide(&mut self, app: AppRef) { + app.borrow_mut().set_status("Closed test screen"); + } +} \ No newline at end of file diff --git a/src/dive/mod.rs b/src/dive/mod.rs index 42e26ba..25ca6f9 100644 --- a/src/dive/mod.rs +++ b/src/dive/mod.rs @@ -1,12 +1,5 @@ -#[allow(dead_code)] pub mod app; -mod help; -#[allow(dead_code)] -mod menu; -mod status; -pub mod tab; -#[allow(dead_code)] mod display_object; -#[allow(dead_code)] -mod inputbox; -mod test; \ No newline at end of file +mod ui; +mod display_objects; +mod tab_manager; diff --git a/src/dive/tab.rs b/src/dive/tab.rs deleted file mode 100644 index 0682497..0000000 --- a/src/dive/tab.rs +++ /dev/null @@ -1,30 +0,0 @@ -use ratatui::widgets::{Block, Borders, Tabs}; -use crate::dive::app::App; - -pub struct Tab { - pub name: String, - pub url: String, - pub content: String, -} - -pub fn tab_switch(app: &mut App, tab: usize) { - if tab < app.tabs.len() { - app.current_tab = tab; - app.set_status(format!("Switched to tab {}", app.current_tab).as_str()); - } -} - -pub fn tabs_render(app: &mut App) -> Tabs<'static> { - let mut tab_names = Vec::new(); - for (idx, tab) in app.tabs.iter().enumerate() { - tab_names.push(format!(" {}:{} ", idx, tab.name.clone())); - } - - Tabs::new(tab_names) - .block(Block::default().borders(Borders::NONE)) - // .style(Style::default().white()) - // .highlight_style(Style::default().yellow()) - .select(app.current_tab) - .divider("|") - .padding("", "") -} \ No newline at end of file diff --git a/src/dive/tab_manager.rs b/src/dive/tab_manager.rs new file mode 100644 index 0000000..d094d38 --- /dev/null +++ b/src/dive/tab_manager.rs @@ -0,0 +1,66 @@ +pub struct Tab { + pub name: String, + pub url: String, + pub content: String, +} + +pub struct TabManager { + pub tabs: Vec, + pub current: usize, +} + +impl TabManager { + pub fn new() -> Self { + Self { + tabs: vec![], + current: 0, + } + } + + pub fn add_tab(&mut self, name: &str, url: &str) -> usize { + let tab = Tab { + name: name.into(), + url: url.into(), + content: String::new(), + }; + + self.tabs.push(tab); + + self.tabs.len() - 1 + } + + pub fn switch(&mut self, idx: usize) -> usize { + if idx < self.tabs.len() { + self.current = idx; + } + + self.current + } + + pub fn next(&mut self) -> usize { + self.current = (self.current + 1) % self.tabs.len(); + + self.current + } + + #[allow(dead_code)] + pub fn prev(&mut self) -> usize { + if self.current > 0 { + self.current -= 1; + } else { + self.current = self.tabs.len() - 1; + } + + self.current + } + + pub fn close(&mut self, idx: usize) { + if idx < self.tabs.len() { + self.tabs.remove(idx); + } + } + + pub fn tab_count(&self) -> usize { + self.tabs.len() + } +} \ No newline at end of file diff --git a/src/dive/test.rs b/src/dive/test.rs deleted file mode 100644 index 9442d6d..0000000 --- a/src/dive/test.rs +++ /dev/null @@ -1,38 +0,0 @@ -use crossterm::event::KeyEvent; -use ratatui::Frame; -use ratatui::prelude::*; -use ratatui::widgets::Paragraph; -use crate::AppRef; -use crate::dive::display_object::Displayable; - -pub struct TestDisplayObject; - -impl TestDisplayObject { - pub fn new() -> Self { - Self - } -} - -impl Displayable for TestDisplayObject { - fn render(&mut self, _app: AppRef, f: &mut Frame) { - let area = f.size(); - f.render_widget( - Paragraph::new("Hello Ratatui! (press 'q' to quit)") - .white() - .on_blue(), - area, - ); - } - - fn event_handler(&mut self, _app: AppRef, _key: KeyEvent) -> anyhow::Result<()> { - Ok(()) - } - - fn on_show(&mut self, app: AppRef) { - app.borrow_mut().set_status("Opened test screen"); - } - - fn on_hide(&mut self, app: AppRef) { - app.borrow_mut().set_status("Closed test screen"); - } -} \ No newline at end of file diff --git a/src/dive/ui.rs b/src/dive/ui.rs new file mode 100644 index 0000000..d6854e6 --- /dev/null +++ b/src/dive/ui.rs @@ -0,0 +1,44 @@ +use std::rc::Rc; +use ratatui::Frame; +use ratatui::layout::{Constraint, Direction, Layout, Rect}; + +pub fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect { + // Cut the given rectangle into three vertical pieces + let popup_layout = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Percentage((100 - percent_y) / 2), + Constraint::Percentage(percent_y), + Constraint::Percentage((100 - percent_y) / 2), + ]) + .split(r); + + // Then cut the middle vertical piece into three width-wise pieces + Layout::default() + .direction(Direction::Horizontal) + .constraints([ + Constraint::Percentage((100 - percent_x) / 2), + Constraint::Percentage(percent_x), + Constraint::Percentage((100 - percent_x) / 2), + ]) + .split(popup_layout[1])[1] // Return the middle chunk +} + +/// Returns the layout chunks for the main screen +pub fn get_layout_chunks(f: &mut Frame) -> Rc<[Rect]> { + let size = f.size(); + let chunks = Layout::default() + .direction(Direction::Vertical) + .margin(1) + .constraints( + [ + Constraint::Length(1), // menu bar + Constraint::Min(0), // content + Constraint::Length(1), // status bar + ] + .as_ref(), + ) + .split(size); + + chunks +} diff --git a/src/main.rs b/src/main.rs index 44fe41c..71e1ef3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,13 @@ -use std::cell::RefCell; -use std::rc::Rc; use anyhow::Result; use crossterm::{ execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; use ratatui::prelude::{CrosstermBackend, Terminal}; -use crate::dive::app::App; +use crate::dive::app::{App, AppRef}; mod dive; -type AppRef = Rc>; - fn startup() -> Result<()> { enable_raw_mode()?; execute!(std::io::stderr(), EnterAlternateScreen)?; @@ -29,7 +25,7 @@ fn run(app: AppRef) -> anyhow::Result<()> { loop { t.draw(|f| { - app.borrow_mut().render(app.clone(), f); + app.borrow().render(app.clone(), f); })?; // application update @@ -45,10 +41,11 @@ fn run(app: AppRef) -> anyhow::Result<()> { } fn main() -> Result<()> { - let app = Rc::new(RefCell::new(App::new())); - app.borrow_mut().add_tab("New Tab", "gosub://blank"); - app.borrow_mut().add_tab("Second Tab", "https://gosub.io"); - app.borrow_mut().add_tab("Third Tab", "https://noxlogic.nl"); + let app = App::new(); + + app.borrow_mut().tab_manager.borrow_mut().add_tab("New Tab", "gosub://blank"); + app.borrow_mut().tab_manager.borrow_mut().add_tab("Second Tab", "https://gosub.io"); + app.borrow_mut().tab_manager.borrow_mut().add_tab("Third Tab", "https://news.ycombinator.com"); startup()?; let status = run(app.clone());