[tui] Add basic component trait.

This commit is contained in:
Shaun Reed 2026-01-17 17:09:42 -05:00
parent b65565adfa
commit 733a43ccde
5 changed files with 87 additions and 45 deletions

View File

@ -6,7 +6,6 @@ use structopt::StructOpt;
pub mod gui; pub mod gui;
pub mod tui; pub mod tui;
/// Command line interface IDE with full GUI and headless modes. /// Command line interface IDE with full GUI and headless modes.
/// If no flags are provided, the GUI editor is launched in a separate process. /// If no flags are provided, the GUI editor is launched in a separate process.
/// If no path is provided, the current directory is used. /// If no path is provided, the current directory is used.

View File

@ -1,4 +1,5 @@
pub mod app; pub mod app;
mod component;
mod explorer; mod explorer;
use anyhow::{Context, Result}; use anyhow::{Context, Result};

View File

@ -1,16 +1,12 @@
use anyhow::Context; use crate::tui::component::{Action, ClideComponent};
use crate::tui::explorer::Explorer;
use ratatui::buffer::Buffer; use ratatui::buffer::Buffer;
use ratatui::crossterm::event;
use ratatui::crossterm::event::{Event, KeyCode};
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, Paragraph, Tabs, Wrap}; use ratatui::widgets::{Block, Borders, Padding, Paragraph, Tabs, Wrap};
use ratatui::{DefaultTerminal, symbols}; use ratatui::{DefaultTerminal, symbols};
use std::time::Duration;
use crate::tui::explorer::Explorer; #[derive(Debug, Clone)]
#[derive(Debug, Clone, Copy)]
pub struct App<'a> { pub struct App<'a> {
explorer: Explorer<'a>, explorer: Explorer<'a>,
} }
@ -24,37 +20,29 @@ impl<'a> App<'a> {
pub fn run(mut self, mut terminal: DefaultTerminal) -> anyhow::Result<()> { pub fn run(mut self, mut terminal: DefaultTerminal) -> anyhow::Result<()> {
loop { loop {
terminal.draw(|f| f.render_widget(self, f.area()))?; terminal.draw(|f| {
if self.should_quit()? { f.render_widget(&self, f.area());
break; })?;
// TODO: Handle events based on which component is active.
// match self.explorer.handle_events() { ... }
match self.handle_events() {
Action::Quit => break,
_ => {}
} }
self.handle_events()?;
} }
Ok(()) Ok(())
} }
fn handle_events(&mut self) -> anyhow::Result<()> { fn draw_status(&self, area: Rect, buf: &mut Buffer) {
// Handle other keyboard events here, aside from quitting. // TODO: Status bar should have drop down menus
Ok(())
}
fn should_quit(self) -> anyhow::Result<bool> {
if event::poll(Duration::from_millis(250)).context("event poll failed")? {
if let Event::Key(key) = event::read().context("event read failed")? {
return Ok(KeyCode::Char('q') == key.code);
}
}
Ok(false)
}
fn draw_status(self, area: Rect, buf: &mut Buffer) {
Tabs::new(["File", "Edit", "View", "Help"]) Tabs::new(["File", "Edit", "View", "Help"])
.style(Style::default()) .style(Style::default())
.block(Block::default().borders(Borders::ALL)) .block(Block::default().borders(Borders::ALL))
.render(area, buf); .render(area, buf);
} }
fn draw_tabs(self, area: Rect, buf: &mut Buffer) { fn draw_tabs(&self, area: Rect, buf: &mut Buffer) {
// TODO: Tabs should be opened from file explorer // TODO: Tabs should be opened from file explorer
Tabs::new(["file.md", "file.cpp"]) Tabs::new(["file.md", "file.cpp"])
.divider(symbols::DOT) .divider(symbols::DOT)
@ -67,7 +55,7 @@ impl<'a> App<'a> {
.render(area, buf); .render(area, buf);
} }
fn draw_editor(self, area: Rect, buf: &mut Buffer) { fn draw_editor(&self, area: Rect, buf: &mut Buffer) {
// TODO: Title should be detected programming language name // TODO: Title should be detected programming language name
// TODO: Content should be file contents // TODO: Content should be file contents
// TODO: Contents should use vim in rendered TTY // TODO: Contents should use vim in rendered TTY
@ -85,7 +73,7 @@ impl<'a> App<'a> {
.render(area, buf); .render(area, buf);
} }
fn draw_terminal(self, area: Rect, buf: &mut Buffer) { fn draw_terminal(&self, area: Rect, buf: &mut Buffer) {
// TODO: Title should be detected shell name // TODO: Title should be detected shell name
// TODO: Contents should be shell output // TODO: Contents should be shell output
Paragraph::new("shaun@pc:~/Code/clide$ ") Paragraph::new("shaun@pc:~/Code/clide$ ")
@ -102,7 +90,7 @@ impl<'a> App<'a> {
} }
// TODO: Separate complex components into their own widgets. // TODO: Separate complex components into their own widgets.
impl<'a> Widget for App<'a> { impl<'a> Widget for &App<'a> {
fn render(self, area: Rect, buf: &mut Buffer) fn render(self, area: Rect, buf: &mut Buffer)
where where
Self: Sized, Self: Sized,
@ -128,16 +116,18 @@ impl<'a> Widget for App<'a> {
.direction(Direction::Vertical) .direction(Direction::Vertical)
.constraints([ .constraints([
Constraint::Length(1), // Editor tabs. Constraint::Length(1), // Editor tabs.
Constraint::Fill(1), Constraint::Fill(1), // Editor contents.
]) ])
.split(horizontal[1]); .split(horizontal[1]);
self.draw_status(vertical[0], buf); self.draw_status(vertical[0], buf);
self.draw_terminal(vertical[2], buf); self.draw_terminal(vertical[2], buf);
self.explorer.draw(horizontal[0], buf); self.explorer.render(horizontal[0], buf);
self.draw_tabs(editor_layout[0], buf); self.draw_tabs(editor_layout[0], buf);
self.draw_editor(editor_layout[1], buf); self.draw_editor(editor_layout[1], buf);
} }
} }
impl<'a> ClideComponent for App<'a> {}

40
src/tui/component.rs Normal file
View File

@ -0,0 +1,40 @@
use ratatui::crossterm::event;
use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, MouseEvent};
use std::time::Duration;
pub enum Action {
Noop,
Quit,
}
pub trait ClideComponent {
fn handle_events(&mut self) -> Action {
if !event::poll(Duration::from_millis(250)).expect("event poll failed") {
return Action::Noop;
}
let key_event = event::read().expect("event read failed");
match key_event {
Event::Key(key_event) => self.handle_key_events(key_event),
Event::Mouse(mouse_event) => self.handle_mouse_events(mouse_event),
_ => Action::Noop,
}
}
fn handle_key_events(&mut self, key: KeyEvent) -> Action {
match key.code {
KeyCode::Char('q') => Action::Quit,
_ => Action::Noop,
}
}
fn handle_mouse_events(&mut self, mouse: MouseEvent) -> Action {
Action::Noop
}
fn update(&mut self, action: Action) -> Action {
Action::Noop
}
// fn render(&mut self, area: Rect, buf: &mut Buffer);
}

View File

@ -1,29 +1,29 @@
use std::fs; use crate::tui::component::ClideComponent;
use anyhow::Result;
use ratatui::buffer::Buffer; use ratatui::buffer::Buffer;
use ratatui::layout::Rect; use ratatui::layout::Rect;
use ratatui::prelude::Style; use ratatui::prelude::Style;
use ratatui::widgets::{Block, Borders, Widget}; use ratatui::widgets::{Block, Borders, Widget};
use std::fs;
use tui_tree_widget::{Tree, TreeItem}; use tui_tree_widget::{Tree, TreeItem};
use uuid::Uuid; use uuid::Uuid;
#[derive(Clone, Copy, Debug)] #[derive(Clone, Debug)]
pub struct Explorer<'a> { pub struct Explorer<'a> {
root_path: &'a std::path::Path, root_path: &'a std::path::Path,
tree_items: TreeItem<'a, String>,
} }
impl<'a> Explorer<'a> { impl<'a> Explorer<'a> {
pub fn new(path: &'a std::path::Path) -> Self { pub fn new(path: &'a std::path::Path) -> Self {
Explorer { root_path: path } let mut explorer = Explorer {
root_path: path,
tree_items: Self::build_tree_from_path(path.into()),
};
explorer
} }
pub fn draw(self, area: Rect, buf: &mut Buffer) { pub fn draw(&self, area: Rect, buf: &mut Buffer) {}
let tree_item = Self::build_tree_from_path(self.root_path.to_path_buf());
Tree::new(&tree_item.children())
.expect("Failed to build tree.")
.style(Style::default())
.block(Block::default().borders(Borders::ALL))
.render(area, buf);
}
fn build_tree_from_path(path: std::path::PathBuf) -> TreeItem<'static, String> { fn build_tree_from_path(path: std::path::PathBuf) -> TreeItem<'static, String> {
let mut children = vec![]; let mut children = vec![];
@ -53,6 +53,18 @@ impl<'a> Explorer<'a> {
.to_string(), .to_string(),
children, children,
) )
.expect("Failed to build tree from path.") .expect("Failed to build tree from path.")
} }
} }
impl<'a> Widget for &Explorer<'a> {
fn render(self, area: Rect, buf: &mut Buffer) {
Tree::new(&self.tree_items.children())
.expect("Failed to build tree.")
.style(Style::default())
.block(Block::default().borders(Borders::ALL))
.render(area, buf);
}
}
impl<'a> ClideComponent for Explorer<'a> {}