обновление

This commit is contained in:
2026-10-13 17:38:18 +02:00
parent 255a99ed0f
commit 08a8a7cce5
6 changed files with 502 additions and 87 deletions

View File

@@ -1,68 +1,80 @@
use std::time::Duration;
use ratatui::widgets::ListState;
use crate::audio::AudioPlayer;
use crate::track::{scan_dir, Track};
use crate::library::{Library, SortMode};
use crate::track::scan_dir;
/// Какая панель активна
#[derive(Clone, Copy, PartialEq)]
pub enum Panel {
Left,
Right,
}
pub struct App {
pub tracks: Vec<Track>,
pub list_state: ListState,
pub library: Library,
pub player: AudioPlayer,
pub status: String,
pub playing_index: Option<usize>,
pub active_panel: Panel,
}
impl App {
pub fn new(music_dir: &str) -> Result<Self, Box<dyn std::error::Error>> {
let tracks = scan_dir(music_dir);
let mut list_state = ListState::default();
if !tracks.is_empty() {
list_state.select(Some(0));
}
let library = Library::build(tracks, SortMode::ByArtist);
Ok(Self {
tracks,
list_state,
library,
player: AudioPlayer::new()?,
status: "Нажмите Enter для воспроизведения".into(),
status: "Enter — play, t — сортировка, Tab — панель".into(),
playing_index: None,
active_panel: Panel::Left,
})
}
pub fn next(&mut self) {
if self.tracks.is_empty() { return; }
let i = match self.list_state.selected() {
Some(i) if i >= self.tracks.len() - 1 => 0,
Some(i) => i + 1,
None => 0,
};
self.list_state.select(Some(i));
/// Навигация вверх с учётом активной панели
pub fn up(&mut self) {
match self.active_panel {
Panel::Left => self.library.left_move(-1),
Panel::Right => self.library.right_move(-1),
}
}
pub fn prev(&mut self) {
if self.tracks.is_empty() { return; }
let i = match self.list_state.selected() {
Some(0) => self.tracks.len() - 1,
Some(i) => i - 1,
None => 0,
};
self.list_state.select(Some(i));
pub fn down(&mut self) {
match self.active_panel {
Panel::Left => self.library.left_move(1),
Panel::Right => self.library.right_move(1),
}
}
pub fn play_index(&mut self, index: usize) {
let Some(track) = self.tracks.get(index) else { return; };
pub fn toggle_panel(&mut self) {
self.active_panel = match self.active_panel {
Panel::Left => Panel::Right,
Panel::Right => Panel::Left,
};
self.status = match self.active_panel {
Panel::Left => "Активна: левая панель".into(),
Panel::Right => "Активна: правая панель".into(),
};
}
pub fn play_index(&mut self, index_in_library: usize) {
let Some(track) = self.library.track(index_in_library).cloned() else {
return;
};
match self.player.play(&track.path) {
Ok(_) => {
self.status = format!("▶ {} — {}", track.artist, track.title);
self.playing_index = Some(index);
self.playing_index = Some(index_in_library);
}
Err(e) => self.status = format!("Ошибка: {}", e),
}
}
pub fn play_selected(&mut self) {
if let Some(i) = self.list_state.selected() {
self.play_index(i);
if let Some(idx) = self.library.right_track_index() {
self.play_index(idx);
}
}
@@ -81,11 +93,11 @@ impl App {
return;
}
let next = match self.playing_index {
Some(i) if i + 1 < self.tracks.len() => i + 1,
Some(i) if i + 1 < self.library.all_tracks.len() => i + 1,
_ => 0,
};
self.play_index(next);
self.list_state.select(Some(next));
self.library.sync_with_playing(next);
}
pub fn volume_up(&mut self) {
@@ -105,7 +117,7 @@ impl App {
}
let total = self
.playing_index
.and_then(|i| self.tracks.get(i))
.and_then(|i| self.library.track(i))
.map(|t| t.duration)
.unwrap_or(Duration::ZERO);
@@ -114,20 +126,47 @@ impl App {
let sign = if delta_secs >= 0 { "+" } else { "" };
self.status = format!(
"Перемотка {}{}с → {:02}:{:02}",
sign, delta_secs,
sign,
delta_secs,
new_pos.as_secs() / 60,
new_pos.as_secs() % 60,
);
}
Err(_) => self.status = "Перемотка недоступна для этого формата".into(),
Err(_) => self.status = "Перемотка недоступна".into(),
}
}
/// Текущая позиция и длина играющего трека (для прогресс-бара)
pub fn progress(&self) -> (Duration, Duration) {
match self.playing_index.and_then(|i| self.tracks.get(i)) {
match self.playing_index.and_then(|i| self.library.track(i)) {
Some(t) => (self.player.position(), t.duration),
None => (Duration::ZERO, Duration::ZERO),
}
}
pub fn toggle_sort(&mut self) {
let new_mode = match self.library.sort_mode {
SortMode::ByArtist => SortMode::ByFilename,
SortMode::ByFilename => SortMode::ByArtist,
};
let playing_path = self
.playing_index
.and_then(|i| self.library.track(i))
.map(|t| t.path.clone());
let all = self.library.all_tracks.clone();
self.library = Library::build(all, new_mode);
self.playing_index = playing_path
.and_then(|p| self.library.all_tracks.iter().position(|t| t.path == p));
if let Some(i) = self.playing_index {
self.library.sync_with_playing(i);
}
self.status = match new_mode {
SortMode::ByArtist => "Сортировка: по артисту/альбому".into(),
SortMode::ByFilename => "Сортировка: по имени файла".into(),
};
}
}