start adding generic traits and default implementations for repeating functionality of components
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
This commit is contained in:
parent
8eb3871e34
commit
6a43dab6dd
@ -2,7 +2,7 @@
|
||||
name = "ghost-eye"
|
||||
authors = ["str3tch <stretch@ghostchain.io>"]
|
||||
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"
|
||||
|
6
src/components/generic/activatable.rs
Normal file
6
src/components/generic/activatable.rs
Normal file
@ -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);
|
||||
}
|
11
src/components/generic/helpable.rs
Normal file
11
src/components/generic/helpable.rs
Normal file
@ -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<Option<Action>> {
|
||||
self.set_inactive();
|
||||
Ok(Some(Action::Help))
|
||||
}
|
||||
}
|
7
src/components/generic/mod.rs
Normal file
7
src/components/generic/mod.rs
Normal file
@ -0,0 +1,7 @@
|
||||
mod activatable;
|
||||
mod helpable;
|
||||
mod scrollable;
|
||||
|
||||
pub use activatable::Activatable;
|
||||
pub use helpable::Helpable;
|
||||
pub use scrollable::Scrollable;
|
58
src/components/generic/scrollable.rs
Normal file
58
src/components/generic/scrollable.rs
Normal file
@ -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<Output = Self::IndexType>
|
||||
+ Sub<Output = Self::IndexType>
|
||||
+ PartialOrd
|
||||
+ PartialEq
|
||||
+ From<u8>
|
||||
+ Copy;
|
||||
|
||||
fn apply_next_row(&mut self, new_index: Self::IndexType) -> Result<Option<Action>>;
|
||||
fn apply_prev_row(&mut self, new_index: Self::IndexType) -> Result<Option<Action>>;
|
||||
|
||||
fn selected_index(&self) -> Option<Self::IndexType>;
|
||||
fn items_length(&self) -> Self::IndexType;
|
||||
|
||||
fn next_row(&mut self) -> Result<Option<Action>> {
|
||||
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<Option<Action>> {
|
||||
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)
|
||||
}
|
||||
}
|
@ -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<Option<Action>> {
|
||||
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<Option<Action>> {
|
||||
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<Option<Action>> {
|
||||
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::IndexType> {
|
||||
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<Option<Action>> {
|
||||
self.move_index(new_index)
|
||||
}
|
||||
|
||||
fn apply_prev_row(&mut self, new_index: Self::IndexType) -> Result<Option<Action>> {
|
||||
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<Option<Action>> {
|
||||
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<Option<Action>> {
|
||||
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(),
|
||||
|
@ -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<UnboundedSender<Action>>,
|
||||
list_state: ListState,
|
||||
items: Vec<String>,
|
||||
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::IndexType> {
|
||||
self.list_state.selected()
|
||||
}
|
||||
|
||||
fn items_length(&self) -> Self::IndexType {
|
||||
self.items.len()
|
||||
}
|
||||
|
||||
fn apply_next_row(&mut self, new_index: Self::IndexType) -> Result<Option<Action>> {
|
||||
self.move_index(new_index)
|
||||
}
|
||||
|
||||
fn apply_prev_row(&mut self, new_index: Self::IndexType) -> Result<Option<Action>> {
|
||||
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<Option<Action>> {
|
||||
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<Option<Action>> {
|
||||
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<Option<Action>> {
|
||||
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<Option<Action>> {
|
||||
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<Action>) -> 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<Option<Action>> {
|
||||
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);
|
||||
|
@ -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<Action>) -> Result<()> {
|
||||
|
Loading…
Reference in New Issue
Block a user