Skip to content

Commit

Permalink
refactor: Library.ArtistList
Browse files Browse the repository at this point in the history
feat: Library.on_select_songs_fn + other small quality-of-life improvements
  • Loading branch information
lautarodragan committed Oct 9, 2024
1 parent e43ed7b commit 9ec78fd
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 107 deletions.
9 changes: 9 additions & 0 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,15 @@ impl<'a> App<'a> {
}
}
});
library.on_select_songs_fn({
let player = player.clone();
let library = library.clone();

move |songs| {
player.enqueue_songs(songs);
library.on_key(KeyEvent::new(KeyCode::Down, KeyModifiers::NONE)); // hackish way to "select_next()"
}
});

let playlist = Arc::new(ui::Playlists::new(config.theme, state.playlists));
playlist.on_select({
Expand Down
1 change: 1 addition & 0 deletions src/components/library.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ pub mod widget;
pub mod keyboard_handler;

mod song_list;
mod artist_list;

pub use library::*;
5 changes: 5 additions & 0 deletions src/components/library/artist_list.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mod artist_list;
mod widget;
mod keyboard_handler;

pub use artist_list::ArtistList;
68 changes: 9 additions & 59 deletions src/components/library/keyboard_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,74 +11,24 @@ impl<'a> KeyboardHandlerRef<'a> for Library<'a> {

fn on_key(&self, key: KeyEvent) -> bool {
log::trace!(target: "::library.on_key", "start {:?}", key);
let focused_element_guard = self.focused_element();

let focused_element = self.focused_element();

match key.code {
KeyCode::Tab => {
self.set_focused_element(match focused_element_guard {
self.set_focused_element(match focused_element {
LibraryScreenElement::ArtistList => LibraryScreenElement::SongList,
LibraryScreenElement::SongList => LibraryScreenElement::ArtistList,
});
true
}
_ if focused_element_guard == LibraryScreenElement::ArtistList => {
self.on_key_event_artist_list(key);
},
_ if focused_element_guard == LibraryScreenElement::SongList => {
self.song_list.on_key(key);
_ if focused_element == LibraryScreenElement::ArtistList => {
self.artist_list.on_key(key)
},
_ => {
return false;
_ if focused_element == LibraryScreenElement::SongList => {
self.song_list.on_key(key)
},
_ => false,
}

true
}
}


impl<'a> Library<'a> {

fn on_key_event_artist_list(&self, key: KeyEvent) {
let mut artists = self.artists.lock().unwrap();
let len = artists.len();

match key.code {
KeyCode::Up => {
let _ = self.selected_artist_index.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |a| { Some(a.saturating_sub(1)) });
self.selected_song_index.store(0, Ordering::SeqCst);
self.offset.store(0, Ordering::SeqCst);
},
KeyCode::Down => {
let _ = self.selected_artist_index.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |a| { Some(a.saturating_add(1).min(len.saturating_sub(1))) });
self.selected_song_index.store(0, Ordering::SeqCst);
self.offset.store(0, Ordering::SeqCst);
},
KeyCode::Home => {
self.selected_artist_index.store(0, Ordering::SeqCst);
self.selected_song_index.store(0, Ordering::SeqCst);
self.offset.store(0, Ordering::SeqCst);
},
KeyCode::End => {
self.selected_artist_index.store(len.saturating_sub(1), Ordering::SeqCst);
self.selected_song_index.store(0, Ordering::SeqCst);
self.offset.store(0, Ordering::SeqCst);
},
KeyCode::Delete => {
let removed_artist = artists.remove(self.selected_artist_index.load(Ordering::SeqCst));
let mut songs = self.songs.lock().unwrap();
songs.remove(removed_artist.as_str());
self.offset.store(0, Ordering::SeqCst);
let _ = self.selected_artist_index.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |a| { Some(a.saturating_sub(1).min(len.saturating_sub(1))) });
},
_ => {},
}


let artist = artists[self.selected_artist_index.load(Ordering::SeqCst)].as_str();
let songs = self.songs.lock().unwrap();
let artist_songs = songs.get(artist).unwrap();
let artist_songs = artist_songs.iter().map(|s| s.clone()).collect();
self.song_list.set_songs(artist_songs);
}

}
78 changes: 70 additions & 8 deletions src/components/library/library.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ use crate::{
cue::CueSheet,
ui::KeyboardHandlerRef,
};
use super::song_list::SongList;

use super::{song_list::SongList, artist_list::ArtistList};

#[derive(Eq, PartialEq)]
#[repr(u8)]
Expand Down Expand Up @@ -56,16 +57,17 @@ impl AtomicLibraryScreenElement {
pub struct Library<'a> {
pub(super) theme: Theme,

pub(super) artists: Arc<Mutex<Vec<String>>>,
pub(super) songs: Mutex<HashMap<String, Vec<Song>>>,
pub(super) song_list: SongList<'a>,
pub(super) songs: Rc<Mutex<HashMap<String, Vec<Song>>>>,
pub(super) song_list: Rc<SongList<'a>>,
pub(super) artist_list: Rc<ArtistList<'a>>,

focused_element: AtomicLibraryScreenElement,

pub(super) selected_artist_index: AtomicUsize,
pub(super) selected_song_index: AtomicUsize,

pub(super) on_select_fn: Rc<Mutex<Box<dyn FnMut((Song, KeyEvent)) + 'a>>>,
pub(super) on_select_songs_fn: Rc<Mutex<Box<dyn FnMut(Vec<Song>) + 'a>>>,

pub(super) offset: AtomicUsize,
pub(super) height: AtomicUsize,
Expand All @@ -74,8 +76,63 @@ pub struct Library<'a> {
impl<'a> Library<'a> {
pub fn new(theme: Theme, songs: Vec<Song>) -> Self {
let on_select_fn: Rc<Mutex<Box<dyn FnMut((Song, KeyEvent)) + 'a>>> = Rc::new(Mutex::new(Box::new(|_| {}) as _));
let on_select_songs_fn: Rc<Mutex<Box<dyn FnMut(Vec<Song>) + 'a>>> = Rc::new(Mutex::new(Box::new(|_| {}) as _));

let song_map = Rc::new(Mutex::new(HashMap::<String, Vec<Song>>::new()));
let artist_list = Rc::new(ArtistList::new(theme));
let song_list = Rc::new(SongList::new(theme));

artist_list.on_select({
let songs = song_map.clone();
let song_list = song_list.clone();

move |artist| {
log::trace!(target: "::library.artist_list.on_select", "artist selected {:?}", artist);

log::trace!(target: "::library.artist_list.on_select", "A");
let songs = songs.lock().unwrap();
log::trace!(target: "::library.artist_list.on_select", "B");
let artist_songs = songs.get(artist.as_str()).unwrap();
let artist_songs = artist_songs.iter().map(|s| s.clone()).collect();
log::trace!(target: "::library.artist_list.on_select", "C");
song_list.set_songs(artist_songs);
log::trace!(target: "::library.artist_list.on_select", "D");
}
});

artist_list.on_confirm({
let songs = song_map.clone();
let on_select_songs_fn = on_select_songs_fn.clone();

move |artist| {
log::trace!(target: "::library.artist_list.on_confirm", "artist confirmed {:?}", artist);

let songs = {
let songs = songs.lock().unwrap();
let Some(songs) = songs.get(artist.as_str()) else {
log::warn!(target: "::library.artist_list.on_confirm", "no songs for artist {:?}", artist);
return;
};

songs.iter().map(|s| s.clone()).collect()
};

on_select_songs_fn.lock().unwrap()(songs);

}
});

artist_list.on_delete({
let songs = song_map.clone();

move |artist| {
log::trace!(target: "::library.artist_list.on_delete", "artist deleted {:?}", artist);

let mut songs = songs.lock().unwrap();
songs.remove(artist.as_str());
}
});

let song_list = SongList::new(theme);
song_list.on_select({
let on_select_fn = on_select_fn.clone();
move |(song, key)| {
Expand All @@ -91,10 +148,11 @@ impl<'a> Library<'a> {
focused_element: AtomicLibraryScreenElement::new(),

on_select_fn,
on_select_songs_fn,

artists: Arc::new(Mutex::new(vec![])),
songs: Mutex::new(HashMap::new()),
songs: song_map,
song_list,
artist_list,

selected_artist_index: AtomicUsize::new(0),
selected_song_index: AtomicUsize::new(0),
Expand All @@ -120,6 +178,10 @@ impl<'a> Library<'a> {
*self.on_select_fn.lock().unwrap() = Box::new(cb);
}

pub fn on_select_songs_fn(&self, cb: impl FnMut(Vec<Song>) + 'a) {
*self.on_select_songs_fn.lock().unwrap() = Box::new(cb);
}

pub fn songs(&self) -> Vec<Song> {
let mut songs = vec![];

Expand Down Expand Up @@ -179,7 +241,7 @@ impl<'a> Library<'a> {
songs.insert(artist.clone(), vec![song]);
}

let mut artists = self.artists.lock().unwrap();
let mut artists = self.artist_list.artists.lock().unwrap();

if !artists.contains(&artist) {
(*artists).push(artist.clone());
Expand Down
42 changes: 2 additions & 40 deletions src/components/library/widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ use ratatui::{
prelude::Widget,
buffer::Buffer,
layout::{Constraint, Layout, Rect},
style::{Color, Style},
widgets::{WidgetRef},
};

use super::{Library, LibraryScreenElement};
use super::Library;

impl<'a> Widget for Library<'a> {
fn render(self, area: Rect, buf: &mut Buffer) {
Expand All @@ -28,44 +27,7 @@ impl<'a> WidgetRef for Library<'a> {

self.height.store(area.height as usize, Ordering::Relaxed);

self.render_ref_artists(area_left, buf);
self.artist_list.render_ref(area_left, buf);
self.song_list.render_ref(area_right, buf);
}
}

fn line_style(theme: &crate::config::Theme, index: usize, selected_index: usize, list_has_focus: bool) -> Style {
if index == selected_index {
if list_has_focus {
Style::default().fg(theme.foreground_selected).bg(theme.background_selected)
} else {
Style::default().fg(theme.foreground_selected).bg(theme.background_selected_blur)
}
} else {
Style::default().fg(theme.foreground_secondary).bg(theme.background)
}
}

impl<'a> Library<'a> {
fn render_ref_artists(&self, area: Rect, buf: &mut Buffer) {
self.height.store(area.height as usize, Ordering::Relaxed);

let focused_element = self.focused_element();
let selected_artist_index = self.selected_artist_index.load(Ordering::Relaxed);
let artists = self.artists.lock().unwrap();

for i in 0..artists.len() {
let artist = artists[i].as_str();
let area = Rect {
y: area.y + i as u16,
height: 1,
..area
};

let style = line_style(&self.theme, i, selected_artist_index, focused_element == LibraryScreenElement::ArtistList);
let line = ratatui::text::Line::from(artist).style(style);

line.render_ref(area, buf);
}
}

}
4 changes: 4 additions & 0 deletions src/player.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,10 @@ impl Player {
self.queue_items.add_back(song);
}

pub fn enqueue_songs(&self, songs: Vec<Song>) {
self.queue_items.append(&mut std::collections::VecDeque::from(songs));
}

pub fn enqueue_cue(&self, cue_sheet: CueSheet) {
let songs = Song::from_cue_sheet(cue_sheet);
self.queue_items.append(&mut std::collections::VecDeque::from(songs));
Expand Down

0 comments on commit 9ec78fd

Please sign in to comment.