use std::path::PathBuf;
use anyhow::Result;
use crate::library::{self, SortKey, Track};
use crate::lyrics::Fetcher;
use crate::matrix::Matrix;
use crate::name::NameBuffer;
use crate::mpris::{self, Mpris};
use crate::player::Audio;
pub const SCROLLOFF: usize = 3;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Mode {
Normal,
Command,
Search,
Visual,
Edit,
EditInsert,
}
impl Mode {
pub fn label(self) -> &'static str {
match self {
Mode::Normal => "NORMAL",
Mode::Command => "COMMAND",
Mode::Search => "SEARCH",
Mode::Visual => "VISUAL",
Mode::Edit => "EDIT",
Mode::EditInsert => "INSERT",
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Tab {
Folders,
Playlists,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Pane {
Folders,
Tracks,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub enum Repeat {
#[default]
Off,
All,
One,
}
impl Repeat {
pub fn next(self) -> Repeat {
match self {
Repeat::Off => Repeat::All,
Repeat::All => Repeat::One,
Repeat::One => Repeat::Off,
}
}
pub fn name(self) -> &'static str {
match self {
Repeat::Off => "off",
Repeat::All => "all",
Repeat::One => "one",
}
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Columns {
pub file: bool,
pub title: bool,
pub artist: bool,
pub album: bool,
}
impl Default for Columns {
fn default() -> Self {
Columns {
file: true,
title: false,
artist: true,
album: true,
}
}
}
impl Columns {
pub fn get(&self, name: &str) -> Option<bool> {
match name {
"file" => Some(self.file),
"title" => Some(self.title),
"artist" => Some(self.artist),
"album" => Some(self.album),
_ => None,
}
}
pub fn set(&mut self, name: &str, on: bool) -> bool {
match name {
"file" => self.file = on,
"title" => self.title = on,
"artist" => self.artist = on,
"album" => self.album = on,
_ => return false,
}
true
}
pub fn shown(self) -> Vec<(&'static str, usize)> {
[
("file", self.file, 40),
("title", self.title, 34),
("artist", self.artist, 26),
("album", self.album, 26),
]
.into_iter()
.filter(|(_, on, _)| *on)
.map(|(name, _, weight)| (name, weight))
.collect()
}
}
#[derive(Clone, Default)]
pub struct Pending {
pub renames: std::collections::BTreeMap<Renaming, String>,
pub playlist_rows: Vec<usize>,
pub playlist_dirty: bool,
pub doomed: Vec<PathBuf>,
pub doomed_files: Vec<PathBuf>,
pub cut: Vec<PathBuf>,
pub moves: Vec<(PathBuf, PathBuf)>,
pub copies: Vec<(PathBuf, PathBuf)>,
pub doomed_dirs: Vec<PathBuf>,
pub sidebar_name: Option<String>,
}
#[derive(Clone)]
pub struct ViewTab {
pub playlist: Option<String>,
pub folder: usize,
pub cur: usize,
pub top: usize,
pub sort_key: SortKey,
rows: Vec<usize>,
dirty: bool,
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum Renaming {
Track(usize),
Folder(PathBuf),
Playlist(String),
}
pub struct NameEdit {
pub buf: NameBuffer,
pub what: Renaming,
}
pub struct App {
pub root: PathBuf,
pub tracks: Vec<Track>,
pub sort_key: SortKey,
pub columns: Columns,
pub music: Option<PathBuf>,
pub show_lyrics: bool,
pub karaoke: bool,
pub matrix: Matrix,
pub lyrics: Fetcher,
pub folders: Vec<(String, PathBuf)>,
pub folder_cur: usize,
pub folder_open: usize,
pub folder_top: usize,
pub folder_h: usize,
pub tab: Tab,
pub playlists: Vec<(String, PathBuf)>,
pub pl_cur: usize,
pub pl_top: usize,
pub playlist_view: Option<String>,
playlist_rows: Vec<usize>,
pub tabs: Vec<ViewTab>,
pub tab_idx: usize,
pub view: Vec<usize>,
pub cur: usize,
pub top: usize,
pub track_h: usize,
pub focus: Pane,
pub mode: Mode,
pub line: String,
pub line_prefix: char,
pub line_cur: usize,
pub line_insert: bool,
pub count: Option<usize>,
pub pending: Option<char>,
pub last_search: String,
pub search_back: bool,
pub visual_anchor: Option<usize>,
pub yank: Vec<PathBuf>,
pub playlist_dirty: bool,
pub doomed: Vec<PathBuf>,
pub danger: bool,
pub doomed_files: Vec<PathBuf>,
pub cut: Vec<PathBuf>,
pub moves: Vec<(PathBuf, PathBuf)>,
pub copies: Vec<(PathBuf, PathBuf)>,
pub doomed_dirs: Vec<PathBuf>,
undo_stack: Vec<Pending>,
redo_stack: Vec<Pending>,
pub edit: Option<NameEdit>,
pub renames: std::collections::BTreeMap<Renaming, String>,
pub playlists_dir: Option<PathBuf>,
pub queue: Vec<usize>,
pub qpos: usize,
history: Vec<usize>,
pub playing: Option<usize>,
pub repeat: Repeat,
pub shuffle: bool,
pub audio: Option<Audio>,
pub mpris: Option<Mpris>,
pub msg: Option<(String, bool)>,
pub cursor_screen: Option<(u16, u16)>,
pub played: Vec<String>,
pub show_history: bool,
pub history_top: usize,
pub show_info: bool,
pub changes_pan: usize,
pub show_changes: bool,
pub show_help: bool,
pub help_scroll: usize,
pub quit: bool,
rng: u64,
}
impl App {
pub fn new(root: PathBuf, sort_key: SortKey, on: library::Report) -> Result<App> {
let (audio, audio_err) = match Audio::new() {
Ok(a) => (Some(a), None),
Err(e) => (None, Some(format!("{e}"))),
};
let mut app = App {
root,
tracks: Vec::new(),
sort_key,
columns: Columns::default(),
music: None,
show_lyrics: false,
karaoke: true,
matrix: Matrix::default(),
lyrics: Fetcher::new(),
folders: Vec::new(),
folder_cur: 0,
folder_open: 0,
folder_top: 0,
folder_h: 1,
tab: Tab::Folders,
playlists: Vec::new(),
pl_cur: 0,
pl_top: 0,
playlist_view: None,
playlist_rows: Vec::new(),
tabs: Vec::new(),
tab_idx: 0,
view: Vec::new(),
cur: 0,
top: 0,
track_h: 1,
focus: Pane::Tracks,
mode: Mode::Normal,
line: String::new(),
line_prefix: ':',
line_cur: 0,
line_insert: true,
count: None,
pending: None,
last_search: String::new(),
search_back: false,
visual_anchor: None,
yank: Vec::new(),
playlist_dirty: false,
doomed: Vec::new(),
danger: false,
doomed_files: Vec::new(),
cut: Vec::new(),
moves: Vec::new(),
copies: Vec::new(),
doomed_dirs: Vec::new(),
undo_stack: Vec::new(),
redo_stack: Vec::new(),
edit: None,
renames: std::collections::BTreeMap::new(),
playlists_dir: None,
queue: Vec::new(),
qpos: 0,
history: Vec::new(),
playing: None,
repeat: Repeat::Off,
shuffle: false,
audio,
mpris: mpris::start().ok(),
msg: None,
cursor_screen: None,
played: Vec::new(),
show_history: false,
history_top: 0,
show_info: false,
changes_pan: 0,
show_changes: false,
show_help: false,
help_scroll: 0,
quit: false,
rng: seed(),
};
app.reload_playlists();
app.reload_reporting(on)?;
app.tabs.push(app.snapshot());
if let Some(e) = audio_err {
app.error(e);
}
Ok(app)
}
pub fn info(&mut self, text: impl Into<String>) {
self.msg = Some((text.into(), false));
}
pub fn error(&mut self, text: impl Into<String>) {
self.msg = Some((text.into(), true));
}
}
fn plural(n: usize) -> &'static str {
if n == 1 { "" } else { "s" }
}
fn seed() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0x2545_F491_4F6C_DD1D, |d| d.as_nanos() as u64)
| 1
}
mod scan;
mod cursor;
mod undo;
mod danger;
mod tabs;
mod select;
mod playlists;
mod rename;
mod search;
mod playback;
#[cfg(test)]
mod tests;