diff --git a/src/main.rs b/src/main.rs index 88a8d8c..cc241d5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,7 @@ use musichoard::{ mod tui; use tui::{ - app::App, event::EventChannel, handler::TuiEventHandler, listener::TuiEventListener, ui::Ui, + app::TuiApp, event::EventChannel, handler::TuiEventHandler, listener::TuiEventListener, ui::Ui, Tui, }; @@ -59,7 +59,7 @@ fn main() { let ui = Ui::new(); - let app = App::new(collection_manager).expect("failed to initialise app"); + let app = TuiApp::new(collection_manager).expect("failed to initialise app"); // Run the TUI application. Tui::run(terminal, app, ui, handler, listener).expect("failed to run tui"); diff --git a/src/tui/app.rs b/src/tui/app.rs index f3163c5..2609c9e 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -110,31 +110,74 @@ struct Selection { artist: Option, } -pub struct App { +pub trait App { + fn is_running(&self) -> bool; + fn quit(&mut self); + + fn increment_category(&mut self); + fn decrement_category(&mut self); + + fn increment_selection(&mut self); + fn decrement_selection(&mut self); + + fn get_active_category(&self) -> Category; + + fn get_artist_ids(&self) -> Vec<&ArtistId>; + fn get_album_ids(&self) -> Vec<&AlbumId>; + fn get_track_ids(&self) -> Vec<&Track>; + + fn selected_artist(&self) -> Option; + fn selected_album(&self) -> Option; + fn selected_track(&self) -> Option; +} + +trait AppPrivate { + fn increment_artist_selection(&mut self); + fn decrement_artist_selection(&mut self); + + fn increment_album_selection(&mut self); + fn decrement_album_selection(&mut self); + + fn increment_track_selection(&mut self); + + fn decrement_track_selection(&mut self); + + fn get_artists(&self) -> &Vec; + fn get_albums(&self) -> Option<&Vec>; + fn get_tracks(&self) -> Option<&Vec>; +} + +pub struct TuiApp { collection_manager: CM, selection: Selection, running: bool, } -impl App { +impl TuiApp { pub fn new(mut collection_manager: CM) -> Result { collection_manager.rescan_library()?; let selection = Selection { active: Category::Artist, artist: ArtistSelection::initialise(collection_manager.get_collection()), }; - Ok(App { + Ok(TuiApp { collection_manager, selection, running: true, }) } +} - pub fn is_running(&self) -> bool { +impl App for TuiApp { + fn is_running(&self) -> bool { self.running } - pub fn increment_category(&mut self) { + fn quit(&mut self) { + self.running = false; + } + + fn increment_category(&mut self) { self.selection.active = match self.selection.active { Category::Artist => Category::Album, Category::Album => Category::Track, @@ -142,7 +185,7 @@ impl App { }; } - pub fn decrement_category(&mut self) { + fn decrement_category(&mut self) { self.selection.active = match self.selection.active { Category::Artist => Category::Artist, Category::Album => Category::Artist, @@ -150,7 +193,7 @@ impl App { }; } - pub fn increment_selection(&mut self) { + fn increment_selection(&mut self) { match self.selection.active { Category::Artist => self.increment_artist_selection(), Category::Album => self.increment_album_selection(), @@ -158,7 +201,7 @@ impl App { } } - pub fn decrement_selection(&mut self) { + fn decrement_selection(&mut self) { match self.selection.active { Category::Artist => self.decrement_artist_selection(), Category::Album => self.decrement_album_selection(), @@ -166,6 +209,57 @@ impl App { } } + fn get_active_category(&self) -> Category { + self.selection.active + } + + fn get_artist_ids(&self) -> Vec<&ArtistId> { + let artists = self.get_artists(); + artists.iter().map(|a| &a.id).collect() + } + + fn get_album_ids(&self) -> Vec<&AlbumId> { + if let Some(albums) = self.get_albums() { + albums.iter().map(|a| &a.id).collect() + } else { + vec![] + } + } + + fn get_track_ids(&self) -> Vec<&Track> { + if let Some(tracks) = self.get_tracks() { + tracks.iter().collect() + } else { + vec![] + } + } + + fn selected_artist(&self) -> Option { + self.selection.artist.as_ref().map(|s| s.index) + } + + fn selected_album(&self) -> Option { + if let Some(ref artist_selection) = self.selection.artist { + artist_selection.album.as_ref().map(|s| s.index) + } else { + None + } + } + + fn selected_track(&self) -> Option { + if let Some(ref artist_selection) = self.selection.artist { + if let Some(ref album_selection) = artist_selection.album { + album_selection.track.as_ref().map(|s| s.index) + } else { + None + } + } else { + None + } + } +} + +impl AppPrivate for TuiApp { fn increment_artist_selection(&mut self) { if let Some(ref mut artist_selection) = self.selection.artist { let artists = &self.collection_manager.get_collection(); @@ -226,10 +320,6 @@ impl App { } } - pub fn get_active_category(&self) -> Category { - self.selection.active - } - fn get_artists(&self) -> &Vec { self.collection_manager.get_collection() } @@ -254,55 +344,6 @@ impl App { None } } - - pub fn get_artist_ids(&self) -> Vec<&ArtistId> { - let artists = self.get_artists(); - artists.iter().map(|a| &a.id).collect() - } - - pub fn get_album_ids(&self) -> Vec<&AlbumId> { - if let Some(albums) = self.get_albums() { - albums.iter().map(|a| &a.id).collect() - } else { - vec![] - } - } - - pub fn get_track_ids(&self) -> Vec<&Track> { - if let Some(tracks) = self.get_tracks() { - tracks.iter().collect() - } else { - vec![] - } - } - - pub fn selected_artist(&self) -> Option { - self.selection.artist.as_ref().map(|s| s.index) - } - - pub fn selected_album(&self) -> Option { - if let Some(ref artist_selection) = self.selection.artist { - artist_selection.album.as_ref().map(|s| s.index) - } else { - None - } - } - - pub fn selected_track(&self) -> Option { - if let Some(ref artist_selection) = self.selection.artist { - if let Some(ref album_selection) = artist_selection.album { - album_selection.track.as_ref().map(|s| s.index) - } else { - None - } - } else { - None - } - } - - pub fn quit(&mut self) { - self.running = false; - } } #[cfg(test)] @@ -516,7 +557,7 @@ mod tests { .expect_get_collection() .return_const(COLLECTION.to_owned()); - let mut app = App::new(collection_manager).unwrap(); + let mut app = TuiApp::new(collection_manager).unwrap(); assert!(app.is_running()); app.quit(); @@ -535,7 +576,7 @@ mod tests { .expect_get_collection() .return_const(COLLECTION.to_owned()); - let mut app = App::new(collection_manager).unwrap(); + let mut app = TuiApp::new(collection_manager).unwrap(); assert!(app.is_running()); assert!(!app.get_artist_ids().is_empty()); @@ -640,7 +681,7 @@ mod tests { .expect_get_collection() .return_const(collection); - let mut app = App::new(collection_manager).unwrap(); + let mut app = TuiApp::new(collection_manager).unwrap(); assert!(app.is_running()); assert!(!app.get_artist_ids().is_empty()); @@ -682,7 +723,7 @@ mod tests { .expect_get_collection() .return_const(collection); - let mut app = App::new(collection_manager).unwrap(); + let mut app = TuiApp::new(collection_manager).unwrap(); assert!(app.is_running()); assert!(!app.get_artist_ids().is_empty()); @@ -736,7 +777,7 @@ mod tests { .expect_get_collection() .return_const(collection); - let mut app = App::new(collection_manager).unwrap(); + let mut app = TuiApp::new(collection_manager).unwrap(); assert!(app.is_running()); assert!(app.get_artist_ids().is_empty()); diff --git a/src/tui/handler.rs b/src/tui/handler.rs index 01db45c..fecc501 100644 --- a/src/tui/handler.rs +++ b/src/tui/handler.rs @@ -2,7 +2,6 @@ use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; #[cfg(test)] use mockall::automock; -use musichoard::collection::CollectionManager; use super::{ app::App, @@ -10,12 +9,12 @@ use super::{ }; #[cfg_attr(test, automock)] -pub trait EventHandler { - fn handle_next_event(&self, app: &mut App) -> Result<(), EventError>; +pub trait EventHandler { + fn handle_next_event(&self, app: &mut APP) -> Result<(), EventError>; } -trait EventHandlerPrivate { - fn handle_key_event(app: &mut App, key_event: KeyEvent); +trait EventHandlerPrivate { + fn handle_key_event(app: &mut APP, key_event: KeyEvent); } pub struct TuiEventHandler { @@ -29,8 +28,8 @@ impl TuiEventHandler { } } -impl EventHandler for TuiEventHandler { - fn handle_next_event(&self, app: &mut App) -> Result<(), EventError> { +impl EventHandler for TuiEventHandler { + fn handle_next_event(&self, app: &mut APP) -> Result<(), EventError> { match self.events.recv()? { Event::Key(key_event) => Self::handle_key_event(app, key_event), Event::Mouse(_) => {} @@ -40,8 +39,8 @@ impl EventHandler for TuiEventHandler { } } -impl EventHandlerPrivate for TuiEventHandler { - fn handle_key_event(app: &mut App, key_event: KeyEvent) { +impl EventHandlerPrivate for TuiEventHandler { + fn handle_key_event(app: &mut APP, key_event: KeyEvent) { match key_event.code { // Exit application on `ESC` or `q`. KeyCode::Esc | KeyCode::Char('q') => { diff --git a/src/tui/mod.rs b/src/tui/mod.rs index ce2b5d7..da87d7b 100644 --- a/src/tui/mod.rs +++ b/src/tui/mod.rs @@ -1,6 +1,6 @@ use crossterm::event::{DisableMouseCapture, EnableMouseCapture}; use crossterm::terminal::{self, EnterAlternateScreen, LeaveAlternateScreen}; -use musichoard::collection::{self, CollectionManager}; +use musichoard::collection; use ratatui::backend::Backend; use ratatui::Terminal; use std::io; @@ -44,12 +44,12 @@ impl From for Error { } } -pub struct Tui { +pub struct Tui { terminal: Terminal, - _phantom: PhantomData, + _phantom: PhantomData, } -impl Tui { +impl Tui { fn init(&mut self) -> Result<(), Error> { self.terminal.hide_cursor()?; self.terminal.clear()?; @@ -68,9 +68,9 @@ impl Tui { fn main_loop( &mut self, - mut app: App, - ui: Ui, - handler: impl EventHandler, + mut app: APP, + ui: Ui, + handler: impl EventHandler, ) -> Result<(), Error> { while app.is_running() { self.terminal.draw(|frame| ui.render(&app, frame))?; @@ -82,9 +82,9 @@ impl Tui { fn main( term: Terminal, - app: App, - ui: Ui, - handler: impl EventHandler, + app: APP, + ui: Ui, + handler: impl EventHandler, listener: impl EventListener, ) -> Result<(), Error> { let mut tui = Tui { @@ -142,9 +142,9 @@ impl Tui { pub fn run( term: Terminal, - app: App, - ui: Ui, - handler: impl EventHandler, + app: APP, + ui: Ui, + handler: impl EventHandler, listener: impl EventListener, ) -> Result<(), Error> { Self::enable()?; @@ -175,8 +175,12 @@ mod tests { use crate::tests::{MockCollectionManager, COLLECTION}; use super::{ - app::App, event::EventError, handler::MockEventHandler, listener::MockEventListener, - ui::Ui, Error, Tui, + app::{App, TuiApp}, + event::EventError, + handler::MockEventHandler, + listener::MockEventListener, + ui::Ui, + Error, Tui, }; pub fn terminal() -> Terminal { @@ -184,7 +188,7 @@ mod tests { Terminal::new(backend).unwrap() } - pub fn app(collection: Collection) -> App { + pub fn app(collection: Collection) -> TuiApp { let mut collection_manager = MockCollectionManager::new(); collection_manager @@ -194,7 +198,7 @@ mod tests { .expect_get_collection() .return_const(collection); - App::new(collection_manager).unwrap() + TuiApp::new(collection_manager).unwrap() } fn listener() -> MockEventListener { @@ -208,12 +212,14 @@ mod tests { listener } - fn handler() -> MockEventHandler { + fn handler() -> MockEventHandler> { let mut handler = MockEventHandler::new(); - handler.expect_handle_next_event().return_once(|app| { - app.quit(); - Ok(()) - }); + handler.expect_handle_next_event().return_once( + |app: &mut TuiApp| { + app.quit(); + Ok(()) + }, + ); handler } diff --git a/src/tui/ui.rs b/src/tui/ui.rs index 1e36c26..47288e3 100644 --- a/src/tui/ui.rs +++ b/src/tui/ui.rs @@ -1,6 +1,6 @@ use std::marker::PhantomData; -use musichoard::{collection::CollectionManager, TrackFormat}; +use musichoard::TrackFormat; use ratatui::{ backend::Backend, layout::{Alignment, Rect}, @@ -59,11 +59,11 @@ struct AppState<'a> { tracks: TrackState<'a>, } -pub struct Ui { - _phantom: PhantomData, +pub struct Ui { + _phantom: PhantomData, } -impl Ui { +impl Ui { pub fn new() -> Self { Ui { _phantom: PhantomData, @@ -127,7 +127,7 @@ impl Ui { } } - fn construct_artist_list(app: &App) -> ArtistState { + fn construct_artist_list(app: &APP) -> ArtistState { let artists = app.get_artist_ids(); let list = List::new( artists @@ -149,7 +149,7 @@ impl Ui { } } - fn construct_album_list(app: &App) -> AlbumState { + fn construct_album_list(app: &APP) -> AlbumState { let albums = app.get_album_ids(); let list = List::new( albums @@ -182,7 +182,7 @@ impl Ui { } } - fn construct_track_list(app: &App) -> TrackState { + fn construct_track_list(app: &APP) -> TrackState { let tracks = app.get_track_ids(); let list = List::new( tracks @@ -226,7 +226,7 @@ impl Ui { } } - fn construct_app_state(app: &App) -> AppState { + fn construct_app_state(app: &APP) -> AppState { AppState { artists: Self::construct_artist_list(app), albums: Self::construct_album_list(app), @@ -318,7 +318,7 @@ impl Ui { Self::render_info_widget("Track info", state.info, state.active, area.info, frame); } - pub fn render(&self, app: &App, frame: &mut Frame<'_, B>) { + pub fn render(&self, app: &APP, frame: &mut Frame<'_, B>) { let areas = Self::construct_areas(frame.size()); let app_state = Self::construct_app_state(app); @@ -334,7 +334,10 @@ mod tests { use crate::{ tests::COLLECTION, - tui::tests::{app, terminal}, + tui::{ + app::App, + tests::{app, terminal}, + }, }; use super::Ui;