обновление

This commit is contained in:
2026-10-13 21:15:09 +02:00
parent 08a8a7cce5
commit f554de7908
8 changed files with 938 additions and 233 deletions

View File

@@ -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;
}
}
}