[tui] Implement View MenuBar actions.

You can show / hide the Logger and the Explorer now.
This commit is contained in:
Shaun Reed 2026-01-25 12:45:52 -05:00
parent fa36a633ee
commit 76fe09f39b
2 changed files with 127 additions and 33 deletions

View File

@ -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),
}
}

View File

@ -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),
}
}
}