обновление
This commit is contained in:
119
src/app.rs
119
src/app.rs
@@ -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(),
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user