ghost-eye/src/app.rs

260 lines
8.2 KiB
Rust
Raw Normal View History

use color_eyre::Result;
use crossterm::event::KeyEvent;
use ratatui::prelude::Rect;
use serde::{Serialize, Deserialize};
use tokio::sync::mpsc::{UnboundedSender, UnboundedReceiver};
use std::sync::mpsc::Sender;
use tracing::info;
use crate::{
action::Action,
config::Config,
tui::{Event, Tui},
components::{
menu::Menu, version::Version, explorer::Explorer, empty::Empty,
health::Health, fps::FpsCounter, Component},
};
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Mode {
Explorer(#[serde(skip)] bool),
Empty(#[serde(skip)] bool),
}
impl Default for Mode {
fn default() -> Self {
Self::Explorer(false)
}
}
pub struct App {
network_tx: Sender<Action>,
action_tx: UnboundedSender<Action>,
action_rx: UnboundedReceiver<Action>,
frame_rate: f32,
tick_rate: f32,
mouse: bool,
paste: bool,
config: Config,
components: Vec<Box<dyn Component>>,
should_quite: bool,
should_suspend: bool,
mode: Mode,
last_tick_key_events: Vec<KeyEvent>,
}
impl App {
pub fn new(
network_tx: Sender<Action>,
action_tx: UnboundedSender<Action>,
action_rx: UnboundedReceiver<Action>,
) -> Result<Self> {
Ok(Self {
network_tx,
action_tx,
action_rx,
frame_rate: 4.0,
tick_rate: 60.0,
mouse: false,
paste: false,
config: Config::new()?,
components: vec![
Box::new(Menu::default()),
Box::new(FpsCounter::default()),
Box::new(Health::default()),
Box::new(Version::default()),
Box::new(Explorer::default()),
Box::new(Empty::default()),
],
should_quite: false,
should_suspend: false,
mode: Mode::default(),
last_tick_key_events: Vec::new(),
})
}
pub fn with_frame_rate(mut self, frame_rate: f32) -> Self {
self.frame_rate = frame_rate;
self
}
pub fn with_tick_rate(mut self, tick_rate: f32) -> Self {
self.tick_rate = tick_rate;
self
}
pub fn with_mouse(mut self, mouse: bool) -> Self {
self.mouse = mouse;
self
}
pub fn with_paste(mut self, paste: bool) -> Self {
self.paste = paste;
self
}
pub async fn run(&mut self) -> Result<()> {
let mut tui = Tui::new()?;
tui.enter()?;
for component in self.components.iter_mut() {
component.register_action_handler(self.action_tx.clone())?;
}
for component in self.components.iter_mut() {
component.register_config_handler(self.config.clone())?;
}
for component in self.components.iter_mut() {
component.init(tui.size()?)?;
}
let action_tx = self.action_tx.clone();
loop {
self.handle_events(&mut tui).await?;
self.handle_actions(&mut tui)?;
if self.should_suspend {
tui.suspend()?;
action_tx.send(Action::Resume)?;
action_tx.send(Action::ClearScreen)?;
tui.enter()?;
} else if self.should_quite {
tui.stop()?;
break;
}
}
tui.exit()?;
Ok(())
}
async fn handle_events(&mut self, tui: &mut Tui) -> Result<()> {
let Some(event) = tui.next_event().await else {
return Ok(());
};
let action_tx = self.action_tx.clone();
match event {
Event::Quit => action_tx.send(Action::Quit)?,
Event::Tick => action_tx.send(Action::Tick)?,
Event::Render => action_tx.send(Action::Render)?,
Event::Resize(x, y) => action_tx.send(Action::Resize(x, y))?,
Event::Key(key) => self.handle_key_event(key)?,
Event::Node => self.trigger_node_events()?,
Event::FastNode => self.trigger_node_fast_events()?,
Event::Runtime => self.trigger_runtime_events()?,
_ => {}
}
for component in self.components.iter_mut() {
if let Some(action) = component.handle_events(Some(event.clone()))? {
action_tx.send(action)?;
}
}
Ok(())
}
fn trigger_node_fast_events(&mut self) -> Result<()> {
self.network_tx.send(Action::GetPendingExtrinsics)?;
Ok(())
}
fn trigger_node_events(&mut self) -> Result<()> {
self.network_tx.send(Action::GetNodeName)?;
self.network_tx.send(Action::GetSyncState)?;
self.network_tx.send(Action::GetGenesisHash)?;
self.network_tx.send(Action::GetChainName)?;
self.network_tx.send(Action::GetNodeVersion)?;
Ok(())
}
fn trigger_runtime_events(&mut self) -> Result<()> {
self.network_tx.send(Action::GetLatestBlock)?;
self.network_tx.send(Action::GetFinalizedBlock)?;
self.network_tx.send(Action::GetActiveEra)?;
self.network_tx.send(Action::GetEpoch)?;
self.network_tx.send(Action::GetValidators)?;
Ok(())
}
fn handle_key_event(&mut self, key: KeyEvent) -> Result<()> {
let action_tx = self.action_tx.clone();
let Some(keymap) = self.config.keybindings.get(&self.mode) else {
return Ok(());
};
match keymap.get(&vec![key]) {
Some(action) => {
info!("got action: {action:?}");
action_tx.send(action.clone())?;
}
_ => {
self.last_tick_key_events.push(key);
if let Some(action) = keymap.get(&self.last_tick_key_events) {
info!("got action: {action:?}");
action_tx.send(action.clone())?;
}
}
}
Ok(())
}
fn handle_actions(&mut self, tui: &mut Tui) -> Result<()> {
while let Ok(action) = self.action_rx.try_recv() {
match action {
Action::Tick => { self.last_tick_key_events.drain(..); },
Action::Quit => self.should_quite = true,
Action::Suspend => self.should_suspend = true,
Action::Resume => self.should_suspend = false,
Action::ClearScreen => tui.terminal.clear()?,
Action::Resize(x, y) => self.handle_resize(tui, x, y)?,
Action::Render => self.render(tui)?,
Action::SetMode(mode) => self.mode = mode,
_ => {}
}
for component in self.components.iter_mut() {
if let Some(action) = component.update(action.clone())? {
self.action_tx.send(action)?
};
}
}
Ok(())
}
fn handle_resize(&mut self, tui: &mut Tui, w: u16, h: u16) -> Result<()> {
tui.resize(Rect::new(0, 0, w, h))?;
self.render(tui)?;
Ok(())
}
fn render(&mut self, tui: &mut Tui) -> Result<()> {
tui.draw(|frame| {
for component in self.components.iter_mut().take(4) {
if let Err(err) = (*component).draw(frame, frame.area()) {
let _ = self
.action_tx
.send(Action::Error(format!("failed to draw: {:?}", err)));
}
}
match self.mode {
Mode::Explorer(_) => {
if let Some(component) = self.components.get_mut(4) {
if let Err(err) = component.draw(frame, frame.area()) {
let _ = self
.action_tx
.send(Action::Error(format!("failed to draw: {:?}", err)));
}
}
},
_ => {
if let Some(component) = self.components.last_mut() {
if let Err(err) = component.draw(frame, frame.area()) {
let _ = self
.action_tx
.send(Action::Error(format!("failed to draw: {:?}", err)));
}
}
},
}
})?;
Ok(())
}
}