TUI #1

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

View File

@ -6,7 +6,6 @@ use structopt::StructOpt;
pub mod gui;
pub mod tui;
/// 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 path is provided, the current directory is used.

View File

@ -1,4 +1,5 @@
pub mod app;
mod component;
mod explorer;
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::crossterm::event;
use ratatui::crossterm::event::{Event, KeyCode};
use ratatui::layout::{Constraint, Direction, Layout, Rect};
use ratatui::prelude::{Color, Style, Widget};
use ratatui::widgets::{Block, Borders, Padding, Paragraph, Tabs, Wrap};
use ratatui::{DefaultTerminal, symbols};
use std::time::Duration;
use crate::tui::explorer::Explorer;
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone)]
pub struct App<'a> {
explorer: Explorer<'a>,
}
@ -24,37 +20,29 @@ impl<'a> App<'a> {
pub fn run(mut self, mut terminal: DefaultTerminal) -> anyhow::Result<()> {
loop {
terminal.draw(|f| f.render_widget(self, f.area()))?;
if self.should_quit()? {
break;
terminal.draw(|f| {
f.render_widget(&self, f.area());
})?;
// 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(())
}
fn handle_events(&mut self) -> anyhow::Result<()> {
// Handle other keyboard events here, aside from quitting.
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) {
fn draw_status(&self, area: Rect, buf: &mut Buffer) {
// TODO: Status bar should have drop down menus
Tabs::new(["File", "Edit", "View", "Help"])
.style(Style::default())
.block(Block::default().borders(Borders::ALL))
.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
Tabs::new(["file.md", "file.cpp"])
.divider(symbols::DOT)
@ -67,7 +55,7 @@ impl<'a> App<'a> {
.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: Content should be file contents
// TODO: Contents should use vim in rendered TTY
@ -85,7 +73,7 @@ impl<'a> App<'a> {
.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: Contents should be shell output
Paragraph::new("shaun@pc:~/Code/clide$ ")
@ -102,7 +90,7 @@ impl<'a> App<'a> {
}
// 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)
where
Self: Sized,
@ -128,16 +116,18 @@ impl<'a> Widget for App<'a> {
.direction(Direction::Vertical)
.constraints([
Constraint::Length(1), // Editor tabs.
Constraint::Fill(1),
Constraint::Fill(1), // Editor contents.
])
.split(horizontal[1]);
self.draw_status(vertical[0], 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_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::layout::Rect;
use ratatui::prelude::Style;
use ratatui::widgets::{Block, Borders, Widget};
use std::fs;
use tui_tree_widget::{Tree, TreeItem};
use uuid::Uuid;
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Debug)]
pub struct Explorer<'a> {
root_path: &'a std::path::Path,
tree_items: TreeItem<'a, String>,
}
impl<'a> Explorer<'a> {
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) {
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);
}
pub fn draw(&self, area: Rect, buf: &mut Buffer) {}
fn build_tree_from_path(path: std::path::PathBuf) -> TreeItem<'static, String> {
let mut children = vec![];
@ -56,3 +56,15 @@ impl<'a> Explorer<'a> {
.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> {}