TUI #1
111
src/tui/app.rs
111
src/tui/app.rs
@ -1,5 +1,5 @@
|
|||||||
use crate::tui::app::AppComponent::{AppEditor, AppExplorer, AppLogger};
|
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::editor_tab::EditorTab;
|
||||||
use crate::tui::explorer::Explorer;
|
use crate::tui::explorer::Explorer;
|
||||||
use crate::tui::logger::Logger;
|
use crate::tui::logger::Logger;
|
||||||
@ -186,40 +186,87 @@ impl<'a> Widget for &mut App<'a> {
|
|||||||
where
|
where
|
||||||
Self: Sized,
|
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()
|
let vertical = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints([
|
.constraints(vertical_constraints)
|
||||||
Constraint::Length(3), // top status bar
|
|
||||||
Constraint::Percentage(70), // horizontal layout
|
|
||||||
Constraint::Percentage(30), // terminal
|
|
||||||
Constraint::Length(3), // bottom status bar
|
|
||||||
])
|
|
||||||
.split(area);
|
.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()
|
let horizontal = Layout::default()
|
||||||
.direction(Direction::Horizontal)
|
.direction(Direction::Horizontal)
|
||||||
.constraints([
|
.constraints(horizontal_constraints)
|
||||||
Constraint::Max(30), // File explorer with a max width of 30 characters.
|
|
||||||
Constraint::Fill(1), // Editor fills the remaining space.
|
|
||||||
])
|
|
||||||
.split(vertical[1]);
|
.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()
|
match self.logger.component_state.vis {
|
||||||
.direction(Direction::Vertical)
|
// Index 1 of vertical is rendered with the horizontal layout above.
|
||||||
.constraints([
|
Visible::Visible => {
|
||||||
Constraint::Length(1), // Editor tabs.
|
self.logger.render(vertical[2], buf);
|
||||||
Constraint::Fill(1), // Editor contents.
|
self.draw_bottom_status(vertical[3], buf);
|
||||||
])
|
// The title bar is rendered last to overlay any popups created for drop-down menus.
|
||||||
.split(horizontal[1]);
|
self.menu_bar.render(vertical[0], buf);
|
||||||
|
}
|
||||||
self.draw_bottom_status(vertical[3], buf);
|
Visible::Hidden => {
|
||||||
self.editor_tabs
|
self.draw_bottom_status(vertical[2], buf);
|
||||||
.render(editor_layout[0], editor_layout[1], buf);
|
// The title bar is rendered last to overlay any popups created for drop-down menus.
|
||||||
self.explorer.render(horizontal[0], buf);
|
self.menu_bar.render(vertical[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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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 {
|
match action {
|
||||||
Action::Quit | Action::Handled => Ok(action),
|
Action::Quit | Action::Handled => Ok(action),
|
||||||
Action::Save => match self.editor_tabs.current_editor_mut() {
|
Action::Save => match self.editor_tabs.current_editor_mut() {
|
||||||
@ -270,7 +318,6 @@ impl<'a> Component for App<'a> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
Action::OpenTab => {
|
Action::OpenTab => {
|
||||||
if let Ok(path) = self.explorer.selected() {
|
if let Ok(path) = self.explorer.selected() {
|
||||||
let path_buf = PathBuf::from(path);
|
let path_buf = PathBuf::from(path);
|
||||||
@ -296,6 +343,14 @@ impl<'a> Component for App<'a> {
|
|||||||
Ok(Action::Noop)
|
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),
|
_ => Ok(Action::Noop),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
#![allow(dead_code, unused_variables)]
|
#![allow(dead_code, unused_variables)]
|
||||||
|
|
||||||
|
use crate::tui::component::Focus::Inactive;
|
||||||
|
use Focus::Active;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use log::trace;
|
use log::trace;
|
||||||
use ratatui::crossterm::event::{Event, KeyEvent, MouseEvent};
|
use ratatui::crossterm::event::{Event, KeyEvent, MouseEvent};
|
||||||
@ -58,6 +60,7 @@ pub trait Component {
|
|||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct ComponentState {
|
pub struct ComponentState {
|
||||||
pub(crate) focus: Focus,
|
pub(crate) focus: Focus,
|
||||||
|
pub(crate) vis: Visible,
|
||||||
pub(crate) help_text: String,
|
pub(crate) help_text: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,7 +72,8 @@ impl ComponentState {
|
|||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
trace!(target:Self::id(), "Building {}", Self::id());
|
trace!(target:Self::id(), "Building {}", Self::id());
|
||||||
Self {
|
Self {
|
||||||
focus: Focus::Active,
|
focus: Active,
|
||||||
|
vis: Visible::Visible,
|
||||||
help_text: String::new(),
|
help_text: String::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -90,8 +94,8 @@ pub enum Focus {
|
|||||||
impl Focus {
|
impl Focus {
|
||||||
pub(crate) fn get_active_color(&self) -> Color {
|
pub(crate) fn get_active_color(&self) -> Color {
|
||||||
match self {
|
match self {
|
||||||
Focus::Active => Color::LightYellow,
|
Active => Color::LightYellow,
|
||||||
Focus::Inactive => Color::White,
|
Inactive => Color::White,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -107,6 +111,7 @@ impl FocusState for ComponentState {
|
|||||||
fn with_focus(self, focus: Focus) -> Self {
|
fn with_focus(self, focus: Focus) -> Self {
|
||||||
Self {
|
Self {
|
||||||
focus,
|
focus,
|
||||||
|
vis: Visible::Visible,
|
||||||
help_text: self.help_text,
|
help_text: self.help_text,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -117,8 +122,8 @@ impl FocusState for ComponentState {
|
|||||||
|
|
||||||
fn toggle_focus(&mut self) {
|
fn toggle_focus(&mut self) {
|
||||||
match self.focus {
|
match self.focus {
|
||||||
Focus::Active => self.set_focus(Focus::Inactive),
|
Active => self.set_focus(Inactive),
|
||||||
Focus::Inactive => self.set_focus(Focus::Active),
|
Inactive => self.set_focus(Active),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,3 +131,37 @@ impl FocusState for ComponentState {
|
|||||||
self.focus.get_active_color()
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user