ACLEditor/src/ui/mod.rs
2025-04-04 08:07:14 +02:00

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);
}