use crate::{
event::{AppEvent, Event, EventHandler},
message::Message,
model::{AppState, Model},
update,
};
use ratatui::{crossterm::event::KeyCode, DefaultTerminal};
pub struct App {
pub model: Model,
pub events: EventHandler,
}
impl App {
pub fn new() -> Self {
Self {
model: Model::new(),
events: EventHandler::new(),
}
}
pub async fn run(mut self, mut terminal: DefaultTerminal) -> color_eyre::Result<()> {
while !self.model.should_quit() {
terminal.draw(|frame| {
crate::view::render(&mut self.model, frame.area(), frame.buffer_mut())
})?;
let event = self.events.next().await?;
let message = self.event_to_message(event)?;
if let Some(msg) = message {
update::update(&mut self.model, msg).await;
}
}
Ok(())
}
fn event_to_message(&self, event: Event) -> color_eyre::Result<Option<Message>> {
match event {
Event::Tick => Ok(Some(Message::Tick)),
Event::Crossterm(event) => {
if let crossterm::event::Event::Key(key_event) = event {
Ok(self.key_to_message(key_event.code))
} else {
Ok(None)
}
}
Event::App(app_event) => Ok(Some(match app_event {
AppEvent::MenuUp => Message::MenuUp,
AppEvent::MenuDown => Message::MenuDown,
AppEvent::Execute => {
if let Some(command) = self.model.get_selected_command() {
Message::ExecuteCommand(command)
} else {
return Ok(None);
}
}
AppEvent::EnterChild => Message::EnterChild,
AppEvent::ExitChild => Message::ExitChild,
AppEvent::Quit => Message::Quit,
})),
}
}
fn key_to_message(&self, key: KeyCode) -> Option<Message> {
match self.model.state {
AppState::Startup => Some(Message::SkipStartup),
AppState::Menu => match key {
KeyCode::Esc | KeyCode::Char('q') => Some(Message::Quit),
KeyCode::Up | KeyCode::Char('k') => Some(Message::MenuUp),
KeyCode::Down | KeyCode::Char('j') => Some(Message::MenuDown),
KeyCode::Enter => {
if self.model.menu.is_in_child_mode() {
self.model
.get_selected_command()
.map(Message::ExecuteCommand)
} else {
Some(Message::EnterChild)
}
}
KeyCode::Right => {
if !self.model.menu.is_in_child_mode() {
Some(Message::EnterChild)
} else {
None
}
}
KeyCode::Left | KeyCode::Backspace => {
if self.model.menu.is_in_child_mode() {
Some(Message::ExitChild)
} else {
None
}
}
_ => None,
},
AppState::Executing | AppState::Loading => match key {
KeyCode::Esc | KeyCode::Char('q') => Some(Message::ReturnToMenu),
_ => None,
},
AppState::ShowResult => match key {
KeyCode::Up | KeyCode::Char('k') => Some(Message::ScrollUp),
KeyCode::Down | KeyCode::Char('j') => Some(Message::ScrollDown),
KeyCode::PageUp => Some(Message::ScrollPageUp),
KeyCode::PageDown => Some(Message::ScrollPageDown),
KeyCode::Home => Some(Message::ScrollToTop),
KeyCode::End => Some(Message::ScrollToBottom),
KeyCode::Esc | KeyCode::Char('q') | KeyCode::Enter | KeyCode::Backspace => {
Some(Message::ReturnToMenu)
}
_ => Some(Message::ReturnToMenu),
},
}
}
}
impl Default for App {
fn default() -> Self {
Self::new()
}
}