From 76fe09f39bb722485f5cc2a803ae148aa54f6039 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 25 Jan 2026 12:45:52 -0500 Subject: [PATCH] [tui] Implement View MenuBar actions. You can show / hide the Logger and the Explorer now. --- src/tui/app.rs | 111 ++++++++++++++++++++++++++++++++----------- src/tui/component.rs | 49 +++++++++++++++++-- 2 files changed, 127 insertions(+), 33 deletions(-) diff --git a/src/tui/app.rs b/src/tui/app.rs index 970f9db..7d5752e 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -1,5 +1,5 @@ use crate::tui::app::AppComponent::{AppEditor, AppExplorer, AppLogger}; -use crate::tui::component::{Action, Component, Focus, FocusState}; +use crate::tui::component::{Action, Component, Focus, FocusState, Visible, VisibleState}; use crate::tui::editor_tab::EditorTab; use crate::tui::explorer::Explorer; use crate::tui::logger::Logger; @@ -186,40 +186,87 @@ impl<'a> Widget for &mut App<'a> { where Self: Sized, { + let vertical_constraints = match self.logger.component_state.vis { + Visible::Visible => { + vec![ + Constraint::Length(3), // top status bar + Constraint::Percentage(70), // horizontal layout + Constraint::Fill(1), // terminal + Constraint::Length(3), // bottom status bar + ] + } + Visible::Hidden => { + vec![ + Constraint::Length(3), // top status bar + Constraint::Fill(1), // horizontal layout + Constraint::Length(3), // bottom status bar + ] + } + }; let vertical = Layout::default() .direction(Direction::Vertical) - .constraints([ - Constraint::Length(3), // top status bar - Constraint::Percentage(70), // horizontal layout - Constraint::Percentage(30), // terminal - Constraint::Length(3), // bottom status bar - ]) + .constraints(vertical_constraints) .split(area); + let horizontal_constraints = match self.explorer.component_state.vis { + Visible::Visible => { + vec![ + Constraint::Max(30), // File explorer with a max width of 30 characters. + Constraint::Fill(1), // Editor fills the remaining space. + ] + } + Visible::Hidden => { + vec![ + Constraint::Fill(1), // Editor fills the remaining space. + ] + } + }; + + // The index used for vertical here does not care if the Logger is Visible or not. let horizontal = Layout::default() .direction(Direction::Horizontal) - .constraints([ - Constraint::Max(30), // File explorer with a max width of 30 characters. - Constraint::Fill(1), // Editor fills the remaining space. - ]) + .constraints(horizontal_constraints) .split(vertical[1]); + match self.explorer.component_state.vis { + Visible::Visible => { + let editor_layout = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Length(1), // Editor tabs. + Constraint::Fill(1), // Editor contents. + ]) + .split(horizontal[1]); + self.editor_tabs + .render(editor_layout[0], editor_layout[1], buf); + self.explorer.render(horizontal[0], buf); + } + Visible::Hidden => { + let editor_layout = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Length(1), // Editor tabs. + Constraint::Fill(1), // Editor contents. + ]) + .split(horizontal[0]); + self.editor_tabs + .render(editor_layout[0], editor_layout[1], buf); + } + } - let editor_layout = Layout::default() - .direction(Direction::Vertical) - .constraints([ - Constraint::Length(1), // Editor tabs. - Constraint::Fill(1), // Editor contents. - ]) - .split(horizontal[1]); - - self.draw_bottom_status(vertical[3], buf); - self.editor_tabs - .render(editor_layout[0], editor_layout[1], buf); - self.explorer.render(horizontal[0], buf); - self.logger.render(vertical[2], buf); - - // The title bar is rendered last to overlay any popups created for drop-down menus. - self.menu_bar.render(vertical[0], buf); + match self.logger.component_state.vis { + // Index 1 of vertical is rendered with the horizontal layout above. + Visible::Visible => { + self.logger.render(vertical[2], buf); + self.draw_bottom_status(vertical[3], buf); + // The title bar is rendered last to overlay any popups created for drop-down menus. + self.menu_bar.render(vertical[0], buf); + } + Visible::Hidden => { + self.draw_bottom_status(vertical[2], buf); + // The title bar is rendered last to overlay any popups created for drop-down menus. + self.menu_bar.render(vertical[0], buf); + } + } } } @@ -255,6 +302,7 @@ impl<'a> Component for App<'a> { } } + // Handle actions returned from widgets that may need context on other widgets or app state. match action { Action::Quit | Action::Handled => Ok(action), Action::Save => match self.editor_tabs.current_editor_mut() { @@ -270,7 +318,6 @@ impl<'a> Component for App<'a> { } }, }, - Action::OpenTab => { if let Ok(path) = self.explorer.selected() { let path_buf = PathBuf::from(path); @@ -296,6 +343,14 @@ impl<'a> Component for App<'a> { Ok(Action::Noop) } } + Action::ShowHideLogger => { + self.logger.component_state.togget_visible(); + Ok(Action::Handled) + } + Action::ShowHideExplorer => { + self.explorer.component_state.togget_visible(); + Ok(Action::Handled) + } _ => Ok(Action::Noop), } } diff --git a/src/tui/component.rs b/src/tui/component.rs index 4487ea1..fac8a97 100644 --- a/src/tui/component.rs +++ b/src/tui/component.rs @@ -1,5 +1,7 @@ #![allow(dead_code, unused_variables)] +use crate::tui::component::Focus::Inactive; +use Focus::Active; use anyhow::Result; use log::trace; use ratatui::crossterm::event::{Event, KeyEvent, MouseEvent}; @@ -58,6 +60,7 @@ pub trait Component { #[derive(Debug, Clone, Default)] pub struct ComponentState { pub(crate) focus: Focus, + pub(crate) vis: Visible, pub(crate) help_text: String, } @@ -69,7 +72,8 @@ impl ComponentState { fn new() -> Self { trace!(target:Self::id(), "Building {}", Self::id()); Self { - focus: Focus::Active, + focus: Active, + vis: Visible::Visible, help_text: String::new(), } } @@ -90,8 +94,8 @@ pub enum Focus { impl Focus { pub(crate) fn get_active_color(&self) -> Color { match self { - Focus::Active => Color::LightYellow, - Focus::Inactive => Color::White, + Active => Color::LightYellow, + Inactive => Color::White, } } } @@ -107,6 +111,7 @@ impl FocusState for ComponentState { fn with_focus(self, focus: Focus) -> Self { Self { focus, + vis: Visible::Visible, help_text: self.help_text, } } @@ -117,8 +122,8 @@ impl FocusState for ComponentState { fn toggle_focus(&mut self) { match self.focus { - Focus::Active => self.set_focus(Focus::Inactive), - Focus::Inactive => self.set_focus(Focus::Active), + Active => self.set_focus(Inactive), + Inactive => self.set_focus(Active), } } @@ -126,3 +131,37 @@ impl FocusState for ComponentState { self.focus.get_active_color() } } + +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub enum Visible { + #[default] + Visible, + Hidden, +} + +pub trait VisibleState { + fn with_visible(self, vis: Visible) -> Self; + fn set_visible(&mut self, vis: Visible); + fn togget_visible(&mut self); +} + +impl VisibleState for ComponentState { + fn with_visible(self, vis: Visible) -> Self { + Self { + focus: self.focus, + vis, + help_text: self.help_text, + } + } + + fn set_visible(&mut self, vis: Visible) { + self.vis = vis; + } + + fn togget_visible(&mut self) { + match self.vis { + Visible::Visible => self.set_visible(Visible::Hidden), + Visible::Hidden => self.set_visible(Visible::Visible), + } + } +}