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, wallet::Wallet, validator::Validator, nominator::Nominator, empty::Empty, health::Health, fps::FpsCounter, Component, }, }; #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum Mode { Menu, Explorer, Wallet, Validator, Nominator, Empty, } impl Default for Mode { fn default() -> Self { Self::Explorer } } pub struct App { network_tx: Sender, action_tx: UnboundedSender, action_rx: UnboundedReceiver, frame_rate: f32, tick_rate: f32, mouse: bool, paste: bool, config: Config, components: Vec>, should_quite: bool, should_suspend: bool, mode: Mode, last_tick_key_events: Vec, } impl App { pub fn new( network_tx: Sender, action_tx: UnboundedSender, action_rx: UnboundedReceiver, ) -> Result { 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(Wallet::default()), Box::new(Validator::default()), Box::new(Nominator::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_network_handler(self.network_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::Oneshot => self.trigger_oneshot_node_events()?, Event::Node => self.trigger_node_fast_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)?; self.network_tx.send(Action::GetConnectedPeers)?; self.network_tx.send(Action::CheckPendingTransactions)?; Ok(()) } fn trigger_oneshot_node_events(&mut self) -> Result<()> { self.network_tx.send(Action::GetNodeName)?; self.network_tx.send(Action::GetSystemHealth)?; self.network_tx.send(Action::GetGenesisHash)?; self.network_tx.send(Action::GetChainName)?; self.network_tx.send(Action::GetChainVersion)?; self.network_tx.send(Action::GetExistentialDeposit)?; self.network_tx.send(Action::GetLocalIdentity)?; self.network_tx.send(Action::GetListenAddresses)?; 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))); } } }, Mode::Wallet => { if let Some(component) = self.components.get_mut(5) { if let Err(err) = component.draw(frame, frame.area()) { let _ = self .action_tx .send(Action::Error(format!("failed to draw: {:?}", err))); } } }, Mode::Validator => { if let Some(component) = self.components.get_mut(6) { if let Err(err) = component.draw(frame, frame.area()) { let _ = self .action_tx .send(Action::Error(format!("failed to draw: {:?}", err))); } } }, Mode::Nominator => { if let Some(component) = self.components.get_mut(7) { 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(()) } }