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, 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 { // 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, pub registered_helper: Rc>>, pub was_handled: Rc>, } 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) -> 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>>, event: Option, pub was_handled: Rc>, 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) -> 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, ) -> 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); }