обновление
This commit is contained in:
246
src/library.rs
246
src/library.rs
@@ -1,16 +1,20 @@
|
||||
use ratatui::widgets::ListState;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::track::Track;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum RightRow {
|
||||
/// Заголовок группы: в режиме ByArtist — название альбома,
|
||||
/// в режиме ByAlbum — имя исполнителя.
|
||||
AlbumHeader { name: String },
|
||||
Track { index_in_library: usize },
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
#[derive(Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||
pub enum SortMode {
|
||||
ByArtist,
|
||||
ByAlbum,
|
||||
ByFilename,
|
||||
}
|
||||
|
||||
@@ -25,11 +29,8 @@ pub struct Library {
|
||||
pub artist_selected: usize,
|
||||
pub right_selected: usize,
|
||||
pub sort_mode: SortMode,
|
||||
/// Активный фильтр поиска (пусто — без фильтра)
|
||||
pub filter: String,
|
||||
/// Состояние скролла левой панели (сохраняется между кадрами)
|
||||
pub left_state: ListState,
|
||||
/// Состояние скролла правой панели
|
||||
pub right_state: ListState,
|
||||
}
|
||||
|
||||
@@ -43,7 +44,6 @@ impl Library {
|
||||
mode: SortMode,
|
||||
filter: String,
|
||||
) -> Self {
|
||||
// Применяем фильтр
|
||||
if !filter.is_empty() {
|
||||
let f = filter.to_lowercase();
|
||||
tracks.retain(|t| {
|
||||
@@ -54,108 +54,165 @@ impl Library {
|
||||
}
|
||||
|
||||
match mode {
|
||||
SortMode::ByArtist => {
|
||||
use std::collections::BTreeMap;
|
||||
SortMode::ByArtist => Self::build_by_artist(tracks, mode, filter),
|
||||
SortMode::ByAlbum => Self::build_by_album(tracks, mode, filter),
|
||||
SortMode::ByFilename => Self::build_by_filename(tracks, mode, filter),
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
fn build_by_artist(
|
||||
mut tracks: Vec<Track>,
|
||||
mode: SortMode,
|
||||
filter: String,
|
||||
) -> Self {
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
let mut all_tracks = Vec::new();
|
||||
let mut artists = Vec::new();
|
||||
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);
|
||||
}
|
||||
|
||||
for (artist_name, albums_map) in map {
|
||||
let mut rows = Vec::new();
|
||||
let mut all_tracks = Vec::new();
|
||||
let mut artists = 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))
|
||||
});
|
||||
for (artist_name, albums_map) in map {
|
||||
let mut rows = Vec::new();
|
||||
|
||||
rows.push(RightRow::AlbumHeader { name: album_name });
|
||||
|
||||
for t in album_tracks {
|
||||
let idx = all_tracks.len();
|
||||
all_tracks.push(t);
|
||||
rows.push(RightRow::Track {
|
||||
index_in_library: idx,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
artists.push(ArtistView {
|
||||
name: artist_name,
|
||||
rows,
|
||||
});
|
||||
}
|
||||
|
||||
// Вычисляем right_selected ДО перемещения artists
|
||||
let right_selected = first_track_row_in(artists.first());
|
||||
|
||||
let mut left_state = ListState::default();
|
||||
let mut right_state = ListState::default();
|
||||
if !artists.is_empty() {
|
||||
left_state.select(Some(0));
|
||||
}
|
||||
right_state.select(Some(right_selected));
|
||||
|
||||
Library {
|
||||
all_tracks,
|
||||
artists,
|
||||
artist_selected: 0,
|
||||
right_selected,
|
||||
sort_mode: mode,
|
||||
filter,
|
||||
left_state,
|
||||
right_state,
|
||||
}
|
||||
}
|
||||
|
||||
SortMode::ByFilename => {
|
||||
tracks.sort_by(|a, b| a.path.cmp(&b.path));
|
||||
let all_tracks = tracks;
|
||||
|
||||
let mut rows = Vec::new();
|
||||
rows.push(RightRow::AlbumHeader {
|
||||
name: "Все файлы".into(),
|
||||
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))
|
||||
});
|
||||
for i in 0..all_tracks.len() {
|
||||
|
||||
rows.push(RightRow::AlbumHeader { name: album_name });
|
||||
|
||||
for t in album_tracks {
|
||||
let idx = all_tracks.len();
|
||||
all_tracks.push(t);
|
||||
rows.push(RightRow::Track {
|
||||
index_in_library: i,
|
||||
index_in_library: idx,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let artists = vec![ArtistView {
|
||||
name: "Все файлы".into(),
|
||||
rows,
|
||||
}];
|
||||
artists.push(ArtistView {
|
||||
name: artist_name,
|
||||
rows,
|
||||
});
|
||||
}
|
||||
|
||||
let right_selected = first_track_row_in(artists.first());
|
||||
Self::finalize(all_tracks, artists, mode, filter)
|
||||
}
|
||||
|
||||
let mut left_state = ListState::default();
|
||||
left_state.select(Some(0));
|
||||
let mut right_state = ListState::default();
|
||||
right_state.select(Some(right_selected));
|
||||
fn build_by_album(
|
||||
mut tracks: Vec<Track>,
|
||||
mode: SortMode,
|
||||
filter: String,
|
||||
) -> Self {
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
Library {
|
||||
all_tracks,
|
||||
artists,
|
||||
artist_selected: 0,
|
||||
right_selected,
|
||||
sort_mode: mode,
|
||||
filter,
|
||||
left_state,
|
||||
right_state,
|
||||
// Album -> Artist -> Vec<Track>
|
||||
let mut map: BTreeMap<String, BTreeMap<String, Vec<Track>>> = BTreeMap::new();
|
||||
for t in tracks.drain(..) {
|
||||
map.entry(t.album.clone())
|
||||
.or_default()
|
||||
.entry(t.artist.clone())
|
||||
.or_default()
|
||||
.push(t);
|
||||
}
|
||||
|
||||
let mut all_tracks = Vec::new();
|
||||
let mut albums = Vec::new();
|
||||
|
||||
for (album_name, artists_map) in map {
|
||||
let mut rows = Vec::new();
|
||||
|
||||
for (artist_name, mut artist_tracks) in artists_map {
|
||||
artist_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))
|
||||
});
|
||||
|
||||
// Заголовок группы = имя исполнителя
|
||||
rows.push(RightRow::AlbumHeader { name: artist_name });
|
||||
|
||||
for t in artist_tracks {
|
||||
let idx = all_tracks.len();
|
||||
all_tracks.push(t);
|
||||
rows.push(RightRow::Track {
|
||||
index_in_library: idx,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
albums.push(ArtistView {
|
||||
name: album_name,
|
||||
rows,
|
||||
});
|
||||
}
|
||||
|
||||
Self::finalize(all_tracks, albums, mode, filter)
|
||||
}
|
||||
|
||||
fn build_by_filename(
|
||||
mut tracks: Vec<Track>,
|
||||
mode: SortMode,
|
||||
filter: String,
|
||||
) -> Self {
|
||||
tracks.sort_by(|a, b| a.path.cmp(&b.path));
|
||||
let all_tracks = tracks;
|
||||
|
||||
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 groups = vec![ArtistView {
|
||||
name: "Все файлы".into(),
|
||||
rows,
|
||||
}];
|
||||
|
||||
Self::finalize(all_tracks, groups, mode, filter)
|
||||
}
|
||||
|
||||
/// Общая инициализация ListState и позиций курсора.
|
||||
fn finalize(
|
||||
all_tracks: Vec<Track>,
|
||||
groups: Vec<ArtistView>,
|
||||
mode: SortMode,
|
||||
filter: String,
|
||||
) -> Self {
|
||||
let right_selected = first_track_row_in(groups.first());
|
||||
|
||||
let mut left_state = ListState::default();
|
||||
let mut right_state = ListState::default();
|
||||
if !groups.is_empty() {
|
||||
left_state.select(Some(0));
|
||||
}
|
||||
right_state.select(Some(right_selected));
|
||||
|
||||
Library {
|
||||
all_tracks,
|
||||
artists: groups,
|
||||
artist_selected: 0,
|
||||
right_selected,
|
||||
sort_mode: mode,
|
||||
filter,
|
||||
left_state,
|
||||
right_state,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,7 +248,6 @@ impl Library {
|
||||
self.artist_selected = new as usize;
|
||||
self.right_selected = self.first_track_row();
|
||||
|
||||
// Синхронизируем ListState — иначе ratatui не знает о смене позиции
|
||||
self.left_state.select(Some(self.artist_selected));
|
||||
self.right_state.select(Some(self.right_selected));
|
||||
}
|
||||
@@ -255,8 +311,8 @@ impl Library {
|
||||
}
|
||||
}
|
||||
|
||||
fn first_track_row_in(artist: Option<&ArtistView>) -> usize {
|
||||
artist
|
||||
fn first_track_row_in(group: Option<&ArtistView>) -> usize {
|
||||
group
|
||||
.and_then(|a| {
|
||||
a.rows
|
||||
.iter()
|
||||
|
||||
Reference in New Issue
Block a user