use super::{MusicPlayer, Track};
use crate::app::{
interaction::{TrackListKind, TrackPos},
ViewKind,
};
impl MusicPlayer {
pub fn selection(&self, list: TrackListKind) -> &[usize] {
match list {
TrackListKind::Queue => &self.queue_selected_indices,
TrackListKind::Active => &self.view_data().selection,
TrackListKind::Recent => &self.recent_selected_indices,
}
}
fn selection_mut(&mut self, list: TrackListKind) -> &mut Vec<usize> {
match list {
TrackListKind::Queue => &mut self.queue_selected_indices,
TrackListKind::Active => &mut self.view_data_mut().selection,
TrackListKind::Recent => &mut self.recent_selected_indices,
}
}
pub fn view_tracks(&self) -> &[Track] {
let vd = self.view_data();
match &vd.kind {
ViewKind::Playlist(entry) => self
.playlists
.playlists
.get(entry.index)
.map_or(&[], |p| &p.tracks),
_ => vd.tracks(),
}
}
pub fn toggle_selection(&mut self, pos: TrackPos) {
let sel = self.selection_mut(pos.list);
if let Some(at) = sel.iter().position(|&i| i == pos.index) {
sel.remove(at);
} else {
sel.push(pos.index);
}
}
pub fn clear_selection(&mut self) {
self.clear_selection_for(TrackListKind::Active);
self.clear_selection_for(TrackListKind::Queue);
self.clear_selection_for(TrackListKind::Recent);
self.playlist_picker = None;
}
pub fn clear_selection_for(&mut self, list: TrackListKind) {
self.selection_mut(list).clear();
self.playlist_picker = None;
}
pub fn has_selection(&self) -> bool {
!self.selection(TrackListKind::Active).is_empty()
|| !self.selection(TrackListKind::Queue).is_empty()
|| !self.selection(TrackListKind::Recent).is_empty()
}
pub fn clear_selection_if_touched(&mut self, indices: &[usize], list: TrackListKind) {
let sel = self.selection(list);
if indices.iter().any(|&i| sel.contains(&i)) {
self.clear_selection();
}
}
pub(crate) fn handle_select_all(&mut self) {
let list = self
.drag
.hovered_track()
.map_or(TrackListKind::Active, |h| h.list);
let count = self.track_count(list);
let sel = self.selection_mut(list);
sel.clear();
sel.extend(0..count);
}
pub fn get_track_at(&self, pos: TrackPos) -> Option<Track> {
let TrackPos { index, list } = pos;
match list {
TrackListKind::Queue => self.queue.tracks.get(index).cloned(),
TrackListKind::Active => self.view_tracks().get(index).cloned(),
TrackListKind::Recent => self.queue.recently_played.get(index).cloned(),
}
}
pub fn is_track_list_match(&self, pos: TrackPos) -> bool {
match &self.track_list_search {
Some(fs) if fs.list == pos.list => fs.matches.contains(&pos.index),
_ => false,
}
}
pub fn track_list_match_position(&self) -> Option<usize> {
let current_idx = self.drag.hovered_track()?.index;
self.track_list_search
.as_ref()?
.matches
.iter()
.position(|&i| i == current_idx)
.map(|p| p + 1)
}
pub fn track_count(&self, list: TrackListKind) -> usize {
match list {
TrackListKind::Queue => self.queue.tracks.len(),
TrackListKind::Active => self.view_tracks().len(),
TrackListKind::Recent => self.queue.recently_played.len(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{app::view_data::ViewData, data::config, types::Track};
fn player() -> MusicPlayer {
let mut p = MusicPlayer::new_with(config::Config::default());
p.nav_history = vec![ViewData::new_search(
String::new(),
crate::providers::ProviderId::YouTube,
crate::providers::SearchScope::Songs,
)];
p.nav_history_pos = 0;
p
}
fn track(id: &str) -> Track {
Track::from_provider(
crate::providers::ProviderId::YouTube,
id.into(),
format!("https://example.com/{id}"),
format!("Track {id}"),
"Artist",
10,
String::new(),
None,
None,
)
}
#[test]
fn recent_selection_toggles_like_other_lists() {
let mut p = player();
p.queue.recently_played.push_back(track("1"));
p.queue.recently_played.push_back(track("2"));
let pos0 = TrackPos::new(0, TrackListKind::Recent);
let pos1 = TrackPos::new(1, TrackListKind::Recent);
assert!(p.selection(TrackListKind::Recent).is_empty());
p.toggle_selection(pos0);
assert_eq!(p.selection(TrackListKind::Recent), &[0]);
p.toggle_selection(pos1);
assert_eq!(p.selection(TrackListKind::Recent), &[0, 1]);
p.toggle_selection(pos0);
assert_eq!(p.selection(TrackListKind::Recent), &[1]);
p.clear_selection_for(TrackListKind::Recent);
assert!(p.selection(TrackListKind::Recent).is_empty());
p.clear_selection();
assert!(p.selection(TrackListKind::Recent).is_empty());
}
#[test]
fn toggle_adds_then_removes_per_list() {
let mut p = player();
p.queue.tracks = vec![track("1"), track("2")];
p.view_data_mut().set_tracks(vec![track("1")]);
let q = TrackPos::new(0, TrackListKind::Queue);
let a = TrackPos::new(0, TrackListKind::Active);
p.toggle_selection(q);
p.toggle_selection(a);
assert_eq!(p.selection(TrackListKind::Queue), &[0]);
assert_eq!(p.selection(TrackListKind::Active), &[0]);
assert_eq!(p.get_track_at(q).map(|t| t.title), Some("Track 1".into()));
assert_eq!(p.get_track_at(a).map(|t| t.title), Some("Track 1".into()));
p.toggle_selection(q);
assert!(p.selection(TrackListKind::Queue).is_empty());
assert_eq!(p.selection(TrackListKind::Active), &[0]);
p.clear_selection();
assert!(p.selection(TrackListKind::Active).is_empty());
}
}