use super::{MusicPlayer, Track};
use crate::app::{
interaction::{TrackListKind, TrackPos},
pane::PaneId,
ViewKind,
};
impl MusicPlayer {
pub fn selection_in(&self, pane: PaneId, list: TrackListKind) -> &[usize] {
match list {
TrackListKind::Queue => &self.queue_selected_indices,
TrackListKind::Active => &self.pane(pane).view_data().selection,
TrackListKind::Recent => &self.recent_selected_indices,
}
}
#[inline]
pub fn selection(&self, list: TrackListKind) -> &[usize] {
self.selection_in(self.focused_pane_id, list)
}
fn selection_mut_in(&mut self, pane: PaneId, list: TrackListKind) -> &mut Vec<usize> {
match list {
TrackListKind::Queue => &mut self.queue_selected_indices,
TrackListKind::Active => &mut self.pane_mut(pane).view_data_mut().selection,
TrackListKind::Recent => &mut self.recent_selected_indices,
}
}
pub fn view_tracks_in(&self, pane: PaneId) -> &[Track] {
let vd = self.view_data_in(pane);
match &vd.kind {
ViewKind::Playlist(entry) => self
.playlists
.playlists
.get(entry.index)
.map_or(&[], |p| &p.tracks),
_ => vd.tracks(),
}
}
pub fn select_range_in(&mut self, pane: PaneId, list: TrackListKind, from: usize, to: usize) {
let sel = self.selection_mut_in(pane, list);
sel.clear();
sel.extend(from.min(to)..=from.max(to));
}
pub fn toggle_selection(&mut self, pos: TrackPos) {
let sel = self.selection_mut_in(pos.pane, 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) {
let panes: Vec<PaneId> = self.panes.keys().copied().collect();
for pane in panes {
self.clear_selection_in(pane, TrackListKind::Active);
}
self.clear_selection_for(TrackListKind::Queue);
self.clear_selection_for(TrackListKind::Recent);
}
pub fn clear_selection_in(&mut self, pane: PaneId, list: TrackListKind) {
self.selection_mut_in(pane, list).clear();
}
#[inline]
pub fn clear_selection_for(&mut self, list: TrackListKind) {
let pane = self.focused_pane_id;
self.clear_selection_in(pane, list);
}
pub fn has_selection(&self) -> bool {
!self.selection(TrackListKind::Queue).is_empty()
|| !self.selection(TrackListKind::Recent).is_empty()
|| self
.panes
.keys()
.any(|p| !self.selection_in(*p, TrackListKind::Active).is_empty())
}
pub fn clear_selection_if_touched_in(
&mut self,
pane: PaneId,
indices: &[usize],
list: TrackListKind,
) {
let sel = self.selection_in(pane, list);
if indices.iter().any(|&i| sel.contains(&i)) {
self.clear_selection_in(pane, list);
}
}
#[inline]
pub fn clear_selection_if_touched(&mut self, indices: &[usize], list: TrackListKind) {
let pane = self.focused_pane_id;
self.clear_selection_if_touched_in(pane, indices, list);
}
pub(crate) fn handle_select_all(&mut self) {
let pane = self.focused_pane_id;
let list = self
.drag
.hovered_track()
.filter(|h| h.pane == pane || !h.list.is_main())
.map_or(TrackListKind::Active, |h| h.list);
let count = self.track_count_in(pane, list);
let sel = self.selection_mut_in(pane, list);
sel.clear();
sel.extend(list.first_index()..count);
}
pub fn get_track_ref_at(&self, pos: TrackPos) -> Option<&Track> {
let TrackPos { index, list, pane } = pos;
match list {
TrackListKind::Queue => self.queue.tracks.get(index),
TrackListKind::Active => self.view_tracks_in(pane).get(index),
TrackListKind::Recent => self.queue.recently_played.get(index),
}
}
pub fn get_track_at(&self, pos: TrackPos) -> Option<Track> {
self.get_track_ref_at(pos).cloned()
}
pub fn focused_hovered_track(&self) -> Option<TrackPos> {
let pos = self.drag.hovered_track()?;
if pos.list.is_main() && pos.pane != self.focused_pane_id {
None
} else {
Some(pos)
}
}
pub fn is_track_list_match(&self, pos: TrackPos) -> bool {
match &self.track_list_search {
Some(fs)
if fs.list == pos.list
&& (fs.list != TrackListKind::Active || fs.pane == pos.pane) =>
{
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_in(&self, pane: PaneId, list: TrackListKind) -> usize {
match list {
TrackListKind::Queue => self.queue.tracks.len(),
TrackListKind::Active => self.view_tracks_in(pane).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.reset_test_pane(vec![ViewData::new_search(
String::new(),
crate::providers::ProviderId::YouTube,
crate::providers::SearchScope::Songs,
)]);
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, p.focused_pane_id);
let pos1 = TrackPos::new(1, TrackListKind::Recent, p.focused_pane_id);
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, p.focused_pane_id);
let a = TrackPos::new(0, TrackListKind::Active, p.focused_pane_id);
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());
}
}