265 lines
7.3 KiB
Rust
Executable File
265 lines
7.3 KiB
Rust
Executable File
use std::{io, rc::Rc, sync::RwLock, time::Duration};
|
|
|
|
use editor::ACLEditor;
|
|
use log::SimpleLogger;
|
|
use ratatui::{
|
|
crossterm::event::{self, Event, KeyCode},
|
|
layout::{Constraint, Direction, Layout, Rect},
|
|
style::{Modifier, Style},
|
|
text::{Line, Span},
|
|
DefaultTerminal, Frame,
|
|
};
|
|
use tree::Tree;
|
|
|
|
mod editor;
|
|
pub mod log;
|
|
mod tree;
|
|
|
|
pub struct App {
|
|
exit: bool,
|
|
tree: Tree,
|
|
editor: Option<ACLEditor>,
|
|
log: crate::ui::log::SimpleLoggerConsumer,
|
|
}
|
|
|
|
impl Default for App {
|
|
fn default() -> Self {
|
|
let initial_path = if let Some(path) = std::env::args().nth(1) {
|
|
std::path::PathBuf::from(path)
|
|
} else {
|
|
std::env::current_dir().unwrap()
|
|
};
|
|
|
|
let log = SimpleLogger::new().init();
|
|
let log = crate::ui::log::SimpleLoggerConsumer::new(log);
|
|
|
|
Self {
|
|
exit: false,
|
|
tree: Tree::new(initial_path),
|
|
editor: None,
|
|
log,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl App {
|
|
pub fn run(&mut self, terminal: &mut DefaultTerminal) -> io::Result<()> {
|
|
let mut app_event = AppEventHost::default();
|
|
while !self.exit {
|
|
self.update(&mut app_event);
|
|
terminal.draw(|frame| self.render(frame, app_event))?;
|
|
app_event = self.handle_events()?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn handle_events(&mut self) -> io::Result<AppEventHost> {
|
|
// Handle events here
|
|
// For example, you can check for key presses and update the state accordingly
|
|
|
|
if event::poll(Duration::from_millis(100))? {
|
|
let evt = event::read()?;
|
|
if let Event::Key(key) = evt {
|
|
match key.code {
|
|
KeyCode::Char('c') => {
|
|
if key.modifiers == event::KeyModifiers::CONTROL {
|
|
self.exit = true;
|
|
return Ok(AppEventHost::default());
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
Ok(AppEventHost::new(Some(evt)))
|
|
} else {
|
|
Ok(AppEventHost::new(None))
|
|
}
|
|
}
|
|
|
|
pub fn update(&mut self, event: &mut AppEventHost) {
|
|
self.log.update(event.get_client(false));
|
|
event.get_client(true).register_helper("Ctrl + C", "Quit");
|
|
|
|
let cc = event.get_client(self.editor.is_none());
|
|
if cc.is_focused_and_key_pressed(KeyCode::Enter, None)
|
|
|| cc.is_focused_and_key_pressed(KeyCode::Char('e'), None)
|
|
{
|
|
self.editor = Some(ACLEditor::new(self.tree.get_selected()));
|
|
}
|
|
|
|
if let Some(editor) = &mut self.editor {
|
|
editor.update(event.get_client(true));
|
|
if editor.exited {
|
|
self.editor = None;
|
|
}
|
|
}
|
|
if self.editor.is_none() {
|
|
event.get_client(true).register_helper("Enter", "Select");
|
|
}
|
|
|
|
self.tree.update(event.get_client(self.editor.is_none()));
|
|
}
|
|
|
|
pub fn render(&mut self, frame: &mut Frame, mut event: AppEventHost) {
|
|
event.event = None;
|
|
let [content_area, log_area, help_area] = Layout::default()
|
|
.direction(Direction::Vertical)
|
|
.constraints([
|
|
Constraint::Fill(1),
|
|
Constraint::Length(5),
|
|
Constraint::Length(2),
|
|
])
|
|
.areas(frame.area());
|
|
|
|
let [content_left_area, content_right_area] = Layout::default()
|
|
.direction(Direction::Horizontal)
|
|
.constraints([Constraint::Percentage(30), Constraint::Fill(1)])
|
|
.areas(content_area);
|
|
|
|
self.tree.render(frame, content_left_area);
|
|
|
|
if let Some(editor) = self.editor.as_mut() {
|
|
editor.render(frame, content_right_area);
|
|
} else {
|
|
frame.render_widget("Select folder from the left", content_right_area);
|
|
}
|
|
|
|
self.log.render(frame, log_area);
|
|
event.render_help(frame, help_area);
|
|
}
|
|
}
|
|
|
|
pub struct AppEventHost {
|
|
pub event: Option<Event>,
|
|
pub registered_helper: Rc<RwLock<Vec<AppEventHelper>>>,
|
|
pub was_handled: Rc<RwLock<bool>>,
|
|
}
|
|
|
|
impl Default for AppEventHost {
|
|
fn default() -> Self {
|
|
Self {
|
|
event: None,
|
|
registered_helper: Rc::from(RwLock::new(vec![])),
|
|
was_handled: Rc::from(RwLock::new(false)),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl AppEventHost {
|
|
pub fn new(event: Option<Event>) -> Self {
|
|
Self {
|
|
event: event,
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
fn render_help(&self, frame: &mut Frame, area: Rect) {
|
|
let mut spans = vec![];
|
|
let infos = self.registered_helper.read().unwrap();
|
|
let mut first = true;
|
|
for info in infos.iter() {
|
|
if first {
|
|
first = false;
|
|
spans.push(Span::from("Help: "));
|
|
} else {
|
|
spans.push(Span::from(", "));
|
|
}
|
|
|
|
let b = Span::styled(&info.key, Style::default().add_modifier(Modifier::BOLD));
|
|
spans.push(b);
|
|
|
|
spans.push(Span::from(" - "));
|
|
let s = Span::styled(&info.help, Style::default());
|
|
spans.push(s);
|
|
}
|
|
|
|
let help = ratatui::widgets::Paragraph::new(Line::from(spans))
|
|
.wrap(ratatui::widgets::Wrap { trim: true });
|
|
frame.render_widget(help, area);
|
|
}
|
|
|
|
fn get_client(&self, focus: bool) -> AppEventClient {
|
|
AppEventClient {
|
|
host: self.registered_helper.clone(),
|
|
event: self.event.clone(),
|
|
has_focus: focus,
|
|
was_handled: self.was_handled.clone(),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct AppEventClient {
|
|
host: Rc<RwLock<Vec<AppEventHelper>>>,
|
|
event: Option<Event>,
|
|
pub was_handled: Rc<RwLock<bool>>,
|
|
pub has_focus: bool,
|
|
}
|
|
|
|
impl AppEventClient {
|
|
pub fn has_focus(&self) -> bool {
|
|
self.has_focus
|
|
}
|
|
|
|
pub fn get_client(&self, focus: bool) -> Self {
|
|
Self {
|
|
host: self.host.clone(),
|
|
event: self.event.clone(),
|
|
was_handled: self.was_handled.clone(),
|
|
has_focus: focus,
|
|
}
|
|
}
|
|
|
|
pub fn was_handled(&self) -> bool {
|
|
*self.was_handled.read().unwrap()
|
|
}
|
|
|
|
pub fn mark_as_handled(&mut self) {
|
|
*self.was_handled.write().unwrap() = true;
|
|
}
|
|
|
|
pub fn is_key_pressed(&self, key: KeyCode, modifiers: Option<event::KeyModifiers>) -> bool {
|
|
if *self.was_handled.read().unwrap() {
|
|
return false;
|
|
}
|
|
|
|
if let Some(evt) = &self.event {
|
|
if let Event::Key(k) = evt {
|
|
if let Some(mods) = modifiers {
|
|
return k.code == key && k.modifiers == mods;
|
|
}
|
|
return k.code == key;
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
pub fn is_focused_and_key_pressed(
|
|
&self,
|
|
key: KeyCode,
|
|
modifiers: Option<event::KeyModifiers>,
|
|
) -> bool {
|
|
if self.has_focus() {
|
|
self.is_key_pressed(key, modifiers)
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
pub fn register_helper(&self, key: &str, help: &str) {
|
|
self.host.write().unwrap().push(AppEventHelper {
|
|
key: key.to_string(),
|
|
help: help.to_string(),
|
|
});
|
|
}
|
|
}
|
|
|
|
pub struct AppEventHelper {
|
|
pub key: String,
|
|
pub help: String,
|
|
}
|
|
|
|
pub trait Component {
|
|
fn update(&mut self, event: AppEventClient);
|
|
fn render(&mut self, frame: &mut Frame, rect: Rect);
|
|
}
|