diff --git a/src/tui/handler.rs b/src/tui/handler.rs index 41429d7..c7bb360 100644 --- a/src/tui/handler.rs +++ b/src/tui/handler.rs @@ -5,21 +5,25 @@ use mockall::automock; use crate::tui::{ event::{Event, EventError, EventReceiver}, - ui::{IUiBrowse, IUiCore, IUiInfo, Ui}, + ui::{IUi, IUiBrowse, IUiInfo, UiState}, }; #[cfg_attr(test, automock)] -pub trait IEventHandler, IS: IUiInfo> { - fn handle_next_event(&self, ui: Ui) -> Result, EventError>; +pub trait IEventHandler { + fn handle_next_event(&self, ui: &mut UI) -> Result<(), EventError>; } -trait IEventHandlerPrivate, IS: IUiInfo> { - fn handle_key_event(ui: Ui, key_event: KeyEvent) -> Result, EventError>; - fn handle_browse_key_event(ui: BS, key_event: KeyEvent) -> Result, EventError>; - fn handle_info_key_event(ui: IS, key_event: KeyEvent) -> Result, EventError>; - fn handle_core_key_event(ui: &mut C, key_event: KeyEvent) - -> Result<(), EventError>; - fn quit(ui: &mut C) -> Result<(), EventError>; +trait IEventHandlerPrivate { + fn handle_key_event(ui: &mut UI, key_event: KeyEvent) -> Result<(), EventError>; + fn handle_browse_key_event( + ui: &mut ::BS, + key_event: KeyEvent, + ) -> Result<(), EventError>; + fn handle_info_key_event( + ui: &mut ::IS, + key_event: KeyEvent, + ) -> Result<(), EventError>; + fn quit(ui: &mut UI) -> Result<(), EventError>; } pub struct EventHandler { @@ -33,26 +37,46 @@ impl EventHandler { } } -impl, IS: IUiInfo> IEventHandler for EventHandler { - fn handle_next_event(&self, mut ui: Ui) -> Result, EventError> { +impl IEventHandler for EventHandler { + fn handle_next_event(&self, ui: &mut UI) -> Result<(), EventError> { match self.events.recv()? { - Event::Key(key_event) => ui = Self::handle_key_event(ui, key_event)?, + Event::Key(key_event) => Self::handle_key_event(ui, key_event)?, Event::Mouse(_) => {} Event::Resize(_, _) => {} }; - Ok(ui) + Ok(()) } } -impl, IS: IUiInfo> IEventHandlerPrivate for EventHandler { - fn handle_key_event(ui: Ui, key_event: KeyEvent) -> Result, EventError> { - match ui { - Ui::Browse(browse) => Self::handle_browse_key_event(browse, key_event), - Ui::Info(info) => Self::handle_info_key_event(info, key_event), - } +impl IEventHandlerPrivate for EventHandler { + fn handle_key_event(ui: &mut UI, key_event: KeyEvent) -> Result<(), EventError> { + match key_event.code { + // Exit application on `ESC` or `q`. + KeyCode::Esc | KeyCode::Char('q') => { + Self::quit(ui)?; + } + // Exit application on `Ctrl-C`. + KeyCode::Char('c') | KeyCode::Char('C') => { + if key_event.modifiers == KeyModifiers::CONTROL { + Self::quit(ui)?; + } + } + _ => match ui.state() { + UiState::Browse(browse) => { + >::handle_browse_key_event(browse, key_event)?; + } + UiState::Info(info) => { + >::handle_info_key_event(info, key_event)?; + } + }, + }; + Ok(()) } - fn handle_browse_key_event(mut ui: BS, key_event: KeyEvent) -> Result, EventError> { + fn handle_browse_key_event( + ui: &mut ::BS, + key_event: KeyEvent, + ) -> Result<(), EventError> { match key_event.code { // Category change. KeyCode::Left => ui.decrement_category(), @@ -61,52 +85,29 @@ impl, IS: IUiInfo> IEventHandlerPrivate for EventH KeyCode::Up => ui.decrement_selection(), KeyCode::Down => ui.increment_selection(), // Toggle overlay. - KeyCode::Char('m') | KeyCode::Char('M') => { - return Ok(ui.display_info_overlay()); - } - // Other keys. - _ => >::handle_core_key_event(&mut ui, key_event)?, - } - - Ok(Ui::Browse(ui)) - } - - fn handle_info_key_event(mut ui: IS, key_event: KeyEvent) -> Result, EventError> { - match key_event.code { - // Toggle overlay. - KeyCode::Char('m') | KeyCode::Char('M') => { - return Ok(ui.hide_info_overlay()); - } - // Other keys. - _ => >::handle_core_key_event(&mut ui, key_event)?, - } - - Ok(Ui::Info(ui)) - } - - fn handle_core_key_event( - ui: &mut C, - key_event: KeyEvent, - ) -> Result<(), EventError> { - match key_event.code { - // Exit application on `ESC` or `q`. - KeyCode::Esc | KeyCode::Char('q') => { - >::quit(ui)?; - } - // Exit application on `Ctrl-C`. - KeyCode::Char('c') | KeyCode::Char('C') => { - if key_event.modifiers == KeyModifiers::CONTROL { - >::quit(ui)?; - } - } - // Other keys. + KeyCode::Char('m') | KeyCode::Char('M') => ui.show_info_overlay(), + // Othey keys. _ => {} } Ok(()) } - fn quit(ui: &mut C) -> Result<(), EventError> { + fn handle_info_key_event( + ui: &mut ::IS, + key_event: KeyEvent, + ) -> Result<(), EventError> { + match key_event.code { + // Toggle overlay. + KeyCode::Char('m') | KeyCode::Char('M') => ui.hide_info_overlay(), + // Othey keys. + _ => {} + } + + Ok(()) + } + + fn quit(ui: &mut UI) -> Result<(), EventError> { ui.quit(); ui.save()?; Ok(()) diff --git a/src/tui/mod.rs b/src/tui/mod.rs index 3376814..11b1da8 100644 --- a/src/tui/mod.rs +++ b/src/tui/mod.rs @@ -15,7 +15,7 @@ use std::marker::PhantomData; use self::event::EventError; use self::handler::IEventHandler; use self::listener::IEventListener; -use self::ui::{IUiBrowse, IUiInfo, Ui}; +use self::ui::IUi; #[derive(Debug, PartialEq, Eq)] pub enum Error { @@ -43,12 +43,12 @@ impl From for Error { } } -pub struct Tui, IS: IUiInfo> { +pub struct Tui { terminal: Terminal, - _phantom: PhantomData>, + _phantom: PhantomData, } -impl, IS: IUiInfo> Tui { +impl Tui { fn init(&mut self) -> Result<(), Error> { self.terminal.hide_cursor()?; self.terminal.clear()?; @@ -65,14 +65,10 @@ impl, IS: IUiInfo> Tui { self.exit(); } - fn main_loop( - &mut self, - mut ui: Ui, - handler: impl IEventHandler, - ) -> Result<(), Error> { + fn main_loop(&mut self, mut ui: UI, handler: impl IEventHandler) -> Result<(), Error> { while ui.is_running() { self.terminal.draw(|frame| ui.render(frame))?; - ui = handler.handle_next_event(ui)?; + handler.handle_next_event(&mut ui)?; } Ok(()) @@ -80,8 +76,8 @@ impl, IS: IUiInfo> Tui { fn main( term: Terminal, - ui: Ui, - handler: impl IEventHandler, + ui: UI, + handler: impl IEventHandler, listener: impl IEventListener, ) -> Result<(), Error> { let mut tui = Tui { @@ -139,8 +135,8 @@ impl, IS: IUiInfo> Tui { pub fn run( term: Terminal, - ui: Ui, - handler: impl IEventHandler, + ui: UI, + handler: impl IEventHandler, listener: impl IEventListener, ) -> Result<(), Error> { Self::enable()?; diff --git a/src/tui/ui.rs b/src/tui/ui.rs index 2cb8cf9..85655be 100644 --- a/src/tui/ui.rs +++ b/src/tui/ui.rs @@ -35,48 +35,46 @@ impl From for UiError { } } -pub enum Ui, IS: IUiInfo> { +pub enum UiState { Browse(BS), Info(IS), } -impl, IS: IUiInfo> Ui { - pub fn is_running(&self) -> bool { - match self { - Ui::Browse(ref browse) => browse.is_running(), - Ui::Info(ref info) => info.is_running(), - } +impl UiState { + fn is_browse(&self) -> bool { + matches!(self, UiState::Browse(_)) } - pub fn render(&mut self, frame: &mut Frame<'_, B>) { - match self { - Ui::Browse(ref mut browse) => browse.render_browse_frame(frame), - Ui::Info(ref mut info) => info.render_info_frame(frame), - } + fn is_info(&self) -> bool { + matches!(self, UiState::Info(_)) } } -pub trait IUiCore { +pub trait IUi { + type BS: IUiBrowse; + type IS: IUiInfo; + fn is_running(&self) -> bool; fn quit(&mut self); + fn save(&mut self) -> Result<(), UiError>; + + fn render(&mut self, frame: &mut Frame<'_, B>); + + fn state(&mut self) -> UiState<&mut Self::BS, &mut Self::IS>; } -pub trait IUiBrowse>: IUiCore + Sized { +pub trait IUiBrowse { fn increment_category(&mut self); fn decrement_category(&mut self); fn increment_selection(&mut self); fn decrement_selection(&mut self); - fn display_info_overlay(self) -> Ui; - - fn render_browse_frame(&mut self, frame: &mut Frame<'_, B>); + fn show_info_overlay(&mut self); } -pub trait IUiInfo>: IUiCore + Sized { - fn hide_info_overlay(self) -> Ui; - - fn render_info_frame(&mut self, frame: &mut Frame<'_, B>); +pub trait IUiInfo { + fn hide_info_overlay(&mut self); } struct TrackSelection { @@ -306,12 +304,6 @@ impl Selection { } } -pub struct UiImpl { - music_hoard: MH, - selection: Selection, - running: bool, -} - struct ArtistArea { list: Rect, } @@ -563,21 +555,23 @@ impl<'a, 'b> TrackState<'a, 'b> { } } -impl Ui, UiImpl> { - pub fn new(mh: MH) -> Result { - Ok(Ui::Browse(UiImpl::new(mh)?)) - } +pub struct Ui { + running: bool, + music_hoard: MH, + selection: Selection, + state: UiState<(), ()>, } -impl UiImpl { +impl Ui { pub fn new(mut music_hoard: MH) -> Result { music_hoard.load_from_database()?; music_hoard.rescan_library()?; let selection = Selection::new(Some(music_hoard.get_collection())); - Ok(UiImpl { + Ok(Ui { + running: true, music_hoard, selection, - running: true, + state: UiState::Browse(()), }) } @@ -724,7 +718,10 @@ impl UiImpl { } } -impl IUiCore for UiImpl { +impl IUi for Ui { + type BS = Self; + type IS = Self; + fn is_running(&self) -> bool { self.running } @@ -734,12 +731,25 @@ impl IUiCore for UiImpl { } fn save(&mut self) -> Result<(), UiError> { - self.music_hoard.save_to_database()?; - Ok(()) + Ok(self.music_hoard.save_to_database()?) + } + + fn state(&mut self) -> UiState<&mut Self::BS, &mut Self::IS> { + match self.state { + UiState::Browse(_) => UiState::Browse(self), + UiState::Info(_) => UiState::Info(self), + } + } + + fn render(&mut self, frame: &mut Frame<'_, B>) { + self.render_collection(frame); + if self.state.is_info() { + self.render_overlay(frame); + } } } -impl IUiBrowse for UiImpl { +impl IUiBrowse for Ui { fn increment_category(&mut self) { self.selection.increment_category(); } @@ -758,23 +768,16 @@ impl IUiBrowse for UiImpl { .decrement_selection(self.music_hoard.get_collection()); } - fn display_info_overlay(self) -> Ui { - Ui::Info(self) - } - - fn render_browse_frame(&mut self, frame: &mut Frame<'_, B>) { - self.render_collection(frame); + fn show_info_overlay(&mut self) { + assert!(self.state.is_browse()); + self.state = UiState::Info(()); } } -impl IUiInfo for UiImpl { - fn hide_info_overlay(self) -> Ui { - Ui::Browse(self) - } - - fn render_info_frame(&mut self, frame: &mut Frame<'_, B>) { - self.render_collection(frame); - self.render_overlay(frame); +impl IUiInfo for Ui { + fn hide_info_overlay(&mut self) { + assert!(self.state.is_info()); + self.state = UiState::Browse(()); } } @@ -1218,10 +1221,12 @@ mod tests { fn overlay() { let mut terminal = terminal(); let mut ui = ui(COLLECTION.to_owned()); + assert!(ui.state().is_browse()); terminal.draw(|frame| ui.render(frame)).unwrap(); - ui.toggle_overlay(); + ui.show_info_overlay(); + assert!(ui.state().is_info()); terminal.draw(|frame| ui.render(frame)).unwrap(); @@ -1230,7 +1235,8 @@ mod tests { terminal.draw(|frame| ui.render(frame)).unwrap(); - ui.toggle_overlay(); + ui.hide_info_overlay(); + assert!(ui.state().is_browse()); terminal.draw(|frame| ui.render(frame)).unwrap(); }