From 9d3225f0edd79df841d89e2aa2c1433c030f74cd Mon Sep 17 00:00:00 2001 From: Nick Dowsett Date: Tue, 3 Dec 2024 22:05:45 +0800 Subject: [PATCH] Implement list and text entry actions (basic) --- youtui/src/app/component/actionhandler.rs | 3 - youtui/src/app/ui.rs | 76 +++++++++++++------ youtui/src/app/ui/action.rs | 2 +- youtui/src/app/ui/browser.rs | 35 ++++++++- .../app/ui/browser/artistalbums/albumsongs.rs | 22 +++++- .../ui/browser/artistalbums/artistsearch.rs | 18 ++++- youtui/src/app/ui/playlist.rs | 12 ++- youtui/src/app/view.rs | 1 + 8 files changed, 139 insertions(+), 30 deletions(-) diff --git a/youtui/src/app/component/actionhandler.rs b/youtui/src/app/component/actionhandler.rs index dc84963..e75da06 100644 --- a/youtui/src/app/component/actionhandler.rs +++ b/youtui/src/app/component/actionhandler.rs @@ -131,9 +131,6 @@ pub fn count_visible_keybinds, A: Action + 'static>(component: & .filter(|(_, kt)| (*kt).get_visibility() != CommandVisibility::Hidden) .count() } -pub trait ContainsList { - fn list_is_active(&self) -> bool; -} /// A component of the application that handles text entry, currently designed /// to wrap rat_text::TextInputState. pub trait TextHandler: Component { diff --git a/youtui/src/app/ui.rs b/youtui/src/app/ui.rs index 05d2011..f3cfecf 100644 --- a/youtui/src/app/ui.rs +++ b/youtui/src/app/ui.rs @@ -1,7 +1,7 @@ use self::{browser::Browser, logger::Logger, playlist::Playlist}; use super::component::actionhandler::{ - count_visible_keybinds, handle_key_stack, Action, ComponentEffect, ContainsList, - DominantKeyRouter, KeyHandleAction, KeyRouter, Keymap, TextHandler, + count_visible_keybinds, handle_key_stack, Action, ComponentEffect, DominantKeyRouter, + KeyHandleAction, KeyRouter, Keymap, TextHandler, }; use super::keycommand::{DisplayableCommand, DisplayableMode}; use super::server::{ArcServer, IncreaseVolume, TaskMetadata}; @@ -83,24 +83,11 @@ impl Scrollable for HelpMenu { .saturating_add_signed(amount) .min(self.len.saturating_sub(1)); } - fn get_selected_item(&self) -> usize { self.cur } -} - -impl ContainsList for YoutuiWindow { - // TODO: propogate implementation down. - fn list_is_active(&self) -> bool { - self.help.shown - || match self.context { - WindowContext::Browser => { - !self.browser.artist_list.search_popped - || self.browser.input_routing == browser::InputRouting::Song - } - WindowContext::Playlist => true, - WindowContext::Logs => false, - } + fn is_scrollable(&self) -> bool { + todo!() } } @@ -130,11 +117,31 @@ impl DominantKeyRouter for YoutuiWindow { } } +impl Scrollable for YoutuiWindow { + fn increment_list(&mut self, amount: isize) { + todo!() + } + fn get_selected_item(&self) -> usize { + todo!() + } + fn is_scrollable(&self) -> bool { + self.help.shown + || match self.context { + WindowContext::Browser => { + !self.browser.artist_list.search_popped + || self.browser.input_routing == browser::InputRouting::Song + } + WindowContext::Playlist => true, + WindowContext::Logs => false, + } + } +} + impl KeyRouter for YoutuiWindow { fn get_active_keybinds(&self) -> impl Iterator> { // If Browser has dominant keybinds, self keybinds shouldn't be visible. let kb = std::iter::once(&self.keybinds); - let kb = if self.list_is_active() { + let kb = if self.is_scrollable() { Either::Left(kb.chain(std::iter::once(&self.list_keybinds))) } else { Either::Right(kb) @@ -164,6 +171,9 @@ impl KeyRouter for YoutuiWindow { impl TextHandler for YoutuiWindow { fn is_text_handling(&self) -> bool { + if self.help.shown { + return false; + } match self.context { WindowContext::Browser => self.browser.is_text_handling(), WindowContext::Playlist => self.playlist.is_text_handling(), @@ -263,15 +273,37 @@ impl YoutuiWindow { } pub fn handle_list_action(&mut self, action: ListAction) -> ComponentEffect { if self.help.shown { - todo!(); + match action { + ListAction::Up => self.help.increment_list(-1), + ListAction::Down => self.help.increment_list(1), + } + return AsyncTask::new_no_op(); } match self.context { - WindowContext::Browser => todo!(), - WindowContext::Playlist => todo!(), + WindowContext::Browser => self + .browser + .handle_list_action(action) + .map(|this: &mut Self| &mut this.browser), + WindowContext::Playlist => self + .playlist + .handle_list_action(action) + .map(|this: &mut Self| &mut this.playlist), + WindowContext::Logs => AsyncTask::new_no_op(), + } + } + pub fn handle_text_entry_action(&mut self, action: TextEntryAction) -> ComponentEffect { + if !self.is_text_handling() { + return AsyncTask::new_no_op(); + } + match self.context { + WindowContext::Browser => self + .browser + .handle_text_entry_action(action) + .map(|this: &mut Self| &mut this.browser), + WindowContext::Playlist => AsyncTask::new_no_op(), WindowContext::Logs => AsyncTask::new_no_op(), } } - pub fn handle_text_entry_action(&mut self, action: TextEntryAction) -> ComponentEffect {} pub fn pauseplay(&mut self) -> ComponentEffect { self.playlist .pauseplay() diff --git a/youtui/src/app/ui/action.rs b/youtui/src/app/ui/action.rs index 8a86a14..ab7ed1d 100644 --- a/youtui/src/app/ui/action.rs +++ b/youtui/src/app/ui/action.rs @@ -260,7 +260,7 @@ impl Action for HelpAction { } fn describe(&self) -> std::borrow::Cow { match self { - HelpAction::Close => "Close Help".return state.handle_text_entry_action(a)into(), + HelpAction::Close => "Close Help".into(), } } async fn apply( diff --git a/youtui/src/app/ui/browser.rs b/youtui/src/app/ui/browser.rs index 2530772..a4876a9 100644 --- a/youtui/src/app/ui/browser.rs +++ b/youtui/src/app/ui/browser.rs @@ -2,7 +2,10 @@ use self::{ artistalbums::{albumsongs::AlbumSongsPanel, artistsearch::ArtistSearchPanel}, draw::draw_browser, }; -use super::{action::AppAction, AppCallback, WindowContext}; +use super::{ + action::{AppAction, ListAction, TextEntryAction}, + AppCallback, WindowContext, +}; use crate::app::{ component::actionhandler::{ Action, Component, ComponentEffect, DominantKeyRouter, KeyRouter, Suggestable, TextHandler, @@ -227,6 +230,36 @@ impl Browser { // Doesn't consider previous routing. self.input_routing = self.input_routing.right(); } + pub fn handle_list_action(&mut self, action: ListAction) -> ComponentEffect { + match self.input_routing { + InputRouting::Artist => self + .artist_list + .handle_list_action(action) + .map(|this: &mut Self| &mut this.artist_list), + InputRouting::Song => self + .album_songs_list + .handle_list_action(action) + .map(|this: &mut Self| &mut this.album_songs_list), + } + } + pub fn handle_text_entry_action(&mut self, action: TextEntryAction) -> ComponentEffect { + if self.is_text_handling() + && self.artist_list.search_popped + && self.input_routing == InputRouting::Artist + { + match action { + TextEntryAction::Submit => return self.get_songs(), + // Handled by old handle_text_event_impl. + // + // TODO: remove the duplication of responsibilities between this function and + // handle_text_event_impl. + TextEntryAction::Left => (), + TextEntryAction::Right => (), + TextEntryAction::Backspace => (), + } + } + AsyncTask::new_no_op() + } pub fn handle_toggle_search(&mut self) { if self.artist_list.search_popped { self.artist_list.close_search(); diff --git a/youtui/src/app/ui/browser/artistalbums/albumsongs.rs b/youtui/src/app/ui/browser/artistalbums/albumsongs.rs index d55b227..56938bf 100644 --- a/youtui/src/app/ui/browser/artistalbums/albumsongs.rs +++ b/youtui/src/app/ui/browser/artistalbums/albumsongs.rs @@ -4,7 +4,7 @@ use crate::app::component::actionhandler::{ }; use crate::app::server::{ArcServer, TaskMetadata}; use crate::app::structures::{ListSong, SongListComponent}; -use crate::app::ui::action::AppAction; +use crate::app::ui::action::{AppAction, ListAction}; use crate::app::ui::browser::Browser; use crate::app::view::{ Filter, FilterString, SortDirection, SortableTableView, TableFilterCommand, TableSortCommand, @@ -340,6 +340,23 @@ impl AlbumSongsPanel { self.sort.shown = false; self.route = AlbumSongsInputRouting::List; } + pub fn handle_list_action(&mut self, action: ListAction) -> ComponentEffect { + if self.sort.shown { + match action { + ListAction::Up => self.handle_sort_up(), + ListAction::Down => self.handle_sort_down(), + } + return AsyncTask::new_no_op(); + } + if self.filter.shown { + return AsyncTask::new_no_op(); + } + match action { + ListAction::Up => self.increment_list(-1), + ListAction::Down => self.increment_list(1), + } + AsyncTask::new_no_op() + } pub fn handle_pop_sort(&mut self) { // If no sortable columns, should we not handle this command? self.sort.cur = 0; @@ -471,6 +488,9 @@ impl Scrollable for AlbumSongsPanel { fn get_selected_item(&self) -> usize { self.cur_selected } + fn is_scrollable(&self) -> bool { + todo!() + } } impl TableView for AlbumSongsPanel { diff --git a/youtui/src/app/ui/browser/artistalbums/artistsearch.rs b/youtui/src/app/ui/browser/artistalbums/artistsearch.rs index 5bad363..d82a7a9 100644 --- a/youtui/src/app/ui/browser/artistalbums/artistsearch.rs +++ b/youtui/src/app/ui/browser/artistalbums/artistsearch.rs @@ -4,7 +4,10 @@ use crate::{ Action, Component, ComponentEffect, KeyRouter, Keymap, Suggestable, TextHandler, }, server::{ArcServer, GetSearchSuggestions, TaskMetadata}, - ui::{action::AppAction, browser::Browser}, + ui::{ + action::{AppAction, ListAction}, + browser::Browser, + }, view::{ListView, Loadable, Scrollable, SortableList}, }, config::Config, @@ -135,6 +138,16 @@ impl ArtistSearchPanel { self.search_popped = false; self.route = ArtistInputRouting::List; } + pub fn handle_list_action(&mut self, action: ListAction) -> ComponentEffect { + if self.route != ArtistInputRouting::List { + return AsyncTask::new_no_op(); + } + match action { + ListAction::Up => self.increment_list(-1), + ListAction::Down => self.increment_list(1), + } + AsyncTask::new_no_op() + } } impl Component for ArtistSearchPanel { type Bkend = ArcServer; @@ -283,6 +296,9 @@ impl Scrollable for ArtistSearchPanel { fn get_selected_item(&self) -> usize { self.selected } + fn is_scrollable(&self) -> bool { + todo!() + } } impl SortableList for ArtistSearchPanel { diff --git a/youtui/src/app/ui/playlist.rs b/youtui/src/app/ui/playlist.rs index 802165e..bd0e83d 100644 --- a/youtui/src/app/ui/playlist.rs +++ b/youtui/src/app/ui/playlist.rs @@ -34,7 +34,7 @@ use std::{borrow::Cow, fmt::Debug}; use tokio::sync::mpsc; use tracing::{error, info, warn}; -use super::action::AppAction; +use super::action::{AppAction, ListAction}; const SONGS_AHEAD_TO_BUFFER: usize = 3; const SONGS_BEHIND_TO_SAVE: usize = 1; @@ -150,6 +150,9 @@ impl Scrollable for Playlist { fn get_selected_item(&self) -> usize { self.cur_selected } + fn is_scrollable(&self) -> bool { + true + } } impl TableView for Playlist { @@ -601,6 +604,13 @@ impl Playlist { // XXX: Consider downloading upcoming songs here. // self.download_upcoming_songs().await; } + pub fn handle_list_action(&mut self, action: ListAction) -> ComponentEffect { + match action { + ListAction::Up => self.increment_list(-1), + ListAction::Down => self.increment_list(1), + } + AsyncTask::new_no_op() + } /// Handle seek command (from global keypress). pub fn handle_seek( &mut self, diff --git a/youtui/src/app/view.rs b/youtui/src/app/view.rs index 2fc6da3..a08285e 100644 --- a/youtui/src/app/view.rs +++ b/youtui/src/app/view.rs @@ -129,6 +129,7 @@ pub trait Scrollable { // Increment the list by the specified amount. fn increment_list(&mut self, amount: isize); fn get_selected_item(&self) -> usize; + fn is_scrollable(&self) -> bool; } /// A struct that can either be scrolled or forward scroll commands to a /// component.