use color_eyre::Result; use ratatui::{prelude::*, widgets::*}; 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 { list_state: ListState, items: Vec, is_active: bool, palette: StylePalette, } impl Default for Menu { 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 { list_state: ListState::default(), items: vec![ String::from("Explorer"), String::from("Wallet"), String::from("Validator"), String::from("Prices"), String::from("Governance"), String::from("Operations"), ], palette: StylePalette::default(), is_active: true, }; new_list.list_state.select(Some(0)); new_list } 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))), _ => Ok(Some(Action::SetMode(Mode::Empty))), } } } impl Component for Menu { fn update(&mut self, action: Action) -> Result> { match action { Action::SetActiveScreen(Mode::Menu) => self.set_active(), _ => {} } Ok(None) } 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()); self.palette.with_hover_style(style.get("hover_style").copied()); self.palette.with_normal_border_style(style.get("normal_border_style").copied()); self.palette.with_hover_border_style(style.get("hover_border_style").copied()); self.palette.with_highlight_style(style.get("highlight_style").copied()); } Ok(()) } fn handle_key_event(&mut self, key: KeyEvent) -> Result> { match key.code { 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))), Some(2) => Ok(Some(Action::SetActiveScreen(Mode::Validator))), _ => Ok(Some(Action::SetActiveScreen(Mode::Empty))), } }, KeyCode::Char('?') if self.is_active() => self.open_help_popup(), _ => Ok(None), } } 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 block = Block::bordered() .border_style(color) .border_type(border_type); let list = List::default() .items(self.items .iter() .map(|item| ListItem::new( Line::raw(item.as_str()).alignment(Alignment::Center) )) .collect::>() ) .block(block) .style(self.palette.create_basic_style(false)) .highlight_style(self.palette.create_highlight_style()); frame.render_stateful_widget(list, menu, &mut self.list_state); Ok(()) } }