Create App struct for TUI.
This commit is contained in:
146
src/tui/app.rs
Normal file
146
src/tui/app.rs
Normal file
@@ -0,0 +1,146 @@
|
||||
use anyhow::Context;
|
||||
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;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct App<'a> {
|
||||
root_path: &'a std::path::Path,
|
||||
}
|
||||
|
||||
impl<'a> App<'a> {
|
||||
pub(crate) fn new(root_path: &'a std::path::Path) -> Self {
|
||||
Self { root_path }
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
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) {
|
||||
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) {
|
||||
// TODO: Tabs should be opened from file explorer
|
||||
Tabs::new(["file.md", "file.cpp"])
|
||||
.divider(symbols::DOT)
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::NONE)
|
||||
.padding(Padding::new(0, 0, 0, 0)),
|
||||
)
|
||||
.highlight_style(Style::default().fg(Color::LightRed))
|
||||
.render(area, buf);
|
||||
}
|
||||
|
||||
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
|
||||
// TODO: Vimrc should be used
|
||||
Paragraph::new("This is an example of the TUI interface (press 'q' to quit)")
|
||||
.style(Style::default())
|
||||
.block(
|
||||
Block::default()
|
||||
.title("Rust")
|
||||
.title_style(Style::default().fg(Color::Yellow))
|
||||
.borders(Borders::ALL)
|
||||
.padding(Padding::new(0, 0, 0, 1)),
|
||||
)
|
||||
.wrap(Wrap { trim: false })
|
||||
.render(area, buf);
|
||||
}
|
||||
|
||||
fn draw_terminal(self, area: Rect, buf: &mut Buffer) {
|
||||
// TODO: Title should be detected shell name
|
||||
// TODO: Contents should be shell output
|
||||
Paragraph::new("Terminal placeholder")
|
||||
.style(Style::default())
|
||||
.block(
|
||||
Block::default()
|
||||
.title("Bash")
|
||||
.title_style(Style::default().fg(Color::DarkGray))
|
||||
.borders(Borders::ALL),
|
||||
)
|
||||
.wrap(Wrap { trim: false })
|
||||
.render(area, buf);
|
||||
}
|
||||
|
||||
fn draw_file_explorer(self, area: Rect, buf: &mut Buffer) {
|
||||
Paragraph::new("File explorer placeholder")
|
||||
.style(Style::default())
|
||||
.block(Block::default().borders(Borders::ALL))
|
||||
.render(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Separate complex components into their own widgets.
|
||||
impl<'a> Widget for App<'a> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer)
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let vertical = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([
|
||||
Constraint::Length(3), // status bar
|
||||
Constraint::Percentage(70), // horizontal layout
|
||||
Constraint::Percentage(30), // terminal
|
||||
])
|
||||
.split(area);
|
||||
|
||||
let horizontal = Layout::default()
|
||||
.direction(Direction::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]);
|
||||
|
||||
let editor_layout = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([
|
||||
Constraint::Length(1), // Editor tabs.
|
||||
Constraint::Fill(1),
|
||||
])
|
||||
.split(horizontal[1]);
|
||||
|
||||
self.draw_status(vertical[0], buf);
|
||||
self.draw_terminal(vertical[2], buf);
|
||||
|
||||
self.draw_file_explorer(horizontal[0], buf);
|
||||
|
||||
self.draw_tabs(editor_layout[0], buf);
|
||||
self.draw_editor(editor_layout[1], buf);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user