mod label;
mod library_search;
mod lyric;
mod music_library;
mod playlist;
mod popups;
mod progress;
mod tageditor;
mod youtube_search;
pub use label::Label;
pub use library_search::{LSInputPopup, LSTablePopup};
pub use lyric::Lyric;
pub use music_library::MusicLibrary;
pub use playlist::Playlist;
pub use popups::{
DeleteConfirmInputPopup, DeleteConfirmRadioPopup, ErrorPopup, HelpPopup, MessagePopup,
QuitPopup,
};
pub use progress::Progress;
pub use youtube_search::{YSInputPopup, YSTablePopup};
pub use tageditor::{
TECounterDelete, TEHelpPopup, TEInputArtist, TEInputTitle, TERadioTag, TESelectLyric,
TETableLyricOptions, TETextareaLyric,
};
use crate::ui::{Id, Loop, Model, Msg, Status};
use tui_realm_stdlib::Phantom;
use tuirealm::props::{Alignment, Borders, Color, Style};
use tuirealm::tui::layout::{Constraint, Direction, Layout, Rect};
use tuirealm::tui::widgets::Block;
use tuirealm::{
event::{Key, KeyEvent, KeyModifiers},
Component, Event, MockComponent, NoUserEvent,
};
use tuirealm::{Sub, SubClause, SubEventClause};
#[derive(Default, MockComponent)]
pub struct GlobalListener {
component: Phantom,
}
impl Component<Msg, NoUserEvent> for GlobalListener {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent {
code: Key::Esc | Key::Char('q'),
..
}) => Some(Msg::QuitPopupShow),
Event::Keyboard(KeyEvent {
code: Key::Char(' '),
..
}) => Some(Msg::PlayerTogglePause),
Event::Keyboard(KeyEvent {
code: Key::Char('n'),
..
}) => Some(Msg::PlaylistNextSong),
Event::Keyboard(KeyEvent {
code: Key::Char('N'),
modifiers: KeyModifiers::SHIFT,
}) => Some(Msg::PlaylistPrevSong),
Event::Keyboard(
KeyEvent {
code: Key::Char('-'),
..
}
| KeyEvent {
code: Key::Char('_'),
modifiers: KeyModifiers::SHIFT,
},
) => Some(Msg::PlayerVolumeDown),
Event::Keyboard(
KeyEvent {
code: Key::Char('='),
..
}
| KeyEvent {
code: Key::Char('+'),
modifiers: KeyModifiers::SHIFT,
},
) => Some(Msg::PlayerVolumeUp),
Event::Keyboard(KeyEvent {
code: Key::Char('h'),
modifiers: KeyModifiers::CONTROL,
}) => Some(Msg::HelpPopupShow),
Event::Keyboard(KeyEvent {
code: Key::Char('f'),
modifiers: KeyModifiers::NONE,
}) => Some(Msg::PlayerSeek(5)),
Event::Keyboard(KeyEvent {
code: Key::Char('b'),
modifiers: KeyModifiers::NONE,
}) => Some(Msg::PlayerSeek(-5)),
Event::Keyboard(KeyEvent {
code: Key::Char('F'),
modifiers: KeyModifiers::SHIFT,
}) => Some(Msg::LyricAdjustDelay(1000)),
Event::Keyboard(KeyEvent {
code: Key::Char('B'),
modifiers: KeyModifiers::SHIFT,
}) => Some(Msg::LyricAdjustDelay(-1000)),
Event::Keyboard(KeyEvent {
code: Key::Char('T'),
modifiers: KeyModifiers::SHIFT,
}) => Some(Msg::LyricCycle),
_ => None,
}
}
}
impl Model {
#[allow(clippy::too_many_lines)]
pub fn subscribe() -> Vec<Sub<Id, NoUserEvent>> {
vec![
Sub::new(
SubEventClause::Keyboard(KeyEvent {
code: Key::Esc,
modifiers: KeyModifiers::NONE,
}),
SubClause::Always,
),
Sub::new(
SubEventClause::Keyboard(KeyEvent {
code: Key::Char('q'),
modifiers: KeyModifiers::NONE,
}),
SubClause::Always,
),
Sub::new(
SubEventClause::Keyboard(KeyEvent {
code: Key::Char(' '),
modifiers: KeyModifiers::NONE,
}),
SubClause::Always,
),
Sub::new(
SubEventClause::Keyboard(KeyEvent {
code: Key::Char('n'),
modifiers: KeyModifiers::NONE,
}),
SubClause::Always,
),
Sub::new(
SubEventClause::Keyboard(KeyEvent {
code: Key::Char('N'),
modifiers: KeyModifiers::SHIFT,
}),
SubClause::Always,
),
Sub::new(
SubEventClause::Keyboard(KeyEvent {
code: Key::Char('-'),
modifiers: KeyModifiers::NONE,
}),
SubClause::Always,
),
Sub::new(
SubEventClause::Keyboard(KeyEvent {
code: Key::Char('='),
modifiers: KeyModifiers::NONE,
}),
SubClause::Always,
),
Sub::new(
SubEventClause::Keyboard(KeyEvent {
code: Key::Char('_'),
modifiers: KeyModifiers::SHIFT,
}),
SubClause::Always,
),
Sub::new(
SubEventClause::Keyboard(KeyEvent {
code: Key::Char('+'),
modifiers: KeyModifiers::SHIFT,
}),
SubClause::Always,
),
Sub::new(
SubEventClause::Keyboard(KeyEvent {
code: Key::Char('h'),
modifiers: KeyModifiers::CONTROL,
}),
SubClause::Always,
),
Sub::new(
SubEventClause::Keyboard(KeyEvent {
code: Key::Char('f'),
modifiers: KeyModifiers::NONE,
}),
SubClause::Always,
),
Sub::new(
SubEventClause::Keyboard(KeyEvent {
code: Key::Char('b'),
modifiers: KeyModifiers::NONE,
}),
SubClause::Always,
),
Sub::new(
SubEventClause::Keyboard(KeyEvent {
code: Key::Char('F'),
modifiers: KeyModifiers::SHIFT,
}),
SubClause::Always,
),
Sub::new(
SubEventClause::Keyboard(KeyEvent {
code: Key::Char('B'),
modifiers: KeyModifiers::SHIFT,
}),
SubClause::Always,
),
Sub::new(
SubEventClause::Keyboard(KeyEvent {
code: Key::Char('T'),
modifiers: KeyModifiers::SHIFT,
}),
SubClause::Always,
),
]
}
pub fn player_next(&mut self) {
if self.playlist_items.is_empty() {
return;
}
self.time_pos = 0;
self.status = Some(Status::Running);
if let Some(song) = self.playlist_items.pop_front() {
if let Some(file) = song.file() {
self.player.add_and_play(file);
}
match self.config.loop_mode {
Loop::Playlist => self.playlist_items.push_back(song.clone()),
Loop::Single => self.playlist_items.push_front(song.clone()),
Loop::Queue => {}
}
self.playlist_sync();
self.current_song = Some(song);
if let Err(e) = self.update_photo() {
self.mount_error_popup(format!("update photo error: {}", e).as_str());
};
self.progress_update_title();
self.update_playing_song();
}
}
pub fn player_previous(&mut self) {
if let Loop::Single | Loop::Queue = self.config.loop_mode {
return;
}
if self.playlist_items.is_empty() {
return;
}
if let Some(song) = self.playlist_items.pop_back() {
self.playlist_items.push_front(song);
}
if let Some(song) = self.playlist_items.pop_back() {
self.playlist_items.push_front(song);
}
self.player_next();
}
pub fn player_toggle_pause(&mut self) {
if self.player.is_paused() {
self.status = Some(Status::Running);
self.player.resume();
} else {
self.status = Some(Status::Paused);
self.player.pause();
}
}
pub fn player_seek(&mut self, offset: i64) {
self.player.seek(offset).ok();
self.progress_update();
}
}
pub fn get_block<'a>(props: &Borders, title: (String, Alignment), focus: bool) -> Block<'a> {
Block::default()
.borders(props.sides)
.border_style(if focus {
props.style()
} else {
Style::default().fg(Color::Reset).bg(Color::Reset)
})
.border_type(props.modifiers)
.title(title.0)
.title_alignment(title.1)
}
pub fn draw_area_in(parent: Rect, width: u16, height: u16) -> Rect {
let new_area = Layout::default()
.direction(Direction::Vertical)
.constraints(
[
Constraint::Percentage((100 - height) / 2),
Constraint::Percentage(height),
Constraint::Percentage((100 - height) / 2),
]
.as_ref(),
)
.split(parent);
Layout::default()
.direction(Direction::Horizontal)
.constraints(
[
Constraint::Percentage((100 - width) / 2),
Constraint::Percentage(width),
Constraint::Percentage((100 - width) / 2),
]
.as_ref(),
)
.split(new_area[1])[1]
}
pub fn draw_area_top_right(parent: Rect, width: u16, height: u16) -> Rect {
let new_area = Layout::default()
.direction(Direction::Vertical)
.constraints(
[
Constraint::Percentage(3),
Constraint::Percentage(height),
Constraint::Percentage(100 - 3 - height),
]
.as_ref(),
)
.split(parent);
Layout::default()
.direction(Direction::Horizontal)
.constraints(
[
Constraint::Percentage(100 - 1 - width),
Constraint::Percentage(width),
Constraint::Percentage(1),
]
.as_ref(),
)
.split(new_area[1])[1]
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_utils_ui_draw_area_in() {
let area: Rect = Rect::new(0, 0, 1024, 512);
let child: Rect = draw_area_in(area, 75, 30);
assert_eq!(child.x, 43);
assert_eq!(child.y, 63);
assert_eq!(child.width, 271);
assert_eq!(child.height, 54);
}
}