обновление
This commit is contained in:
202
src/library.rs
Normal file
202
src/library.rs
Normal file
@@ -0,0 +1,202 @@
|
||||
use crate::track::Track;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum LeftRow {
|
||||
Artist { name: String },
|
||||
Album { name: String },
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum RightRow {
|
||||
Track { index_in_library: usize },
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum SortMode {
|
||||
ByArtist,
|
||||
ByFilename,
|
||||
}
|
||||
|
||||
pub struct Library {
|
||||
pub all_tracks: Vec<Track>,
|
||||
/// Левая панель: артисты и альбомы
|
||||
pub left_rows: Vec<LeftRow>,
|
||||
/// Для каждой строки левой панели — список индексов треков в all_tracks
|
||||
pub left_to_tracks: Vec<Vec<usize>>,
|
||||
/// Индекс выбранной строки левой панели
|
||||
pub left_selected: usize,
|
||||
/// Индекс выбранного трека в правой панели (внутри текущего набора)
|
||||
pub right_selected: usize,
|
||||
pub sort_mode: SortMode,
|
||||
}
|
||||
|
||||
impl Library {
|
||||
pub fn build(mut tracks: Vec<Track>, mode: SortMode) -> Self {
|
||||
match mode {
|
||||
SortMode::ByArtist => {
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
let mut map: BTreeMap<String, BTreeMap<String, Vec<Track>>> = BTreeMap::new();
|
||||
for t in tracks.drain(..) {
|
||||
map.entry(t.artist.clone())
|
||||
.or_default()
|
||||
.entry(t.album.clone())
|
||||
.or_default()
|
||||
.push(t);
|
||||
}
|
||||
|
||||
let mut all_tracks = Vec::new();
|
||||
let mut left_rows = Vec::new();
|
||||
let mut left_to_tracks: Vec<Vec<usize>> = Vec::new();
|
||||
|
||||
for (artist_name, albums_map) in map {
|
||||
// Строка артиста: содержит все треки всех его альбомов
|
||||
let mut artist_track_indices = Vec::new();
|
||||
let mut album_entries = Vec::new();
|
||||
|
||||
for (album_name, mut album_tracks) in albums_map {
|
||||
album_tracks.sort_by(|a, b| {
|
||||
a.track_number
|
||||
.unwrap_or(u32::MAX)
|
||||
.cmp(&b.track_number.unwrap_or(u32::MAX))
|
||||
.then(a.title.cmp(&b.title))
|
||||
});
|
||||
|
||||
let mut album_indices = Vec::new();
|
||||
for t in album_tracks {
|
||||
let idx = all_tracks.len();
|
||||
all_tracks.push(t);
|
||||
album_indices.push(idx);
|
||||
artist_track_indices.push(idx);
|
||||
}
|
||||
album_entries.push((album_name, album_indices));
|
||||
}
|
||||
|
||||
// 1) строка артиста
|
||||
left_rows.push(LeftRow::Artist {
|
||||
name: artist_name,
|
||||
});
|
||||
left_to_tracks.push(artist_track_indices);
|
||||
|
||||
// 2) строки альбомов
|
||||
for (album_name, indices) in album_entries {
|
||||
left_rows.push(LeftRow::Album { name: album_name });
|
||||
left_to_tracks.push(indices);
|
||||
}
|
||||
}
|
||||
|
||||
Library {
|
||||
all_tracks,
|
||||
left_rows,
|
||||
left_to_tracks,
|
||||
left_selected: 0,
|
||||
right_selected: 0,
|
||||
sort_mode: mode,
|
||||
}
|
||||
}
|
||||
|
||||
SortMode::ByFilename => {
|
||||
tracks.sort_by(|a, b| a.path.cmp(&b.path));
|
||||
let all_tracks = tracks;
|
||||
|
||||
let left_rows = vec![LeftRow::Album {
|
||||
name: "Все файлы".into(),
|
||||
}];
|
||||
let left_to_tracks = vec![(0..all_tracks.len()).collect()];
|
||||
|
||||
Library {
|
||||
all_tracks,
|
||||
left_rows,
|
||||
left_to_tracks,
|
||||
left_selected: 0,
|
||||
right_selected: 0,
|
||||
sort_mode: mode,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn track(&self, index: usize) -> Option<&Track> {
|
||||
self.all_tracks.get(index)
|
||||
}
|
||||
|
||||
/// Индексы треков, относящихся к текущей выделенной строке левой панели
|
||||
pub fn current_right_indices(&self) -> &[usize] {
|
||||
self.left_to_tracks
|
||||
.get(self.left_selected)
|
||||
.map(|v| v.as_slice())
|
||||
.unwrap_or(&[])
|
||||
}
|
||||
|
||||
/// Индекс трека в all_tracks, выделенного в правой панели
|
||||
pub fn right_track_index(&self) -> Option<usize> {
|
||||
self.current_right_indices()
|
||||
.get(self.right_selected)
|
||||
.copied()
|
||||
}
|
||||
|
||||
/// Найти строку левой панели, к которой относится трек (для синхронизации)
|
||||
pub fn left_row_of_track(&self, index_in_library: usize) -> Option<usize> {
|
||||
// Приоритет — альбом (более точное совпадение)
|
||||
let mut album_match = None;
|
||||
for (i, row) in self.left_rows.iter().enumerate() {
|
||||
let contains = self
|
||||
.left_to_tracks
|
||||
.get(i)
|
||||
.map(|v| v.contains(&index_in_library))
|
||||
.unwrap_or(false);
|
||||
if contains {
|
||||
match row {
|
||||
LeftRow::Album { .. } => return Some(i),
|
||||
LeftRow::Artist { .. } => album_match = Some(i),
|
||||
}
|
||||
}
|
||||
}
|
||||
album_match
|
||||
}
|
||||
|
||||
/// Передвинуть курсор левой панели вверх/вниз, синхронизировать правую
|
||||
pub fn left_move(&mut self, delta: isize) {
|
||||
if self.left_rows.is_empty() {
|
||||
return;
|
||||
}
|
||||
let len = self.left_rows.len() as isize;
|
||||
let mut new = self.left_selected as isize + delta;
|
||||
if new < 0 {
|
||||
new = len - 1;
|
||||
} else if new >= len {
|
||||
new = 0;
|
||||
}
|
||||
self.left_selected = new as usize;
|
||||
self.right_selected = 0;
|
||||
}
|
||||
|
||||
/// Передвинуть курсор правой панели вверх/вниз
|
||||
pub fn right_move(&mut self, delta: isize) {
|
||||
let len = self.current_right_indices().len() as isize;
|
||||
if len == 0 {
|
||||
return;
|
||||
}
|
||||
let mut new = self.right_selected as isize + delta;
|
||||
if new < 0 {
|
||||
new = len - 1;
|
||||
} else if new >= len {
|
||||
new = 0;
|
||||
}
|
||||
self.right_selected = new as usize;
|
||||
}
|
||||
|
||||
/// Синхронизировать правую панель с играющим треком
|
||||
pub fn sync_with_playing(&mut self, index_in_library: usize) {
|
||||
if let Some(row) = self.left_row_of_track(index_in_library) {
|
||||
self.left_selected = row;
|
||||
if let Some(pos) = self
|
||||
.left_to_tracks
|
||||
.get(row)
|
||||
.and_then(|v| v.iter().position(|&i| i == index_in_library))
|
||||
{
|
||||
self.right_selected = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user