From e84aa971548429c6c4cd7fac07b27c56f3514175 Mon Sep 17 00:00:00 2001 From: Wojciech Kozlowski Date: Sun, 28 Jan 2024 11:49:41 +0100 Subject: [PATCH] Add type-based statefulness for ui --- src/tui/handler.rs | 96 ++++++++++++++++++++++++++++++---------------- src/tui/mod.rs | 24 +++++++----- src/tui/ui.rs | 76 ++++++++++++++++++++++++++++-------- 3 files changed, 136 insertions(+), 60 deletions(-) diff --git a/src/tui/handler.rs b/src/tui/handler.rs index 2c65098..235aaf3 100644 --- a/src/tui/handler.rs +++ b/src/tui/handler.rs @@ -5,17 +5,21 @@ use mockall::automock; use crate::tui::{ event::{Event, EventError, EventReceiver}, - ui::IUi, + ui::{IUiBrowse, IUiCore, IUiInfo, Ui}, }; #[cfg_attr(test, automock)] -pub trait IEventHandler { - fn handle_next_event(&self, ui: &mut UI) -> Result<(), EventError>; +pub trait IEventHandler, IS: IUiInfo> { + fn handle_next_event(&self, ui: Ui) -> Result, EventError>; } -trait IEventHandlerPrivate { - fn handle_key_event(ui: &mut UI, key_event: KeyEvent) -> Result<(), EventError>; - fn quit(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>; } pub struct EventHandler { @@ -29,48 +33,74 @@ impl EventHandler { } } -impl IEventHandler for EventHandler { - fn handle_next_event(&self, ui: &mut UI) -> Result<(), EventError> { +impl, IS: IUiInfo> IEventHandler for EventHandler { + fn handle_next_event(&self, mut ui: Ui) -> Result, EventError> { match self.events.recv()? { - Event::Key(key_event) => Self::handle_key_event(ui, key_event)?, + Event::Key(key_event) => { + ui = Self::handle_key_event(ui, key_event)?; + } Event::Mouse(_) => {} Event::Resize(_, _) => {} }; - Ok(()) + Ok(ui) } } -impl IEventHandlerPrivate for EventHandler { - fn handle_key_event(ui: &mut UI, key_event: KeyEvent) -> Result<(), EventError> { +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), + } + } + + fn handle_browse_key_event(mut ui: BS, key_event: KeyEvent) -> Result, EventError> { + match key_event.code { + // Category change. + KeyCode::Left => ui.decrement_category(), + KeyCode::Right => ui.increment_category(), + // Selection change. + 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') => { - Self::quit(ui)?; + >::quit(ui)?; } // Exit application on `Ctrl-C`. KeyCode::Char('c') | KeyCode::Char('C') => { if key_event.modifiers == KeyModifiers::CONTROL { - Self::quit(ui)?; + >::quit(ui)?; } } - // Category change. - KeyCode::Left => { - ui.decrement_category(); - } - KeyCode::Right => { - ui.increment_category(); - } - // Selection change. - KeyCode::Up => { - ui.decrement_selection(); - } - KeyCode::Down => { - ui.increment_selection(); - } - // Toggle overlay. - KeyCode::Char('m') | KeyCode::Char('M') => { - ui.toggle_overlay(); - } // Other keys. _ => {} } @@ -78,7 +108,7 @@ impl IEventHandlerPrivate for EventHandler { Ok(()) } - fn quit(ui: &mut UI) -> Result<(), EventError> { + fn quit(ui: &mut C) -> Result<(), EventError> { ui.quit(); ui.save()?; Ok(()) diff --git a/src/tui/mod.rs b/src/tui/mod.rs index c4584da..3376814 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::IUi; +use self::ui::{IUiBrowse, IUiInfo, Ui}; #[derive(Debug, PartialEq, Eq)] pub enum Error { @@ -43,12 +43,12 @@ impl From for Error { } } -pub struct Tui { +pub struct Tui, IS: IUiInfo> { terminal: Terminal, - _phantom: PhantomData, + _phantom: PhantomData>, } -impl Tui { +impl, IS: IUiInfo> Tui { fn init(&mut self) -> Result<(), Error> { self.terminal.hide_cursor()?; self.terminal.clear()?; @@ -65,10 +65,14 @@ impl 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))?; - handler.handle_next_event(&mut ui)?; + ui = handler.handle_next_event(ui)?; } Ok(()) @@ -76,8 +80,8 @@ impl Tui { fn main( term: Terminal, - ui: UI, - handler: impl IEventHandler, + ui: Ui, + handler: impl IEventHandler, listener: impl IEventListener, ) -> Result<(), Error> { let mut tui = Tui { @@ -135,8 +139,8 @@ impl 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 826a184..863d243 100644 --- a/src/tui/ui.rs +++ b/src/tui/ui.rs @@ -35,20 +35,54 @@ impl From for UiError { } } -pub trait IUi { +pub enum Ui, IS: IUiInfo> { + Browse(BS), + Info(IS), +} + +impl Ui, UiImpl> { + pub fn new(mh: MH) -> Result { + Ok(Ui::Browse(UiImpl::new(mh)?)) + } +} + +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(), + } + } + + 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), + } + } +} + +pub trait IUiCore { fn is_running(&self) -> bool; fn quit(&mut self); - fn save(&mut self) -> Result<(), UiError>; +} +pub trait IUiBrowse>: IUiCore + Sized { fn increment_category(&mut self); fn decrement_category(&mut self); - fn increment_selection(&mut self); fn decrement_selection(&mut self); - fn toggle_overlay(&mut self); - fn render(&mut self, frame: &mut Frame<'_, B>); + fn display_info_overlay(self) -> Ui; + + fn render_browse_frame(&mut self, frame: &mut Frame<'_, B>); +} + +pub trait IUiInfo>: IUiCore + Sized { + fn hide_info_overlay(self) -> Ui; + + fn render_info_frame(&mut self, frame: &mut Frame<'_, B>); } struct TrackSelection { @@ -278,10 +312,9 @@ impl Selection { } } -pub struct Ui { +pub struct UiImpl { music_hoard: MH, selection: Selection, - overlay: bool, running: bool, } @@ -536,15 +569,14 @@ impl<'a, 'b> TrackState<'a, 'b> { } } -impl Ui { +impl UiImpl { 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(Ui { + Ok(UiImpl { music_hoard, selection, - overlay: false, running: true, }) } @@ -692,7 +724,7 @@ impl Ui { } } -impl IUi for Ui { +impl IUiCore for UiImpl { fn is_running(&self) -> bool { self.running } @@ -705,7 +737,9 @@ impl IUi for Ui { self.music_hoard.save_to_database()?; Ok(()) } +} +impl IUiBrowse for UiImpl { fn increment_category(&mut self) { self.selection.increment_category(); } @@ -724,15 +758,23 @@ impl IUi for Ui { .decrement_selection(self.music_hoard.get_collection()); } - fn toggle_overlay(&mut self) { - self.overlay = !self.overlay; + fn display_info_overlay(self) -> Ui { + Ui::Info(self) } - fn render(&mut self, frame: &mut Frame<'_, B>) { + fn render_browse_frame(&mut self, frame: &mut Frame<'_, B>) { self.render_collection(frame); - if self.overlay { - self.render_overlay(frame); - } + } +} + +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); } }