mod components;
pub mod model;
use crate::config::Termusic;
use crate::songtag::SongTag;
use model::Model;
use serde::{Deserialize, Serialize};
use std::time::Duration;
use tuirealm::application::PollStrategy;
use tuirealm::{Application, Update};
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
const FORCED_REDRAW_INTERVAL: Duration = Duration::from_millis(1000);
#[derive(Debug, PartialEq)]
pub enum Msg {
DeleteConfirmCloseCancel,
DeleteConfirmCloseOk,
DeleteConfirmShow,
ErrorPopupClose,
HelpPopupShow,
HelpPopupClose,
LibraryTreeExtendDir(String),
LibraryTreeGoToUpperDir,
LibraryTreeBlur,
LibraryYank,
LibraryPaste,
LibrarySearchPopupShow,
LibrarySearchPopupCloseCancel,
LibrarySearchInputBlur,
LibrarySearchPopupUpdate(String),
LibrarySearchTableBlur,
LibrarySearchPopupCloseAddPlaylist,
LibrarySearchPopupCloseOkLocate,
LyricCycle,
LyricAdjustDelay(i64),
PlayerTogglePause,
PlayerVolumeUp,
PlayerVolumeDown,
PlayerSeek(isize),
PlaylistAddFront,
PlaylistNextSong,
PlaylistPrevSong,
PlaylistTableBlur,
PlaylistAdd(String),
PlaylistAddSongs(String),
PlaylistDelete(usize),
PlaylistDeleteAll,
PlaylistLoopModeCycle,
PlaylistPlaySelected(usize),
PlaylistShuffle,
QuitPopupCloseCancel,
QuitPopupCloseOk,
QuitPopupShow,
TagEditorRun(String),
TagEditorBlur(Option<String>),
TECounterDeleteBlur,
TECounterDeleteOk,
TEDownload(usize),
TEEmbed(usize),
TEHelpPopupShow,
TEHelpPopupClose,
TEInputArtistBlur,
TEInputTitleBlur,
TERadioTagBlur,
TERadioTagOk,
TESearch,
TESelectLyricBlur,
TESelectLyricOk(usize),
TETableLyricOptionsBlur,
TETextareaLyricBlur,
YoutubeSearchInputPopupShow,
YoutubeSearchInputPopupCloseCancel,
YoutubeSearchInputPopupCloseOk(String),
YoutubeSearchTablePopupNext,
YoutubeSearchTablePopupPrevious,
YoutubeSearchTablePopupCloseCancel,
YoutubeSearchTablePopupCloseOk(usize),
None,
}
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
pub enum Id {
DeleteConfirmRadioPopup,
DeleteConfirmInputPopup,
ErrorPopup,
GlobalListener,
HelpPopup,
Label,
Library,
LibrarySearchInput,
LibrarySearchTable,
Lyric,
MessagePopup,
Playlist,
Progress,
QuitPopup,
TECounterDelete,
TEHelpPopup,
TELabelHint,
TEInputArtist,
TEInputTitle,
TERadioTag,
TESelectLyric,
TETableLyricOptions,
TETextareaLyric,
YoutubeSearchInputPopup,
YoutubeSearchTablePopup,
}
#[derive(Clone, Copy)]
pub enum Status {
Running,
Stopped,
Paused,
}
#[derive(Copy, Clone)]
pub enum StatusLine {
Default,
Success,
Running,
Error,
}
#[derive(Clone, Deserialize, Serialize)]
pub enum Loop {
Single,
Playlist,
Queue,
}
pub enum SearchLyricState {
Finish(Vec<SongTag>),
}
impl std::fmt::Display for Loop {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let loop_state = match self {
Self::Single => "single",
Self::Playlist => "playlist",
Self::Queue => "consume",
};
write!(f, "{}", loop_state)
}
}
pub struct UI {
model: Model,
}
impl UI {
pub fn new(config: &Termusic) -> Self {
let mut model = Model::new(config);
model.init_config();
model.library_reload_tree();
Self { model }
}
pub fn run(&mut self) {
self.model.init_terminal();
self.model.playlist_load().ok();
let mut progress_interval = 0;
while !self.model.quit {
#[cfg(feature = "mpris")]
self.model.update_mpris();
self.model.te_update_lyric_options();
self.model.update_playlist_items();
self.model.update_components();
self.model.progress_update();
self.model.update_lyric();
if progress_interval == 0 {
self.model.run();
}
progress_interval += 1;
if progress_interval >= 8 {
progress_interval = 0;
}
match self.model.app.tick(PollStrategy::Once) {
Err(err) => {
self.model
.mount_error_popup(format!("Application error: {}", err).as_str());
}
Ok(messages) if !messages.is_empty() => {
self.model.redraw = true;
for msg in messages {
let mut msg = Some(msg);
while msg.is_some() {
msg = self.model.update(msg);
}
}
}
_ => {}
}
self.check_force_redraw();
self.model.view();
}
assert!(self.model.playlist_save().is_ok());
assert!(self.model.config.save().is_ok());
assert!(self.model.clear_photo().is_ok());
self.model.finalize_terminal();
}
fn check_force_redraw(&mut self) {
if let Some(Status::Running) = self.model.status {
if self.model.since_last_redraw() >= FORCED_REDRAW_INTERVAL {
self.model.force_redraw();
}
}
}
}