-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Library.on_select_songs_fn + other small quality-of-life improvements
- Loading branch information
1 parent
e43ed7b
commit 24a534f
Showing
10 changed files
with
364 additions
and
107 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,5 +3,6 @@ pub mod widget; | |
pub mod keyboard_handler; | ||
|
||
mod song_list; | ||
mod artist_list; | ||
|
||
pub use library::*; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
use std::{ | ||
cmp::Ordering, | ||
collections::{HashMap, HashSet}, | ||
sync::{ | ||
atomic::{AtomicUsize, Ordering as AtomicOrdering}, | ||
Mutex, | ||
Arc, | ||
}, | ||
path::PathBuf, | ||
}; | ||
|
||
use crossterm::event::KeyEvent; | ||
|
||
use crate::{ | ||
config::Theme, | ||
}; | ||
|
||
pub struct ArtistList<'a> { | ||
pub(super) theme: Theme, | ||
|
||
pub artists: Mutex<Vec<String>>, | ||
pub(super) selected_index: AtomicUsize, | ||
|
||
pub(super) on_select_fn: Mutex<Box<dyn FnMut(String) + 'a>>, | ||
pub(super) on_confirm_fn: Mutex<Box<dyn FnMut(String) + 'a>>, | ||
pub(super) on_delete_fn: Mutex<Box<dyn FnMut(String) + 'a>>, | ||
|
||
pub(super) offset: AtomicUsize, | ||
pub(super) height: AtomicUsize, | ||
} | ||
|
||
impl<'a> ArtistList<'a> { | ||
pub fn new(theme: Theme) -> Self { | ||
Self { | ||
theme, | ||
|
||
on_select_fn: Mutex::new(Box::new(|_| {}) as _), | ||
on_confirm_fn: Mutex::new(Box::new(|_| {}) as _), | ||
on_delete_fn: Mutex::new(Box::new(|_| {}) as _), | ||
|
||
artists: Mutex::new(Vec::new()), | ||
selected_index: AtomicUsize::new(0), | ||
|
||
offset: AtomicUsize::new(0), | ||
height: AtomicUsize::new(0), | ||
} | ||
} | ||
|
||
pub fn on_select(&self, cb: impl FnMut(String) + 'a) { | ||
*self.on_select_fn.lock().unwrap() = Box::new(cb); | ||
} | ||
|
||
pub fn on_confirm(&self, cb: impl FnMut(String) + 'a) { | ||
*self.on_confirm_fn.lock().unwrap() = Box::new(cb); | ||
} | ||
|
||
pub fn on_delete(&self, cb: impl FnMut(String) + 'a) { | ||
*self.on_delete_fn.lock().unwrap() = Box::new(cb); | ||
} | ||
|
||
pub fn set_artists(&self, artists: Vec<String>) { | ||
*self.artists.lock().unwrap() = artists; | ||
} | ||
|
||
pub fn selected_artist(&self) -> String { | ||
let artists = self.artists.lock().unwrap(); | ||
|
||
let i = self.selected_index.load(AtomicOrdering::SeqCst); | ||
|
||
if i >= artists.len() { | ||
log::error!(target: "::ArtistList.selected_artist()", "selected_index > artists.len"); | ||
return "".to_string(); | ||
} | ||
|
||
artists[i].clone() | ||
} | ||
} | ||
|
||
impl Drop for ArtistList<'_> { | ||
fn drop(&mut self) { | ||
log::trace!("Artists.drop()"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
use std::sync::atomic::Ordering; | ||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; | ||
|
||
use crate::{ | ||
ui::KeyboardHandlerRef, | ||
}; | ||
|
||
use super::artist_list::ArtistList; | ||
|
||
impl<'a> KeyboardHandlerRef<'a> for ArtistList<'a> { | ||
|
||
fn on_key(&self, key: KeyEvent) -> bool { | ||
let target = "::ArtistList.on_key"; | ||
log::trace!(target: target, "{:?}", key); | ||
|
||
match key.code { | ||
KeyCode::Up | KeyCode::Down | KeyCode::Home | KeyCode::End => { | ||
self.on_artist_list_directional_key(key); | ||
let artist = self.selected_artist(); | ||
self.on_select_fn.lock().unwrap()(artist); | ||
}, | ||
KeyCode::Enter => { | ||
let artist = self.selected_artist(); | ||
self.on_confirm_fn.lock().unwrap()(artist); | ||
}, | ||
KeyCode::Delete => { | ||
let mut artists = self.artists.lock().unwrap(); | ||
let i = self.selected_index.load(Ordering::SeqCst); | ||
|
||
let removed_artist = artists.remove(i); | ||
|
||
self.on_delete_fn.lock().unwrap()(removed_artist); | ||
|
||
let i = i.saturating_sub(1).min(artists.len().saturating_sub(1)); | ||
|
||
self.selected_index.store(i, Ordering::SeqCst); | ||
self.offset.store(0, Ordering::SeqCst); | ||
|
||
let artist = artists[i].clone(); | ||
|
||
drop(artists); | ||
|
||
self.on_select_fn.lock().unwrap()(artist); | ||
}, | ||
KeyCode::Char(char) => { | ||
let artists = self.artists.lock().unwrap(); | ||
let char_up = char.to_lowercase().to_string(); | ||
let char_low = char.to_uppercase().to_string(); | ||
|
||
let Some(i) = artists.iter().position(|a| | ||
a.starts_with(char) || | ||
a.starts_with(char_up.as_str()) || | ||
a.starts_with(char_low.as_str()) | ||
) else { | ||
return false; | ||
}; | ||
|
||
self.selected_index.store(i, Ordering::SeqCst); | ||
|
||
let artist = artists[i].clone(); | ||
drop(artists); | ||
|
||
self.on_select_fn.lock().unwrap()(artist); | ||
} | ||
_ => {}, | ||
} | ||
|
||
true | ||
} | ||
} | ||
|
||
|
||
impl<'a> ArtistList<'a> { | ||
|
||
fn on_artist_list_directional_key(&self, key: KeyEvent) { | ||
let artists = self.artists.lock().unwrap(); | ||
let length = artists.len() as i32; | ||
|
||
let height = self.height.load(Ordering::Relaxed) as i32; | ||
let padding = 5; | ||
|
||
let mut offset = self.offset.load(Ordering::SeqCst) as i32; | ||
let mut i = self.selected_index.load(Ordering::SeqCst) as i32; | ||
|
||
match key.code { | ||
KeyCode::Up | KeyCode::Down => { | ||
if key.code == KeyCode::Up { | ||
i -= 1; | ||
} else { | ||
i += 1; | ||
} | ||
|
||
let padding = if key.code == KeyCode::Up { | ||
padding | ||
} else { | ||
height.saturating_sub(padding).saturating_sub(1) | ||
}; | ||
|
||
if (key.code == KeyCode::Up && i < offset + padding) || (key.code == KeyCode::Down && i > offset + padding) { | ||
offset = if i > padding { | ||
i - padding | ||
} else { | ||
0 | ||
}; | ||
} | ||
|
||
}, | ||
KeyCode::Home => { | ||
i = 0; | ||
offset = 0; | ||
}, | ||
KeyCode::End => { | ||
i = length - 1; | ||
offset = i - height + padding; | ||
}, | ||
_ => {}, | ||
} | ||
|
||
offset = offset.min(length - height).max(0); | ||
i = i.min(length - 1).max(0); | ||
|
||
self.offset.store(offset as usize, Ordering::SeqCst); | ||
self.selected_index.store(i as usize, Ordering::SeqCst); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
use std::sync::atomic::Ordering; | ||
|
||
use ratatui::{ | ||
prelude::Widget, | ||
buffer::Buffer, | ||
layout::{Constraint, Layout, Rect}, | ||
style::{Color, Style}, | ||
widgets::{WidgetRef}, | ||
}; | ||
|
||
use super::artist_list::ArtistList; | ||
|
||
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> WidgetRef for ArtistList<'a> { | ||
fn render_ref(&self, area: Rect, buf: &mut Buffer) { | ||
self.height.store(area.height as usize, Ordering::Relaxed); | ||
|
||
let artists = &self.artists.lock().unwrap(); | ||
|
||
if artists.len() < 1 { | ||
return; | ||
} | ||
|
||
let selected_index = self.selected_index.load(Ordering::Relaxed); | ||
let offset = self.offset.load(Ordering::Relaxed); | ||
|
||
for i in 0..artists.len().min(area.height as usize) { | ||
let artist_index = i + offset; | ||
|
||
if artist_index >= artists.len() { | ||
log::error!("artist index {artist_index} > artists.len() {} offset={offset}", artists.len()); | ||
break; | ||
} | ||
|
||
let artist = artists[artist_index].clone(); | ||
let area = Rect { | ||
y: area.y + i as u16, | ||
height: 1, | ||
..area | ||
}; | ||
|
||
let style = line_style(&self.theme, artist_index, selected_index, true); | ||
let line = ratatui::text::Line::from(artist).style(style); | ||
|
||
line.render_ref(area, buf); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.