diff --git a/Cargo.toml b/Cargo.toml index 77210a0..b211cbf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "ghost-eye" authors = ["str3tch "] description = "Application for interacting with Casper/Ghost nodes that are exposing RPC only to the localhost" -version = "0.3.59" +version = "0.3.60" edition = "2021" homepage = "https://git.ghostchain.io/ghostchain" repository = "https://git.ghostchain.io/ghostchain/ghost-eye" diff --git a/src/components/generic/activatable.rs b/src/components/generic/activatable.rs new file mode 100644 index 0000000..4653b96 --- /dev/null +++ b/src/components/generic/activatable.rs @@ -0,0 +1,6 @@ +pub trait Activatable { + fn is_active(&self) -> bool; + fn is_inactive(&self) -> bool; + fn set_active(&mut self); + fn set_inactive(&mut self); +} diff --git a/src/components/generic/helpable.rs b/src/components/generic/helpable.rs new file mode 100644 index 0000000..ae1fdc6 --- /dev/null +++ b/src/components/generic/helpable.rs @@ -0,0 +1,11 @@ +use color_eyre::Result; +use crate::action::Action; + +use super::Activatable; + +pub trait Helpable: Activatable { + fn open_help_popup(&mut self) -> Result> { + self.set_inactive(); + Ok(Some(Action::Help)) + } +} diff --git a/src/components/generic/mod.rs b/src/components/generic/mod.rs new file mode 100644 index 0000000..ac13d13 --- /dev/null +++ b/src/components/generic/mod.rs @@ -0,0 +1,7 @@ +mod activatable; +mod helpable; +mod scrollable; + +pub use activatable::Activatable; +pub use helpable::Helpable; +pub use scrollable::Scrollable; diff --git a/src/components/generic/scrollable.rs b/src/components/generic/scrollable.rs new file mode 100644 index 0000000..d550f91 --- /dev/null +++ b/src/components/generic/scrollable.rs @@ -0,0 +1,58 @@ +use std::{ + cmp::{PartialEq, PartialOrd}, ops::{Add, Sub} +}; + +use color_eyre::Result; +use crate::action::Action; + +pub trait Scrollable { + type IndexType: Add + + Sub + + PartialOrd + + PartialEq + + From + + Copy; + + fn apply_next_row(&mut self, new_index: Self::IndexType) -> Result>; + fn apply_prev_row(&mut self, new_index: Self::IndexType) -> Result>; + + fn selected_index(&self) -> Option; + fn items_length(&self) -> Self::IndexType; + + fn next_row(&mut self) -> Result> { + let length = self.items_length(); + if length == 0.into() { + return Ok(None); + } + + let new_index = match self.selected_index() { + Some(index) => { + if index < length - 1.into() { + index + 1.into() + } else { + index + } + }, + None => 0.into(), + }; + self.apply_next_row(new_index) + } + + fn prev_row(&mut self) -> Result> { + if self.items_length() == 0.into() { + return Ok(None); + } + + let new_index = match self.selected_index() { + Some(index) => { + if index == 0.into() { + 0.into() + } else { + index - 1.into() + } + } + None => 0.into() + }; + self.apply_prev_row(new_index) + } +} diff --git a/src/components/help.rs b/src/components/help.rs index a621d5a..21ccc2a 100644 --- a/src/components/help.rs +++ b/src/components/help.rs @@ -12,9 +12,13 @@ use ratatui::{ }; use super::palette::StylePalette; +use super::generic::{Activatable, Helpable, Scrollable}; use super::Component; + use crate::{action::Action, app::Mode, config::Config}; +const ITEM_HEIGHT: usize = 3; + #[derive(Debug, Clone)] pub struct Help { is_active: bool, @@ -24,32 +28,10 @@ pub struct Help { table_state: TableState, } -const ITEM_HEIGHT: usize = 3; - impl Help { - fn move_down(&mut self) -> Result> { - let i = match self.table_state.selected() { - Some(i) => { - if i >= self.current_mode.get_help_data().len() { - 0 - } else { - i + 1 - } - }, - None => 0, - }; - self.table_state.select(Some(i)); - self.scroll_state = self.scroll_state.position(i * ITEM_HEIGHT); - Ok(None) - } - - fn move_up(&mut self) -> Result> { - let i = match self.table_state.selected() { - Some(i) => i.saturating_sub(1), - None => 0, - }; - self.table_state.select(Some(i)); - self.scroll_state = self.scroll_state.position(i * ITEM_HEIGHT); + fn move_index(&mut self, new_index: usize) -> Result> { + self.table_state.select(Some(new_index)); + self.scroll_state = self.scroll_state.position(new_index * ITEM_HEIGHT); Ok(None) } } @@ -66,6 +48,35 @@ impl Default for Help { } } +impl Activatable for Help { + fn is_active(&self) -> bool { self.is_active } + fn is_inactive(&self) -> bool { !self.is_active } + fn set_inactive(&mut self) { self.is_active = false; } + fn set_active(&mut self) { self.is_active = true; } +} + +impl Helpable for Help { } + +impl Scrollable for Help { + type IndexType = usize; + + fn selected_index(&self) -> Option { + self.table_state.selected() + } + + fn items_length(&self) -> Self::IndexType { + self.current_mode.get_help_data().len() + } + + fn apply_next_row(&mut self, new_index: Self::IndexType) -> Result> { + self.move_index(new_index) + } + + fn apply_prev_row(&mut self, new_index: Self::IndexType) -> Result> { + self.move_index(new_index) + } +} + impl Component for Help { fn register_config_handler(&mut self, config: Config) -> Result<()> { if let Some(style) = config.styles.get(&crate::app::Mode::Help) { @@ -80,7 +91,7 @@ impl Component for Help { fn update(&mut self, action: Action) -> Result> { match action { - Action::Help if !self.is_active => self.is_active = true, + Action::Help if self.is_inactive() => self.set_active(), Action::SetActiveScreen(mode) => { if self.current_mode != mode { self.current_mode = mode; @@ -95,10 +106,10 @@ impl Component for Help { fn handle_key_event(&mut self, key: KeyEvent) -> Result> { match key.code { - KeyCode::Up | KeyCode::Char('k') => self.move_up(), - KeyCode::Down | KeyCode::Char('j') => self.move_down(), - KeyCode::Esc if self.is_active => { - self.is_active = false; + KeyCode::Up | KeyCode::Char('k') if self.is_active() => self.next_row(), + KeyCode::Down | KeyCode::Char('j') if self.is_active() => self.prev_row(), + KeyCode::Esc if self.is_active() => { + self.set_inactive(); Ok(Some(Action::SetActiveScreen(self.current_mode))) }, _ => Ok(None), @@ -106,7 +117,7 @@ impl Component for Help { } fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { - if self.is_active { + if self.is_active() { let highlight_symbol = Text::from(vec![ "".into(), " █ ".into(), diff --git a/src/components/menu.rs b/src/components/menu.rs index fdb345b..8f27a35 100644 --- a/src/components/menu.rs +++ b/src/components/menu.rs @@ -1,14 +1,14 @@ use color_eyre::Result; use ratatui::{prelude::*, widgets::*}; -use tokio::sync::mpsc::UnboundedSender; use crossterm::event::{KeyEvent, KeyCode}; use super::Component; use super::palette::StylePalette; use crate::{config::Config, action::Action, app::Mode}; +use super::generic::{Activatable, Helpable, Scrollable}; + pub struct Menu { - command_tx: Option>, list_state: ListState, items: Vec, is_active: bool, @@ -16,15 +16,41 @@ pub struct Menu { } impl Default for Menu { - fn default() -> Self { - Menu::new() + fn default() -> Self { Menu::new() } +} + +impl Activatable for Menu { + fn is_active(&self) -> bool { self.is_active } + fn is_inactive(&self) -> bool { !self.is_active } + fn set_inactive(&mut self) { self.is_active = false; } + fn set_active(&mut self) { self.is_active = true; } +} + +impl Helpable for Menu { } + +impl Scrollable for Menu { + type IndexType = usize; + + fn selected_index(&self) -> Option { + self.list_state.selected() + } + + fn items_length(&self) -> Self::IndexType { + self.items.len() + } + + fn apply_next_row(&mut self, new_index: Self::IndexType) -> Result> { + self.move_index(new_index) + } + + fn apply_prev_row(&mut self, new_index: Self::IndexType) -> Result> { + self.move_index(new_index) } } impl Menu { pub fn new() -> Self { let mut new_list = Self { - command_tx: None, list_state: ListState::default(), items: vec![ String::from("Explorer"), @@ -42,39 +68,9 @@ impl Menu { new_list } - fn next_row(&mut self) -> Result> { - let i = match self.list_state.selected() { - Some(i) => { - if i >= self.items.len() - 1 { - i - } else { - i + 1 - } - }, - None => 0, - }; - self.list_state.select(Some(i)); - match i { - 0 => Ok(Some(Action::SetMode(Mode::Explorer))), - 1 => Ok(Some(Action::SetMode(Mode::Wallet))), - 2 => Ok(Some(Action::SetMode(Mode::Validator))), - _ => Ok(Some(Action::SetMode(Mode::Empty))), - } - } - - fn previous_row(&mut self) -> Result> { - let i = match self.list_state.selected() { - Some(i) => { - if i == 0 { - 0 - } else { - i - 1 - } - }, - None => 0 - }; - self.list_state.select(Some(i)); - match i { + fn move_index(&mut self, new_index: usize) -> Result> { + self.list_state.select(Some(new_index)); + match new_index { 0 => Ok(Some(Action::SetMode(Mode::Explorer))), 1 => Ok(Some(Action::SetMode(Mode::Wallet))), 2 => Ok(Some(Action::SetMode(Mode::Validator))), @@ -86,17 +82,12 @@ impl Menu { impl Component for Menu { fn update(&mut self, action: Action) -> Result> { match action { - Action::SetActiveScreen(Mode::Menu) => self.is_active = true, + Action::SetActiveScreen(Mode::Menu) => self.set_active(), _ => {} - }; + } Ok(None) } - fn register_action_handler(&mut self, tx: UnboundedSender) -> Result<()> { - self.command_tx = Some(tx); - Ok(()) - } - fn register_config_handler(&mut self, config: Config) -> Result<()> { if let Some(style) = config.styles.get(&crate::app::Mode::Menu) { self.palette.with_normal_style(style.get("normal_style").copied()); @@ -110,10 +101,10 @@ impl Component for Menu { fn handle_key_event(&mut self, key: KeyEvent) -> Result> { match key.code { - KeyCode::Up | KeyCode::Char('k') if self.is_active => self.previous_row(), - KeyCode::Down | KeyCode::Char('j') if self.is_active => self.next_row(), - KeyCode::Enter | KeyCode::Char('l') | KeyCode::Right if self.is_active => { - self.is_active = false; + KeyCode::Up | KeyCode::Char('k') if self.is_active() => self.prev_row(), + KeyCode::Down | KeyCode::Char('j') if self.is_active() => self.next_row(), + KeyCode::Enter | KeyCode::Char('l') | KeyCode::Right if self.is_active() => { + self.set_inactive(); match self.list_state.selected() { Some(0) => Ok(Some(Action::SetActiveScreen(Mode::Explorer))), Some(1) => Ok(Some(Action::SetActiveScreen(Mode::Wallet))), @@ -121,10 +112,7 @@ impl Component for Menu { _ => Ok(Some(Action::SetActiveScreen(Mode::Empty))), } }, - KeyCode::Char('?') if self.is_active => { - self.is_active = false; - Ok(Some(Action::Help)) - }, + KeyCode::Char('?') if self.is_active() => self.open_help_popup(), _ => Ok(None), } } @@ -132,7 +120,7 @@ impl Component for Menu { fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { let [menu, _] = super::menu_layout(area); - let (color, border_type) = self.palette.create_border_style(self.is_active); + let (color, border_type) = self.palette.create_border_style(self.is_active()); let block = Block::bordered() .border_style(color) .border_type(border_type); diff --git a/src/components/mod.rs b/src/components/mod.rs index 583e81d..f99d43e 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -18,6 +18,7 @@ pub mod wallet; pub mod validator; pub mod empty; pub mod help; +pub mod generic; pub trait Component { fn register_network_handler(&mut self, tx: Sender) -> Result<()> {