TUI #1

Merged
shaunrd0 merged 73 commits from ui into master 2026-01-25 20:57:37 +00:00
5 changed files with 42 additions and 10 deletions
Showing only changes of commit a3c1065f96 - Show all commits

View File

@ -9,7 +9,8 @@ use ratatui::crossterm::event;
use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers}; use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
use ratatui::layout::{Constraint, Direction, Layout, Rect}; use ratatui::layout::{Constraint, Direction, Layout, Rect};
use ratatui::prelude::{Color, Style, Widget}; use ratatui::prelude::{Color, Style, Widget};
use ratatui::widgets::{Block, Borders, Padding, Tabs}; use ratatui::text::Text;
use ratatui::widgets::{Block, Borders, Padding, Paragraph, Tabs, Wrap};
use ratatui::{DefaultTerminal, symbols}; use ratatui::{DefaultTerminal, symbols};
use std::path::PathBuf; use std::path::PathBuf;
use std::time::Duration; use std::time::Duration;
@ -93,7 +94,7 @@ impl<'a> App<'a> {
Ok(()) Ok(())
} }
fn draw_status(&self, area: Rect, buf: &mut Buffer) { fn draw_top_status(&self, area: Rect, buf: &mut Buffer) {
// TODO: Status bar should have drop down menus // TODO: Status bar should have drop down menus
Tabs::new(["File", "Edit", "View", "Help"]) Tabs::new(["File", "Edit", "View", "Help"])
.style(Style::default()) .style(Style::default())
@ -101,6 +102,21 @@ impl<'a> App<'a> {
.render(area, buf); .render(area, buf);
} }
fn draw_bottom_status(&self, area: Rect, buf: &mut Buffer) {
// TODO: Set help text based on most recent component enabled.
Paragraph::new(
self.get_component::<Logger>()
.unwrap()
.component_state
.help_text
.clone(),
)
.style(Color::Gray)
.wrap(Wrap { trim: false })
.centered()
.render(area, buf);
}
fn draw_tabs(&self, area: Rect, buf: &mut Buffer) { fn draw_tabs(&self, area: Rect, buf: &mut Buffer) {
// Determine the tab title from the current file (or use a fallback). // Determine the tab title from the current file (or use a fallback).
let mut title: Option<&str> = None; let mut title: Option<&str> = None;
@ -153,9 +169,10 @@ impl<'a> Widget for &mut App<'a> {
let vertical = Layout::default() let vertical = Layout::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
.constraints([ .constraints([
Constraint::Length(3), // status bar Constraint::Length(3), // top status bar
Constraint::Percentage(70), // horizontal layout Constraint::Percentage(70), // horizontal layout
Constraint::Percentage(30), // terminal Constraint::Percentage(30), // terminal
Constraint::Length(3), // bottom status bar
]) ])
.split(area); .split(area);
@ -175,7 +192,8 @@ impl<'a> Widget for &mut App<'a> {
]) ])
.split(horizontal[1]); .split(horizontal[1]);
self.draw_status(vertical[0], buf); self.draw_top_status(vertical[0], buf);
self.draw_bottom_status(vertical[3], buf);
self.draw_tabs(editor_layout[0], buf); self.draw_tabs(editor_layout[0], buf);
let id = self.id().to_string(); let id = self.id().to_string();
for component in &mut self.components { for component in &mut self.components {
@ -225,7 +243,7 @@ impl<'a> Component for App<'a> {
AppComponents::AppEditor(e) => e as &mut dyn Component, AppComponents::AppEditor(e) => e as &mut dyn Component,
AppComponents::AppExplorer(e) => e as &mut dyn Component, AppComponents::AppExplorer(e) => e as &mut dyn Component,
AppComponents::AppLogger(e) => e as &mut dyn Component, AppComponents::AppLogger(e) => e as &mut dyn Component,
AppComponents::AppComponent(e) => e.as_mut() as &mut dyn Component, AppComponents::AppComponent(e) => e.as_mut(),
}; };
if !c.is_active() { if !c.is_active() {
if let Some(mouse) = event.as_mouse_event() { if let Some(mouse) = event.as_mouse_event() {

View File

@ -51,17 +51,24 @@ pub trait Component {
} }
} }
#[derive(Debug, Clone, Copy, Default)] #[derive(Debug, Clone, Default)]
pub struct ComponentState { pub struct ComponentState {
pub(crate) focus: Focus, pub(crate) focus: Focus,
pub(crate) help_text: String,
} }
impl ComponentState { impl ComponentState {
fn new() -> Self { fn new() -> Self {
Self { Self {
focus: Focus::Active, focus: Focus::Active,
help_text: String::new(),
} }
} }
pub(crate) fn with_help_text(mut self, help_text: &str) -> Self {
self.help_text = help_text.into();
self
}
} }
#[derive(Debug, Clone, Copy, Default, PartialEq)] #[derive(Debug, Clone, Copy, Default, PartialEq)]
@ -79,7 +86,10 @@ pub trait FocusState {
impl FocusState for ComponentState { impl FocusState for ComponentState {
fn with_focus(self, focus: Focus) -> Self { fn with_focus(self, focus: Focus) -> Self {
Self { focus } Self {
focus,
help_text: self.help_text,
}
} }
fn set_focus(&mut self, focus: Focus) { fn set_focus(&mut self, focus: Focus) {

View File

@ -46,7 +46,7 @@ impl Editor {
event_handler: EditorEventHandler::default(), event_handler: EditorEventHandler::default(),
file_path: None, file_path: None,
syntax_set: SyntaxSet::load_defaults_nonewlines(), syntax_set: SyntaxSet::load_defaults_nonewlines(),
component_state: Default::default(), component_state: ComponentState::default().with_help_text("TODO: Vim help text"),
} }
} }

View File

@ -39,7 +39,7 @@ impl<'a> Explorer<'a> {
root_path: path.to_owned(), root_path: path.to_owned(),
tree_items: Self::build_tree_from_path(path.to_owned())?, tree_items: Self::build_tree_from_path(path.to_owned())?,
tree_state: TreeState::default(), tree_state: TreeState::default(),
component_state: Default::default(), component_state: ComponentState::default().with_help_text("TODO: Explorer help text."),
}; };
Ok(explorer) Ok(explorer)
} }

View File

@ -33,7 +33,11 @@ impl Logger {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
state: TuiWidgetState::new(), state: TuiWidgetState::new(),
component_state: Default::default(), component_state: ComponentState::default().with_help_text(concat!(
"Q: Quit | Tab: Switch state | ↑/↓: Select target | f: Focus target",
" | ←/→: Display level | +/-: Filter level | Space: Toggle hidden targets",
" | h: Hide target selector | PageUp/Down: Scroll | Esc: Cancel scroll"
)),
} }
} }
} }