обновление
This commit is contained in:
204
src/library.rs
204
src/library.rs
@@ -1,13 +1,8 @@
|
||||
use crate::track::Track;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum LeftRow {
|
||||
Artist { name: String },
|
||||
Album { name: String },
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum RightRow {
|
||||
AlbumHeader { name: String },
|
||||
Track { index_in_library: usize },
|
||||
}
|
||||
|
||||
@@ -17,21 +12,41 @@ pub enum SortMode {
|
||||
ByFilename,
|
||||
}
|
||||
|
||||
pub struct ArtistView {
|
||||
pub name: String,
|
||||
pub rows: Vec<RightRow>,
|
||||
}
|
||||
|
||||
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 artists: Vec<ArtistView>,
|
||||
pub artist_selected: usize,
|
||||
pub right_selected: usize,
|
||||
pub sort_mode: SortMode,
|
||||
/// Активный фильтр поиска (пусто — без фильтра)
|
||||
pub filter: String,
|
||||
}
|
||||
|
||||
impl Library {
|
||||
pub fn build(mut tracks: Vec<Track>, mode: SortMode) -> Self {
|
||||
pub fn build(tracks: Vec<Track>, mode: SortMode) -> Self {
|
||||
Self::build_with_filter(tracks, mode, String::new())
|
||||
}
|
||||
|
||||
pub fn build_with_filter(
|
||||
mut tracks: Vec<Track>,
|
||||
mode: SortMode,
|
||||
filter: String,
|
||||
) -> Self {
|
||||
// Применяем фильтр
|
||||
if !filter.is_empty() {
|
||||
let f = filter.to_lowercase();
|
||||
tracks.retain(|t| {
|
||||
t.title.to_lowercase().contains(&f)
|
||||
|| t.artist.to_lowercase().contains(&f)
|
||||
|| t.album.to_lowercase().contains(&f)
|
||||
});
|
||||
}
|
||||
|
||||
match mode {
|
||||
SortMode::ByArtist => {
|
||||
use std::collections::BTreeMap;
|
||||
@@ -46,13 +61,10 @@ impl Library {
|
||||
}
|
||||
|
||||
let mut all_tracks = Vec::new();
|
||||
let mut left_rows = Vec::new();
|
||||
let mut left_to_tracks: Vec<Vec<usize>> = Vec::new();
|
||||
let mut artists = Vec::new();
|
||||
|
||||
for (artist_name, albums_map) in map {
|
||||
// Строка артиста: содержит все треки всех его альбомов
|
||||
let mut artist_track_indices = Vec::new();
|
||||
let mut album_entries = Vec::new();
|
||||
let mut rows = Vec::new();
|
||||
|
||||
for (album_name, mut album_tracks) in albums_map {
|
||||
album_tracks.sort_by(|a, b| {
|
||||
@@ -62,36 +74,30 @@ impl Library {
|
||||
.then(a.title.cmp(&b.title))
|
||||
});
|
||||
|
||||
let mut album_indices = Vec::new();
|
||||
rows.push(RightRow::AlbumHeader { name: album_name });
|
||||
|
||||
for t in album_tracks {
|
||||
let idx = all_tracks.len();
|
||||
all_tracks.push(t);
|
||||
album_indices.push(idx);
|
||||
artist_track_indices.push(idx);
|
||||
rows.push(RightRow::Track {
|
||||
index_in_library: idx,
|
||||
});
|
||||
}
|
||||
album_entries.push((album_name, album_indices));
|
||||
}
|
||||
|
||||
// 1) строка артиста
|
||||
left_rows.push(LeftRow::Artist {
|
||||
artists.push(ArtistView {
|
||||
name: artist_name,
|
||||
rows,
|
||||
});
|
||||
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,
|
||||
artists,
|
||||
artist_selected: 0,
|
||||
right_selected: 0,
|
||||
sort_mode: mode,
|
||||
filter,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,18 +105,28 @@ impl Library {
|
||||
tracks.sort_by(|a, b| a.path.cmp(&b.path));
|
||||
let all_tracks = tracks;
|
||||
|
||||
let left_rows = vec![LeftRow::Album {
|
||||
let mut rows = Vec::new();
|
||||
rows.push(RightRow::AlbumHeader {
|
||||
name: "Все файлы".into(),
|
||||
});
|
||||
for i in 0..all_tracks.len() {
|
||||
rows.push(RightRow::Track {
|
||||
index_in_library: i,
|
||||
});
|
||||
}
|
||||
|
||||
let artists = vec![ArtistView {
|
||||
name: "Все файлы".into(),
|
||||
rows,
|
||||
}];
|
||||
let left_to_tracks = vec![(0..all_tracks.len()).collect()];
|
||||
|
||||
Library {
|
||||
all_tracks,
|
||||
left_rows,
|
||||
left_to_tracks,
|
||||
left_selected: 0,
|
||||
artists,
|
||||
artist_selected: 0,
|
||||
right_selected: 0,
|
||||
sort_mode: mode,
|
||||
filter,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -120,82 +136,86 @@ impl Library {
|
||||
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())
|
||||
pub fn current_right_rows(&self) -> &[RightRow] {
|
||||
self.artists
|
||||
.get(self.artist_selected)
|
||||
.map(|a| a.rows.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),
|
||||
}
|
||||
}
|
||||
match self.current_right_rows().get(self.right_selected)? {
|
||||
RightRow::Track { index_in_library } => Some(*index_in_library),
|
||||
RightRow::AlbumHeader { .. } => None,
|
||||
}
|
||||
album_match
|
||||
}
|
||||
|
||||
/// Передвинуть курсор левой панели вверх/вниз, синхронизировать правую
|
||||
pub fn left_move(&mut self, delta: isize) {
|
||||
if self.left_rows.is_empty() {
|
||||
pub fn artist_move(&mut self, delta: isize) {
|
||||
if self.artists.is_empty() {
|
||||
return;
|
||||
}
|
||||
let len = self.left_rows.len() as isize;
|
||||
let mut new = self.left_selected as isize + delta;
|
||||
let len = self.artists.len() as isize;
|
||||
let mut new = self.artist_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;
|
||||
self.artist_selected = new as usize;
|
||||
self.right_selected = self.first_track_row();
|
||||
}
|
||||
|
||||
/// Передвинуть курсор правой панели вверх/вниз
|
||||
pub fn right_move(&mut self, delta: isize) {
|
||||
let len = self.current_right_indices().len() as isize;
|
||||
if len == 0 {
|
||||
let track_rows: Vec<usize> = self
|
||||
.current_right_rows()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, r)| matches!(r, RightRow::Track { .. }).then_some(i))
|
||||
.collect();
|
||||
|
||||
if track_rows.is_empty() {
|
||||
return;
|
||||
}
|
||||
let mut new = self.right_selected as isize + delta;
|
||||
if new < 0 {
|
||||
new = len - 1;
|
||||
} else if new >= len {
|
||||
new = 0;
|
||||
|
||||
let current_pos = track_rows
|
||||
.iter()
|
||||
.position(|&i| i == self.right_selected)
|
||||
.unwrap_or(0) as isize;
|
||||
|
||||
let len = track_rows.len() as isize;
|
||||
let mut new_pos = current_pos + delta;
|
||||
if new_pos < 0 {
|
||||
new_pos = len - 1;
|
||||
} else if new_pos >= len {
|
||||
new_pos = 0;
|
||||
}
|
||||
self.right_selected = new as usize;
|
||||
|
||||
self.right_selected = track_rows[new_pos as usize];
|
||||
}
|
||||
|
||||
pub fn right_goto_start(&mut self) {
|
||||
self.right_move(-1000000);
|
||||
}
|
||||
|
||||
pub fn right_goto_end(&mut self) {
|
||||
self.right_move(1000000);
|
||||
}
|
||||
|
||||
fn first_track_row(&self) -> usize {
|
||||
self.current_right_rows()
|
||||
.iter()
|
||||
.position(|r| matches!(r, RightRow::Track { .. }))
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
/// Синхронизировать правую панель с играющим треком
|
||||
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))
|
||||
{
|
||||
for (ai, artist) in self.artists.iter().enumerate() {
|
||||
if let Some(pos) = artist.rows.iter().position(|r| {
|
||||
matches!(r, RightRow::Track { index_in_library: i } if *i == index_in_library)
|
||||
}) {
|
||||
self.artist_selected = ai;
|
||||
self.right_selected = pos;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user