Working alternative type-based ui state
This commit is contained in:
parent
9679efefc5
commit
665ba45a1c
@ -5,21 +5,25 @@ use mockall::automock;
|
|||||||
|
|
||||||
use crate::tui::{
|
use crate::tui::{
|
||||||
event::{Event, EventError, EventReceiver},
|
event::{Event, EventError, EventReceiver},
|
||||||
ui::{IUiBrowse, IUiCore, IUiInfo, Ui},
|
ui::{IUi, IUiBrowse, IUiInfo, UiState},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg_attr(test, automock)]
|
#[cfg_attr(test, automock)]
|
||||||
pub trait IEventHandler<BS: IUiBrowse<IS>, IS: IUiInfo<BS>> {
|
pub trait IEventHandler<UI: IUi> {
|
||||||
fn handle_next_event(&self, ui: Ui<BS, IS>) -> Result<Ui<BS, IS>, EventError>;
|
fn handle_next_event(&self, ui: &mut UI) -> Result<(), EventError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
trait IEventHandlerPrivate<BS: IUiBrowse<IS>, IS: IUiInfo<BS>> {
|
trait IEventHandlerPrivate<UI: IUi> {
|
||||||
fn handle_key_event(ui: Ui<BS, IS>, key_event: KeyEvent) -> Result<Ui<BS, IS>, EventError>;
|
fn handle_key_event(ui: &mut UI, key_event: KeyEvent) -> Result<(), EventError>;
|
||||||
fn handle_browse_key_event(ui: BS, key_event: KeyEvent) -> Result<Ui<BS, IS>, EventError>;
|
fn handle_browse_key_event(
|
||||||
fn handle_info_key_event(ui: IS, key_event: KeyEvent) -> Result<Ui<BS, IS>, EventError>;
|
ui: &mut <UI as IUi>::BS,
|
||||||
fn handle_core_key_event<C: IUiCore>(ui: &mut C, key_event: KeyEvent)
|
key_event: KeyEvent,
|
||||||
-> Result<(), EventError>;
|
) -> Result<(), EventError>;
|
||||||
fn quit<C: IUiCore>(ui: &mut C) -> Result<(), EventError>;
|
fn handle_info_key_event(
|
||||||
|
ui: &mut <UI as IUi>::IS,
|
||||||
|
key_event: KeyEvent,
|
||||||
|
) -> Result<(), EventError>;
|
||||||
|
fn quit(ui: &mut UI) -> Result<(), EventError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct EventHandler {
|
pub struct EventHandler {
|
||||||
@ -33,26 +37,46 @@ impl EventHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<BS: IUiBrowse<IS>, IS: IUiInfo<BS>> IEventHandler<BS, IS> for EventHandler {
|
impl<UI: IUi> IEventHandler<UI> for EventHandler {
|
||||||
fn handle_next_event(&self, mut ui: Ui<BS, IS>) -> Result<Ui<BS, IS>, EventError> {
|
fn handle_next_event(&self, ui: &mut UI) -> Result<(), EventError> {
|
||||||
match self.events.recv()? {
|
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::Mouse(_) => {}
|
||||||
Event::Resize(_, _) => {}
|
Event::Resize(_, _) => {}
|
||||||
};
|
};
|
||||||
Ok(ui)
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<BS: IUiBrowse<IS>, IS: IUiInfo<BS>> IEventHandlerPrivate<BS, IS> for EventHandler {
|
impl<UI: IUi> IEventHandlerPrivate<UI> for EventHandler {
|
||||||
fn handle_key_event(ui: Ui<BS, IS>, key_event: KeyEvent) -> Result<Ui<BS, IS>, EventError> {
|
fn handle_key_event(ui: &mut UI, key_event: KeyEvent) -> Result<(), EventError> {
|
||||||
match ui {
|
match key_event.code {
|
||||||
Ui::Browse(browse) => Self::handle_browse_key_event(browse, key_event),
|
// Exit application on `ESC` or `q`.
|
||||||
Ui::Info(info) => Self::handle_info_key_event(info, key_event),
|
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) => {
|
||||||
|
<Self as IEventHandlerPrivate<UI>>::handle_browse_key_event(browse, key_event)?;
|
||||||
|
}
|
||||||
|
UiState::Info(info) => {
|
||||||
|
<Self as IEventHandlerPrivate<UI>>::handle_info_key_event(info, key_event)?;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_browse_key_event(mut ui: BS, key_event: KeyEvent) -> Result<Ui<BS, IS>, EventError> {
|
fn handle_browse_key_event(
|
||||||
|
ui: &mut <UI as IUi>::BS,
|
||||||
|
key_event: KeyEvent,
|
||||||
|
) -> Result<(), EventError> {
|
||||||
match key_event.code {
|
match key_event.code {
|
||||||
// Category change.
|
// Category change.
|
||||||
KeyCode::Left => ui.decrement_category(),
|
KeyCode::Left => ui.decrement_category(),
|
||||||
@ -61,52 +85,29 @@ impl<BS: IUiBrowse<IS>, IS: IUiInfo<BS>> IEventHandlerPrivate<BS, IS> for EventH
|
|||||||
KeyCode::Up => ui.decrement_selection(),
|
KeyCode::Up => ui.decrement_selection(),
|
||||||
KeyCode::Down => ui.increment_selection(),
|
KeyCode::Down => ui.increment_selection(),
|
||||||
// Toggle overlay.
|
// Toggle overlay.
|
||||||
KeyCode::Char('m') | KeyCode::Char('M') => {
|
KeyCode::Char('m') | KeyCode::Char('M') => ui.show_info_overlay(),
|
||||||
return Ok(ui.display_info_overlay());
|
// Othey keys.
|
||||||
}
|
|
||||||
// Other keys.
|
|
||||||
_ => <Self as IEventHandlerPrivate<BS, IS>>::handle_core_key_event(&mut ui, key_event)?,
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Ui::Browse(ui))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_info_key_event(mut ui: IS, key_event: KeyEvent) -> Result<Ui<BS, IS>, EventError> {
|
|
||||||
match key_event.code {
|
|
||||||
// Toggle overlay.
|
|
||||||
KeyCode::Char('m') | KeyCode::Char('M') => {
|
|
||||||
return Ok(ui.hide_info_overlay());
|
|
||||||
}
|
|
||||||
// Other keys.
|
|
||||||
_ => <Self as IEventHandlerPrivate<BS, IS>>::handle_core_key_event(&mut ui, key_event)?,
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Ui::Info(ui))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_core_key_event<C: IUiCore>(
|
|
||||||
ui: &mut C,
|
|
||||||
key_event: KeyEvent,
|
|
||||||
) -> Result<(), EventError> {
|
|
||||||
match key_event.code {
|
|
||||||
// Exit application on `ESC` or `q`.
|
|
||||||
KeyCode::Esc | KeyCode::Char('q') => {
|
|
||||||
<Self as IEventHandlerPrivate<BS, IS>>::quit(ui)?;
|
|
||||||
}
|
|
||||||
// Exit application on `Ctrl-C`.
|
|
||||||
KeyCode::Char('c') | KeyCode::Char('C') => {
|
|
||||||
if key_event.modifiers == KeyModifiers::CONTROL {
|
|
||||||
<Self as IEventHandlerPrivate<BS, IS>>::quit(ui)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Other keys.
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn quit<C: IUiCore>(ui: &mut C) -> Result<(), EventError> {
|
fn handle_info_key_event(
|
||||||
|
ui: &mut <UI as IUi>::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.quit();
|
||||||
ui.save()?;
|
ui.save()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -15,7 +15,7 @@ use std::marker::PhantomData;
|
|||||||
use self::event::EventError;
|
use self::event::EventError;
|
||||||
use self::handler::IEventHandler;
|
use self::handler::IEventHandler;
|
||||||
use self::listener::IEventListener;
|
use self::listener::IEventListener;
|
||||||
use self::ui::{IUiBrowse, IUiInfo, Ui};
|
use self::ui::IUi;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
@ -43,12 +43,12 @@ impl From<EventError> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Tui<B: Backend, BS: IUiBrowse<IS>, IS: IUiInfo<BS>> {
|
pub struct Tui<B: Backend, UI: IUi> {
|
||||||
terminal: Terminal<B>,
|
terminal: Terminal<B>,
|
||||||
_phantom: PhantomData<Ui<BS, IS>>,
|
_phantom: PhantomData<UI>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B: Backend, BS: IUiBrowse<IS>, IS: IUiInfo<BS>> Tui<B, BS, IS> {
|
impl<B: Backend, UI: IUi> Tui<B, UI> {
|
||||||
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()?;
|
||||||
@ -65,14 +65,10 @@ impl<B: Backend, BS: IUiBrowse<IS>, IS: IUiInfo<BS>> Tui<B, BS, IS> {
|
|||||||
self.exit();
|
self.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main_loop(
|
fn main_loop(&mut self, mut ui: UI, handler: impl IEventHandler<UI>) -> Result<(), Error> {
|
||||||
&mut self,
|
|
||||||
mut ui: Ui<BS, IS>,
|
|
||||||
handler: impl IEventHandler<BS, IS>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
while ui.is_running() {
|
while ui.is_running() {
|
||||||
self.terminal.draw(|frame| ui.render(frame))?;
|
self.terminal.draw(|frame| ui.render(frame))?;
|
||||||
ui = handler.handle_next_event(ui)?;
|
handler.handle_next_event(&mut ui)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -80,8 +76,8 @@ impl<B: Backend, BS: IUiBrowse<IS>, IS: IUiInfo<BS>> Tui<B, BS, IS> {
|
|||||||
|
|
||||||
fn main(
|
fn main(
|
||||||
term: Terminal<B>,
|
term: Terminal<B>,
|
||||||
ui: Ui<BS, IS>,
|
ui: UI,
|
||||||
handler: impl IEventHandler<BS, IS>,
|
handler: impl IEventHandler<UI>,
|
||||||
listener: impl IEventListener,
|
listener: impl IEventListener,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mut tui = Tui {
|
let mut tui = Tui {
|
||||||
@ -139,8 +135,8 @@ impl<B: Backend, BS: IUiBrowse<IS>, IS: IUiInfo<BS>> Tui<B, BS, IS> {
|
|||||||
|
|
||||||
pub fn run(
|
pub fn run(
|
||||||
term: Terminal<B>,
|
term: Terminal<B>,
|
||||||
ui: Ui<BS, IS>,
|
ui: UI,
|
||||||
handler: impl IEventHandler<BS, IS>,
|
handler: impl IEventHandler<UI>,
|
||||||
listener: impl IEventListener,
|
listener: impl IEventListener,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
Self::enable()?;
|
Self::enable()?;
|
||||||
|
107
src/tui/ui.rs
107
src/tui/ui.rs
@ -35,48 +35,46 @@ impl From<musichoard::Error> for UiError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Ui<BS: IUiBrowse<IS>, IS: IUiInfo<BS>> {
|
pub enum UiState<BS, IS> {
|
||||||
Browse(BS),
|
Browse(BS),
|
||||||
Info(IS),
|
Info(IS),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<BS: IUiBrowse<IS>, IS: IUiInfo<BS>> Ui<BS, IS> {
|
impl<BS, IS> UiState<BS, IS> {
|
||||||
pub fn is_running(&self) -> bool {
|
fn is_browse(&self) -> bool {
|
||||||
match self {
|
matches!(self, UiState::Browse(_))
|
||||||
Ui::Browse(ref browse) => browse.is_running(),
|
|
||||||
Ui::Info(ref info) => info.is_running(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render<B: Backend>(&mut self, frame: &mut Frame<'_, B>) {
|
fn is_info(&self) -> bool {
|
||||||
match self {
|
matches!(self, UiState::Info(_))
|
||||||
Ui::Browse(ref mut browse) => browse.render_browse_frame(frame),
|
|
||||||
Ui::Info(ref mut info) => info.render_info_frame(frame),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait IUiCore {
|
pub trait IUi {
|
||||||
|
type BS: IUiBrowse;
|
||||||
|
type IS: IUiInfo;
|
||||||
|
|
||||||
fn is_running(&self) -> bool;
|
fn is_running(&self) -> bool;
|
||||||
fn quit(&mut self);
|
fn quit(&mut self);
|
||||||
|
|
||||||
fn save(&mut self) -> Result<(), UiError>;
|
fn save(&mut self) -> Result<(), UiError>;
|
||||||
|
|
||||||
|
fn render<B: Backend>(&mut self, frame: &mut Frame<'_, B>);
|
||||||
|
|
||||||
|
fn state(&mut self) -> UiState<&mut Self::BS, &mut Self::IS>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait IUiBrowse<IS: IUiInfo<Self>>: IUiCore + Sized {
|
pub trait IUiBrowse {
|
||||||
fn increment_category(&mut self);
|
fn increment_category(&mut self);
|
||||||
fn decrement_category(&mut self);
|
fn decrement_category(&mut self);
|
||||||
fn increment_selection(&mut self);
|
fn increment_selection(&mut self);
|
||||||
fn decrement_selection(&mut self);
|
fn decrement_selection(&mut self);
|
||||||
|
|
||||||
fn display_info_overlay(self) -> Ui<Self, IS>;
|
fn show_info_overlay(&mut self);
|
||||||
|
|
||||||
fn render_browse_frame<B: Backend>(&mut self, frame: &mut Frame<'_, B>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait IUiInfo<BS: IUiBrowse<Self>>: IUiCore + Sized {
|
pub trait IUiInfo {
|
||||||
fn hide_info_overlay(self) -> Ui<BS, Self>;
|
fn hide_info_overlay(&mut self);
|
||||||
|
|
||||||
fn render_info_frame<B: Backend>(&mut self, frame: &mut Frame<'_, B>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TrackSelection {
|
struct TrackSelection {
|
||||||
@ -306,12 +304,6 @@ impl Selection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct UiImpl<MH> {
|
|
||||||
music_hoard: MH,
|
|
||||||
selection: Selection,
|
|
||||||
running: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ArtistArea {
|
struct ArtistArea {
|
||||||
list: Rect,
|
list: Rect,
|
||||||
}
|
}
|
||||||
@ -563,21 +555,23 @@ impl<'a, 'b> TrackState<'a, 'b> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<MH: IMusicHoard> Ui<UiImpl<MH>, UiImpl<MH>> {
|
pub struct Ui<MH> {
|
||||||
pub fn new(mh: MH) -> Result<Self, Error> {
|
running: bool,
|
||||||
Ok(Ui::Browse(UiImpl::new(mh)?))
|
music_hoard: MH,
|
||||||
}
|
selection: Selection,
|
||||||
|
state: UiState<(), ()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<MH: IMusicHoard> UiImpl<MH> {
|
impl<MH: IMusicHoard> Ui<MH> {
|
||||||
pub fn new(mut music_hoard: MH) -> Result<Self, Error> {
|
pub fn new(mut music_hoard: MH) -> Result<Self, Error> {
|
||||||
music_hoard.load_from_database()?;
|
music_hoard.load_from_database()?;
|
||||||
music_hoard.rescan_library()?;
|
music_hoard.rescan_library()?;
|
||||||
let selection = Selection::new(Some(music_hoard.get_collection()));
|
let selection = Selection::new(Some(music_hoard.get_collection()));
|
||||||
Ok(UiImpl {
|
Ok(Ui {
|
||||||
|
running: true,
|
||||||
music_hoard,
|
music_hoard,
|
||||||
selection,
|
selection,
|
||||||
running: true,
|
state: UiState::Browse(()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -724,7 +718,10 @@ impl<MH: IMusicHoard> UiImpl<MH> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<MH: IMusicHoard> IUiCore for UiImpl<MH> {
|
impl<MH: IMusicHoard> IUi for Ui<MH> {
|
||||||
|
type BS = Self;
|
||||||
|
type IS = Self;
|
||||||
|
|
||||||
fn is_running(&self) -> bool {
|
fn is_running(&self) -> bool {
|
||||||
self.running
|
self.running
|
||||||
}
|
}
|
||||||
@ -734,12 +731,25 @@ impl<MH: IMusicHoard> IUiCore for UiImpl<MH> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn save(&mut self) -> Result<(), UiError> {
|
fn save(&mut self) -> Result<(), UiError> {
|
||||||
self.music_hoard.save_to_database()?;
|
Ok(self.music_hoard.save_to_database()?)
|
||||||
Ok(())
|
}
|
||||||
|
|
||||||
|
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<B: Backend>(&mut self, frame: &mut Frame<'_, B>) {
|
||||||
|
self.render_collection(frame);
|
||||||
|
if self.state.is_info() {
|
||||||
|
self.render_overlay(frame);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<MH: IMusicHoard> IUiBrowse<Self> for UiImpl<MH> {
|
impl<MH: IMusicHoard> IUiBrowse for Ui<MH> {
|
||||||
fn increment_category(&mut self) {
|
fn increment_category(&mut self) {
|
||||||
self.selection.increment_category();
|
self.selection.increment_category();
|
||||||
}
|
}
|
||||||
@ -758,23 +768,16 @@ impl<MH: IMusicHoard> IUiBrowse<Self> for UiImpl<MH> {
|
|||||||
.decrement_selection(self.music_hoard.get_collection());
|
.decrement_selection(self.music_hoard.get_collection());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_info_overlay(self) -> Ui<Self, Self> {
|
fn show_info_overlay(&mut self) {
|
||||||
Ui::Info(self)
|
assert!(self.state.is_browse());
|
||||||
}
|
self.state = UiState::Info(());
|
||||||
|
|
||||||
fn render_browse_frame<B: Backend>(&mut self, frame: &mut Frame<'_, B>) {
|
|
||||||
self.render_collection(frame);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<MH: IMusicHoard> IUiInfo<Self> for UiImpl<MH> {
|
impl<MH: IMusicHoard> IUiInfo for Ui<MH> {
|
||||||
fn hide_info_overlay(self) -> Ui<Self, Self> {
|
fn hide_info_overlay(&mut self) {
|
||||||
Ui::Browse(self)
|
assert!(self.state.is_info());
|
||||||
}
|
self.state = UiState::Browse(());
|
||||||
|
|
||||||
fn render_info_frame<B: Backend>(&mut self, frame: &mut Frame<'_, B>) {
|
|
||||||
self.render_collection(frame);
|
|
||||||
self.render_overlay(frame);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user