Replace as many Box<dyn Trait> with generics as possible #32
@ -61,7 +61,7 @@ fn main() {
|
|||||||
|
|
||||||
let ui = Ui::new();
|
let ui = Ui::new();
|
||||||
|
|
||||||
let app = App::new(Box::new(collection_manager)).expect("failed to initialise app");
|
let app = App::new(collection_manager).expect("failed to initialise app");
|
||||||
|
|
||||||
// Run the TUI application.
|
// Run the TUI application.
|
||||||
Tui::run(terminal, app, ui, handler, listener).expect("failed to run tui");
|
Tui::run(terminal, app, ui, handler, listener).expect("failed to run tui");
|
||||||
|
@ -110,14 +110,14 @@ struct Selection {
|
|||||||
artist: Option<ArtistSelection>,
|
artist: Option<ArtistSelection>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct App {
|
pub struct App<CM> {
|
||||||
collection_manager: Box<dyn CollectionManager>,
|
collection_manager: CM,
|
||||||
selection: Selection,
|
selection: Selection,
|
||||||
running: bool,
|
running: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl<CM: CollectionManager> App<CM> {
|
||||||
pub fn new(mut collection_manager: Box<dyn CollectionManager>) -> Result<Self, Error> {
|
pub fn new(mut collection_manager: CM) -> Result<Self, Error> {
|
||||||
collection_manager.rescan_library()?;
|
collection_manager.rescan_library()?;
|
||||||
let selection = Selection {
|
let selection = Selection {
|
||||||
active: Category::Artist,
|
active: Category::Artist,
|
||||||
@ -516,7 +516,7 @@ mod tests {
|
|||||||
.expect_get_collection()
|
.expect_get_collection()
|
||||||
.return_const(COLLECTION.to_owned());
|
.return_const(COLLECTION.to_owned());
|
||||||
|
|
||||||
let mut app = App::new(Box::new(collection_manager)).unwrap();
|
let mut app = App::new(collection_manager).unwrap();
|
||||||
assert!(app.is_running());
|
assert!(app.is_running());
|
||||||
|
|
||||||
app.quit();
|
app.quit();
|
||||||
@ -535,7 +535,7 @@ mod tests {
|
|||||||
.expect_get_collection()
|
.expect_get_collection()
|
||||||
.return_const(COLLECTION.to_owned());
|
.return_const(COLLECTION.to_owned());
|
||||||
|
|
||||||
let mut app = App::new(Box::new(collection_manager)).unwrap();
|
let mut app = App::new(collection_manager).unwrap();
|
||||||
assert!(app.is_running());
|
assert!(app.is_running());
|
||||||
|
|
||||||
assert!(!app.get_artist_ids().is_empty());
|
assert!(!app.get_artist_ids().is_empty());
|
||||||
@ -640,7 +640,7 @@ mod tests {
|
|||||||
.expect_get_collection()
|
.expect_get_collection()
|
||||||
.return_const(collection);
|
.return_const(collection);
|
||||||
|
|
||||||
let mut app = App::new(Box::new(collection_manager)).unwrap();
|
let mut app = App::new(collection_manager).unwrap();
|
||||||
assert!(app.is_running());
|
assert!(app.is_running());
|
||||||
|
|
||||||
assert!(!app.get_artist_ids().is_empty());
|
assert!(!app.get_artist_ids().is_empty());
|
||||||
@ -682,7 +682,7 @@ mod tests {
|
|||||||
.expect_get_collection()
|
.expect_get_collection()
|
||||||
.return_const(collection);
|
.return_const(collection);
|
||||||
|
|
||||||
let mut app = App::new(Box::new(collection_manager)).unwrap();
|
let mut app = App::new(collection_manager).unwrap();
|
||||||
assert!(app.is_running());
|
assert!(app.is_running());
|
||||||
|
|
||||||
assert!(!app.get_artist_ids().is_empty());
|
assert!(!app.get_artist_ids().is_empty());
|
||||||
@ -736,7 +736,7 @@ mod tests {
|
|||||||
.expect_get_collection()
|
.expect_get_collection()
|
||||||
.return_const(collection);
|
.return_const(collection);
|
||||||
|
|
||||||
let mut app = App::new(Box::new(collection_manager)).unwrap();
|
let mut app = App::new(collection_manager).unwrap();
|
||||||
assert!(app.is_running());
|
assert!(app.is_running());
|
||||||
|
|
||||||
assert!(app.get_artist_ids().is_empty());
|
assert!(app.get_artist_ids().is_empty());
|
||||||
|
@ -2,6 +2,7 @@ use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use mockall::automock;
|
use mockall::automock;
|
||||||
|
use musichoard::collection::CollectionManager;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
app::App,
|
app::App,
|
||||||
@ -9,12 +10,12 @@ use super::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[cfg_attr(test, automock)]
|
#[cfg_attr(test, automock)]
|
||||||
pub trait EventHandler {
|
pub trait EventHandler<CM> {
|
||||||
fn handle_next_event(&self, app: &mut App) -> Result<(), EventError>;
|
fn handle_next_event(&self, app: &mut App<CM>) -> Result<(), EventError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
trait EventHandlerPrivate {
|
trait EventHandlerPrivate<CM> {
|
||||||
fn handle_key_event(app: &mut App, key_event: KeyEvent);
|
fn handle_key_event(app: &mut App<CM>, key_event: KeyEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TuiEventHandler {
|
pub struct TuiEventHandler {
|
||||||
@ -28,8 +29,8 @@ impl TuiEventHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventHandler for TuiEventHandler {
|
impl<CM: CollectionManager> EventHandler<CM> for TuiEventHandler {
|
||||||
fn handle_next_event(&self, app: &mut App) -> Result<(), EventError> {
|
fn handle_next_event(&self, app: &mut App<CM>) -> Result<(), EventError> {
|
||||||
match self.events.recv()? {
|
match self.events.recv()? {
|
||||||
Event::Key(key_event) => Self::handle_key_event(app, key_event),
|
Event::Key(key_event) => Self::handle_key_event(app, key_event),
|
||||||
Event::Mouse(_) => {}
|
Event::Mouse(_) => {}
|
||||||
@ -39,8 +40,8 @@ impl EventHandler for TuiEventHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventHandlerPrivate for TuiEventHandler {
|
impl<CM: CollectionManager> EventHandlerPrivate<CM> for TuiEventHandler {
|
||||||
fn handle_key_event(app: &mut App, key_event: KeyEvent) {
|
fn handle_key_event(app: &mut App<CM>, key_event: KeyEvent) {
|
||||||
match key_event.code {
|
match key_event.code {
|
||||||
// Exit application on `ESC` or `q`.
|
// Exit application on `ESC` or `q`.
|
||||||
KeyCode::Esc | KeyCode::Char('q') => {
|
KeyCode::Esc | KeyCode::Char('q') => {
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
use crossterm::event::{DisableMouseCapture, EnableMouseCapture};
|
use crossterm::event::{DisableMouseCapture, EnableMouseCapture};
|
||||||
use crossterm::terminal::{self, EnterAlternateScreen, LeaveAlternateScreen};
|
use crossterm::terminal::{self, EnterAlternateScreen, LeaveAlternateScreen};
|
||||||
use musichoard::collection;
|
use musichoard::collection::{self, CollectionManager};
|
||||||
use ratatui::backend::Backend;
|
use ratatui::backend::Backend;
|
||||||
use ratatui::Terminal;
|
use ratatui::Terminal;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
pub mod app;
|
pub mod app;
|
||||||
pub mod event;
|
pub mod event;
|
||||||
@ -43,11 +44,12 @@ impl From<EventError> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Tui<B: Backend> {
|
pub struct Tui<B: Backend, CM> {
|
||||||
terminal: Terminal<B>,
|
terminal: Terminal<B>,
|
||||||
|
_phantom: PhantomData<CM>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B: Backend> Tui<B> {
|
impl<B: Backend, CM: CollectionManager> Tui<B, CM> {
|
||||||
fn init(&mut self) -> Result<(), Error> {
|
fn init(&mut self) -> Result<(), Error> {
|
||||||
self.terminal.hide_cursor()?;
|
self.terminal.hide_cursor()?;
|
||||||
self.terminal.clear()?;
|
self.terminal.clear()?;
|
||||||
@ -64,7 +66,12 @@ impl<B: Backend> Tui<B> {
|
|||||||
self.exit();
|
self.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main_loop(&mut self, mut app: App, ui: Ui, handler: impl EventHandler) -> Result<(), Error> {
|
fn main_loop(
|
||||||
|
&mut self,
|
||||||
|
mut app: App<CM>,
|
||||||
|
ui: Ui<CM>,
|
||||||
|
handler: impl EventHandler<CM>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
while app.is_running() {
|
while app.is_running() {
|
||||||
self.terminal.draw(|frame| ui.render(&app, frame))?;
|
self.terminal.draw(|frame| ui.render(&app, frame))?;
|
||||||
handler.handle_next_event(&mut app)?;
|
handler.handle_next_event(&mut app)?;
|
||||||
@ -75,12 +82,15 @@ impl<B: Backend> Tui<B> {
|
|||||||
|
|
||||||
fn main(
|
fn main(
|
||||||
term: Terminal<B>,
|
term: Terminal<B>,
|
||||||
app: App,
|
app: App<CM>,
|
||||||
ui: Ui,
|
ui: Ui<CM>,
|
||||||
handler: impl EventHandler,
|
handler: impl EventHandler<CM>,
|
||||||
listener: impl EventListener,
|
listener: impl EventListener,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mut tui = Tui { terminal: term };
|
let mut tui = Tui {
|
||||||
|
terminal: term,
|
||||||
|
_phantom: PhantomData,
|
||||||
|
};
|
||||||
|
|
||||||
tui.init()?;
|
tui.init()?;
|
||||||
|
|
||||||
@ -132,9 +142,9 @@ impl<B: Backend> Tui<B> {
|
|||||||
|
|
||||||
pub fn run(
|
pub fn run(
|
||||||
term: Terminal<B>,
|
term: Terminal<B>,
|
||||||
app: App,
|
app: App<CM>,
|
||||||
ui: Ui,
|
ui: Ui<CM>,
|
||||||
handler: impl EventHandler,
|
handler: impl EventHandler<CM>,
|
||||||
listener: impl EventListener,
|
listener: impl EventListener,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
Self::enable()?;
|
Self::enable()?;
|
||||||
@ -174,7 +184,7 @@ mod tests {
|
|||||||
Terminal::new(backend).unwrap()
|
Terminal::new(backend).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn app(collection: Collection) -> App {
|
pub fn app(collection: Collection) -> App<MockCollectionManager> {
|
||||||
let mut collection_manager = MockCollectionManager::new();
|
let mut collection_manager = MockCollectionManager::new();
|
||||||
|
|
||||||
collection_manager
|
collection_manager
|
||||||
@ -184,7 +194,7 @@ mod tests {
|
|||||||
.expect_get_collection()
|
.expect_get_collection()
|
||||||
.return_const(collection);
|
.return_const(collection);
|
||||||
|
|
||||||
App::new(Box::new(collection_manager)).unwrap()
|
App::new(collection_manager).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn listener() -> MockEventListener {
|
fn listener() -> MockEventListener {
|
||||||
@ -198,7 +208,7 @@ mod tests {
|
|||||||
listener
|
listener
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handler() -> MockEventHandler {
|
fn handler() -> MockEventHandler<MockCollectionManager> {
|
||||||
let mut handler = MockEventHandler::new();
|
let mut handler = MockEventHandler::new();
|
||||||
handler.expect_handle_next_event().return_once(|app| {
|
handler.expect_handle_next_event().return_once(|app| {
|
||||||
app.quit();
|
app.quit();
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
use musichoard::TrackFormat;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
use musichoard::{collection::CollectionManager, TrackFormat};
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
backend::Backend,
|
backend::Backend,
|
||||||
layout::{Alignment, Rect},
|
layout::{Alignment, Rect},
|
||||||
@ -57,11 +59,15 @@ struct AppState<'a> {
|
|||||||
tracks: TrackState<'a>,
|
tracks: TrackState<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Ui {}
|
pub struct Ui<CM> {
|
||||||
|
_phantom: PhantomData<CM>,
|
||||||
|
}
|
||||||
|
|
||||||
impl Ui {
|
impl<CM: CollectionManager> Ui<CM> {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Ui {}
|
Ui {
|
||||||
|
_phantom: PhantomData,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn construct_areas(frame: Rect) -> FrameAreas {
|
fn construct_areas(frame: Rect) -> FrameAreas {
|
||||||
@ -121,7 +127,7 @@ impl Ui {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn construct_artist_list(app: &App) -> ArtistState {
|
fn construct_artist_list(app: &App<CM>) -> ArtistState {
|
||||||
let artists = app.get_artist_ids();
|
let artists = app.get_artist_ids();
|
||||||
let list = List::new(
|
let list = List::new(
|
||||||
artists
|
artists
|
||||||
@ -143,7 +149,7 @@ impl Ui {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn construct_album_list(app: &App) -> AlbumState {
|
fn construct_album_list(app: &App<CM>) -> AlbumState {
|
||||||
let albums = app.get_album_ids();
|
let albums = app.get_album_ids();
|
||||||
let list = List::new(
|
let list = List::new(
|
||||||
albums
|
albums
|
||||||
@ -176,7 +182,7 @@ impl Ui {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn construct_track_list(app: &App) -> TrackState {
|
fn construct_track_list(app: &App<CM>) -> TrackState {
|
||||||
let tracks = app.get_track_ids();
|
let tracks = app.get_track_ids();
|
||||||
let list = List::new(
|
let list = List::new(
|
||||||
tracks
|
tracks
|
||||||
@ -220,7 +226,7 @@ impl Ui {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn construct_app_state(app: &App) -> AppState {
|
fn construct_app_state(app: &App<CM>) -> AppState {
|
||||||
AppState {
|
AppState {
|
||||||
artists: Self::construct_artist_list(app),
|
artists: Self::construct_artist_list(app),
|
||||||
albums: Self::construct_album_list(app),
|
albums: Self::construct_album_list(app),
|
||||||
@ -312,7 +318,7 @@ impl Ui {
|
|||||||
Self::render_info_widget("Track info", state.info, state.active, area.info, frame);
|
Self::render_info_widget("Track info", state.info, state.active, area.info, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render<B: Backend>(&self, app: &App, frame: &mut Frame<'_, B>) {
|
pub fn render<B: Backend>(&self, app: &App<CM>, frame: &mut Frame<'_, B>) {
|
||||||
let areas = Self::construct_areas(frame.size());
|
let areas = Self::construct_areas(frame.size());
|
||||||
let app_state = Self::construct_app_state(app);
|
let app_state = Self::construct_app_state(app);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user