better event handling with tick, search

This commit is contained in:
krolxon 2024-04-25 12:58:13 +05:30
parent 4bc03ce8f4
commit f665c4e9f3
6 changed files with 107 additions and 33 deletions

7
Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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) {

View File

@ -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() {

View File

@ -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)?;
} }
_ => {} _ => {}

View File

@ -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,15 +37,15 @@ 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]),
@ -57,10 +57,15 @@ pub fn render(app: &mut App, frame: &mut Frame) {
/// 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,7 +118,21 @@ 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(
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); // .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))
@ -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);
} }