better event handling with tick, search
This commit is contained in:
parent
4bc03ce8f4
commit
f665c4e9f3
|
|
@ -388,10 +388,17 @@ dependencies = [
|
||||||
"crossterm",
|
"crossterm",
|
||||||
"mpd",
|
"mpd",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
|
"rust-fuzzy-search",
|
||||||
"rust_fzf",
|
"rust_fzf",
|
||||||
"simple-dmenu",
|
"simple-dmenu",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rust-fuzzy-search"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a157657054ffe556d8858504af8a672a054a6e0bd9e8ee531059100c0fa11bb2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rust_fzf"
|
name = "rust_fzf"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
|
|
|
||||||
|
|
@ -12,3 +12,4 @@ rust_fzf = "0.3.1"
|
||||||
simple-dmenu = "0.1.0"
|
simple-dmenu = "0.1.0"
|
||||||
ratatui = "0.26.2"
|
ratatui = "0.26.2"
|
||||||
crossterm = "0.27.0"
|
crossterm = "0.27.0"
|
||||||
|
rust-fuzzy-search = "0.1.1"
|
||||||
|
|
|
||||||
11
src/app.rs
11
src/app.rs
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::connection::Connection;
|
use crate::connection::Connection;
|
||||||
use crate::list::ContentList;
|
use crate::list::ContentList;
|
||||||
use mpd::Client;
|
use mpd::Client;
|
||||||
|
|
@ -56,7 +58,11 @@ impl App {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tick(&self) {}
|
pub fn tick(&mut self) {
|
||||||
|
self.conn.update_state();
|
||||||
|
self.conn.update_progress();
|
||||||
|
self.update_queue();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn quit(&mut self) {
|
pub fn quit(&mut self) {
|
||||||
self.running = false;
|
self.running = false;
|
||||||
|
|
@ -76,8 +82,7 @@ impl App {
|
||||||
// });
|
// });
|
||||||
conn.conn.queue().unwrap().into_iter().for_each(|x| {
|
conn.conn.queue().unwrap().into_iter().for_each(|x| {
|
||||||
vec.push(x.file);
|
vec.push(x.file);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_queue(&mut self) {
|
pub fn update_queue(&mut self) {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ use mpd::song::Song;
|
||||||
use mpd::{Client, State};
|
use mpd::{Client, State};
|
||||||
use simple_dmenu::dmenu;
|
use simple_dmenu::dmenu;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
pub type Result<T> = core::result::Result<T, Error>;
|
pub type Result<T> = core::result::Result<T, Error>;
|
||||||
pub type Error = Box<dyn std::error::Error>;
|
pub type Error = Box<dyn std::error::Error>;
|
||||||
|
|
@ -11,6 +12,8 @@ pub struct Connection {
|
||||||
pub conn: Client,
|
pub conn: Client,
|
||||||
pub songs_filenames: Vec<String>,
|
pub songs_filenames: Vec<String>,
|
||||||
pub state: String,
|
pub state: String,
|
||||||
|
pub elapsed: Duration,
|
||||||
|
pub total_duration: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Connection {
|
impl Connection {
|
||||||
|
|
@ -24,10 +27,14 @@ impl Connection {
|
||||||
.map(|x| x.file)
|
.map(|x| x.file)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
let (elapsed, total) = conn.status().unwrap().time.unwrap_or_default();
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
conn,
|
conn,
|
||||||
songs_filenames,
|
songs_filenames,
|
||||||
state: "Stopped".to_string(),
|
state: "Stopped".to_string(),
|
||||||
|
elapsed,
|
||||||
|
total_duration: total,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,6 +71,26 @@ impl Connection {
|
||||||
self.state.clone()
|
self.state.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_progress(&mut self) {
|
||||||
|
let (elapsed, total) = self.conn.status().unwrap().time.unwrap_or_default();
|
||||||
|
self.elapsed = elapsed;
|
||||||
|
self.total_duration = total;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_progress_ratio(&self) -> f64 {
|
||||||
|
let total = self.total_duration.as_secs_f64();
|
||||||
|
if total == 0.0 {
|
||||||
|
0.0
|
||||||
|
} else {
|
||||||
|
let ratio = self.elapsed.as_secs_f64() / self.total_duration.as_secs_f64();
|
||||||
|
if ratio > 1.0 || ratio == 0.0 {
|
||||||
|
1.0
|
||||||
|
} else {
|
||||||
|
ratio
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// push the given song to queue
|
/// push the given song to queue
|
||||||
pub fn push(&mut self, song: &Song) -> Result<()> {
|
pub fn push(&mut self, song: &Song) -> Result<()> {
|
||||||
if self.conn.queue().unwrap().is_empty() {
|
if self.conn.queue().unwrap().is_empty() {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,10 @@ use std::time::Duration;
|
||||||
|
|
||||||
use crate::app::{App, AppResult, SelectedTab};
|
use crate::app::{App, AppResult, SelectedTab};
|
||||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||||
|
use mpd::{Query, Term};
|
||||||
|
use ratatui::style::Modifier;
|
||||||
|
use rust_fuzzy_search;
|
||||||
|
use simple_dmenu::dmenu;
|
||||||
|
|
||||||
pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
|
pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
|
||||||
match key_event.code {
|
match key_event.code {
|
||||||
|
|
@ -39,7 +43,6 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
|
||||||
app.queue_list.list.get(app.queue_list.index).unwrap(),
|
app.queue_list.list.get(app.queue_list.index).unwrap(),
|
||||||
);
|
);
|
||||||
app.conn.push(&song)?;
|
app.conn.push(&song)?;
|
||||||
|
|
||||||
}
|
}
|
||||||
SelectedTab::Playlists => {
|
SelectedTab::Playlists => {
|
||||||
app.conn
|
app.conn
|
||||||
|
|
@ -52,19 +55,16 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
|
||||||
// Toggle Pause
|
// Toggle Pause
|
||||||
KeyCode::Char('p') => {
|
KeyCode::Char('p') => {
|
||||||
app.conn.toggle_pause();
|
app.conn.toggle_pause();
|
||||||
app.conn.update_state();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pause
|
// Pause
|
||||||
KeyCode::Char('s') => {
|
KeyCode::Char('s') => {
|
||||||
app.conn.pause();
|
app.conn.pause();
|
||||||
app.conn.update_state();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear Queue
|
// Clear Queue
|
||||||
KeyCode::Char('x') => {
|
KeyCode::Char('x') => {
|
||||||
app.conn.conn.clear()?;
|
app.conn.conn.clear()?;
|
||||||
app.conn.update_state();
|
|
||||||
// app.update_queue();
|
// app.update_queue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -73,7 +73,6 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
|
||||||
app.conn.play_dmenu()?;
|
app.conn.play_dmenu()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// add to queue
|
// add to queue
|
||||||
KeyCode::Char('a') => {
|
KeyCode::Char('a') => {
|
||||||
let song = app.conn.get_song_with_only_filename(
|
let song = app.conn.get_song_with_only_filename(
|
||||||
|
|
@ -82,7 +81,6 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
|
||||||
app.conn.conn.push(&song)?;
|
app.conn.conn.push(&song)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
KeyCode::Right => {
|
KeyCode::Right => {
|
||||||
app.conn
|
app.conn
|
||||||
.push_playlist(app.pl_list.list.get(app.pl_list.index).unwrap())?;
|
.push_playlist(app.pl_list.list.get(app.pl_list.index).unwrap())?;
|
||||||
|
|
@ -138,6 +136,28 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
|
||||||
// Delete highlighted song from the queue
|
// Delete highlighted song from the queue
|
||||||
KeyCode::Char('d') => {
|
KeyCode::Char('d') => {
|
||||||
app.conn.conn.delete(app.queue_list.index as u32)?;
|
app.conn.conn.delete(app.queue_list.index as u32)?;
|
||||||
|
app.update_queue();
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyCode::Char('U') => {
|
||||||
|
app.conn.conn.update()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyCode::Char('L') => {
|
||||||
|
let str = dmenu!(prompt "Search: ");
|
||||||
|
let list = app
|
||||||
|
.conn
|
||||||
|
.songs_filenames
|
||||||
|
.iter()
|
||||||
|
.map(|f| f.as_str())
|
||||||
|
.collect::<Vec<&str>>();
|
||||||
|
let (filename, _) = rust_fuzzy_search::fuzzy_search_sorted(&str, &list)
|
||||||
|
.get(0)
|
||||||
|
.unwrap()
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let song = app.conn.get_song_with_only_filename(filename);
|
||||||
|
app.conn.push(&song)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|
|
||||||
62
src/ui.rs
62
src/ui.rs
|
|
@ -1,7 +1,6 @@
|
||||||
use crate::app::{App, AppResult, SelectedTab};
|
use crate::app::{App, AppResult, SelectedTab};
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
style::palette::tailwind,
|
|
||||||
widgets::{block::Title, *},
|
widgets::{block::Title, *},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -16,8 +15,9 @@ pub fn render(app: &mut App, frame: &mut Frame) {
|
||||||
let layout = Layout::default()
|
let layout = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints(vec![
|
.constraints(vec![
|
||||||
Constraint::Percentage(5),
|
Constraint::Percentage(0),
|
||||||
Constraint::Percentage(88),
|
// Constraint::Percentage(88),
|
||||||
|
Constraint::Percentage(93),
|
||||||
Constraint::Percentage(7),
|
Constraint::Percentage(7),
|
||||||
])
|
])
|
||||||
.split(frame.size());
|
.split(frame.size());
|
||||||
|
|
@ -37,30 +37,35 @@ pub fn render(app: &mut App, frame: &mut Frame) {
|
||||||
// draw_playlists(frame, app, inner_layout[1]);
|
// draw_playlists(frame, app, inner_layout[1]);
|
||||||
draw_progress_bar(frame, app, layout[2]);
|
draw_progress_bar(frame, app, layout[2]);
|
||||||
|
|
||||||
let highlight_style = (Color::default(), tailwind::YELLOW.c700);
|
// let highlight_style = (Color::default(), tailwind::YELLOW.c700);
|
||||||
let tab = Tabs::new(vec!["Songs List", "Play Queue", "Playlists"])
|
// let tab = Tabs::new(vec!["Songs List", "Play Queue", "Playlists"])
|
||||||
.block(Block::default().title("Tabs").borders(Borders::ALL))
|
// .block(Block::default().title("Tabs").borders(Borders::ALL))
|
||||||
.style(Style::default().white())
|
// .style(Style::default().white())
|
||||||
.highlight_style(highlight_style)
|
// .highlight_style(highlight_style)
|
||||||
.divider(" ")
|
// .divider(" ")
|
||||||
.select(app.selected_tab.clone() as usize)
|
// .select(app.selected_tab.clone() as usize)
|
||||||
.padding("", "");
|
// .padding("", "");
|
||||||
frame.render_widget(tab, layout[0]);
|
// frame.render_widget(tab, layout[0]);
|
||||||
|
|
||||||
match app.selected_tab {
|
match app.selected_tab {
|
||||||
SelectedTab::SongList => draw_song_list(frame, app, layout[1]),
|
SelectedTab::SongList => draw_song_list(frame, app, layout[1]),
|
||||||
SelectedTab::Queue => draw_queue(frame, app, layout[1]),
|
SelectedTab::Queue => draw_queue(frame, app, layout[1]),
|
||||||
SelectedTab::Playlists => draw_playlists(frame, app, layout[1]),
|
SelectedTab::Playlists => draw_playlists(frame, app, layout[1]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// draws list of songs
|
/// draws list of songs
|
||||||
fn draw_song_list(frame: &mut Frame, app: &mut App, size: Rect) {
|
fn draw_song_list(frame: &mut Frame, app: &mut App, size: Rect) {
|
||||||
let mut song_state = ListState::default();
|
let mut song_state = ListState::default();
|
||||||
|
let total_songs = app.conn.conn.stats().unwrap().songs.to_string();
|
||||||
let list = List::new(app.conn.songs_filenames.clone())
|
let list = List::new(app.conn.songs_filenames.clone())
|
||||||
.block(
|
.block(
|
||||||
Block::default()
|
Block::default()
|
||||||
.title("Song List".green().bold())
|
.title("Song List".green().bold())
|
||||||
|
.title(
|
||||||
|
Title::from(format!("Total Songs: {}", total_songs).bold().green())
|
||||||
|
.alignment(Alignment::Right),
|
||||||
|
)
|
||||||
.borders(Borders::ALL),
|
.borders(Borders::ALL),
|
||||||
)
|
)
|
||||||
.highlight_style(Style::new().add_modifier(Modifier::REVERSED))
|
.highlight_style(Style::new().add_modifier(Modifier::REVERSED))
|
||||||
|
|
@ -74,16 +79,13 @@ fn draw_song_list(frame: &mut Frame, app: &mut App, size: Rect) {
|
||||||
/// draws playing queue
|
/// draws playing queue
|
||||||
fn draw_queue(frame: &mut Frame, app: &mut App, size: Rect) {
|
fn draw_queue(frame: &mut Frame, app: &mut App, size: Rect) {
|
||||||
let mut queue_state = ListState::default();
|
let mut queue_state = ListState::default();
|
||||||
let title = Block::default()
|
let title = Block::default().title(Title::from("Play Queue".green().bold()));
|
||||||
.title(Title::from("Play Queue".green().bold()))
|
|
||||||
.title("Shift + ▲ ▼ to scroll, Shift + Enter to play".yellow());
|
|
||||||
let list = List::new(app.queue_list.list.clone())
|
let list = List::new(app.queue_list.list.clone())
|
||||||
.block(title.borders(Borders::ALL))
|
.block(title.borders(Borders::ALL))
|
||||||
.highlight_style(Style::new().add_modifier(Modifier::REVERSED))
|
.highlight_style(Style::new().add_modifier(Modifier::REVERSED))
|
||||||
.highlight_symbol(">>")
|
.highlight_symbol(">>")
|
||||||
.repeat_highlight_symbol(true);
|
.repeat_highlight_symbol(true);
|
||||||
|
|
||||||
app.update_queue();
|
|
||||||
queue_state.select(Some(app.queue_list.index));
|
queue_state.select(Some(app.queue_list.index));
|
||||||
frame.render_stateful_widget(list, size, &mut queue_state);
|
frame.render_stateful_widget(list, size, &mut queue_state);
|
||||||
}
|
}
|
||||||
|
|
@ -92,9 +94,7 @@ fn draw_queue(frame: &mut Frame, app: &mut App, size: Rect) {
|
||||||
fn draw_playlists(frame: &mut Frame, app: &mut App, size: Rect) {
|
fn draw_playlists(frame: &mut Frame, app: &mut App, size: Rect) {
|
||||||
let mut state = ListState::default();
|
let mut state = ListState::default();
|
||||||
|
|
||||||
let title = Block::default()
|
let title = Block::default().title(Title::from("Playlist".green().bold()));
|
||||||
.title(Title::from("Playlist".green().bold()))
|
|
||||||
.title("▲ ▼ to scroll, Use ► to add playlist to queue".yellow());
|
|
||||||
let list = List::new(app.pl_list.list.clone())
|
let list = List::new(app.pl_list.list.clone())
|
||||||
.block(title.borders(Borders::ALL))
|
.block(title.borders(Borders::ALL))
|
||||||
.highlight_style(Style::new().add_modifier(Modifier::REVERSED))
|
.highlight_style(Style::new().add_modifier(Modifier::REVERSED))
|
||||||
|
|
@ -118,8 +118,22 @@ fn draw_progress_bar(frame: &mut Frame, app: &mut App, size: Rect) {
|
||||||
|
|
||||||
let title = Block::default()
|
let title = Block::default()
|
||||||
.title(Title::from(format!("{}: ", state).red().bold()))
|
.title(Title::from(format!("{}: ", state).red().bold()))
|
||||||
.title(Title::from(song.green().bold()));
|
.title(Title::from(song.green().bold()))
|
||||||
// .title(Title::from(app.conn.conn.status().unwrap_or_default().volume.to_string().yellow())).title_alignment(Alignment::Right);
|
.title(
|
||||||
|
Title::from(
|
||||||
|
format!(
|
||||||
|
"{}/{}",
|
||||||
|
app.conn.elapsed.as_secs(),
|
||||||
|
app.conn.total_duration.as_secs()
|
||||||
|
)
|
||||||
|
.cyan()
|
||||||
|
.bold(),
|
||||||
|
)
|
||||||
|
.alignment(Alignment::Right),
|
||||||
|
)
|
||||||
|
.borders(Borders::ALL);
|
||||||
|
|
||||||
|
// .title(Title::from(app.conn.conn.status().unwrap_or_default().volume.to_string().yellow())).title_alignment(Alignment::Right);
|
||||||
let progress_bar = LineGauge::default()
|
let progress_bar = LineGauge::default()
|
||||||
.block(title.borders(Borders::ALL))
|
.block(title.borders(Borders::ALL))
|
||||||
.gauge_style(
|
.gauge_style(
|
||||||
|
|
@ -129,7 +143,7 @@ fn draw_progress_bar(frame: &mut Frame, app: &mut App, size: Rect) {
|
||||||
.add_modifier(Modifier::BOLD),
|
.add_modifier(Modifier::BOLD),
|
||||||
)
|
)
|
||||||
.line_set(symbols::line::THICK)
|
.line_set(symbols::line::THICK)
|
||||||
.ratio(0.2);
|
.ratio(app.conn.get_progress_ratio());
|
||||||
|
|
||||||
frame.render_widget(progress_bar, size);
|
frame.render_widget(progress_bar, size);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Reference in New Issue