TUI #1
@ -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.
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
pub mod app;
|
||||
mod component;
|
||||
mod explorer;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
@ -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
40
src/tui/component.rs
Normal 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);
|
||||
}
|
||||
@ -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> {}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user