add keymaps to readme.md and minor changes
This commit is contained in:
parent
00c5c12d82
commit
a8161521d0
22
README.md
22
README.md
|
|
@ -1,4 +1,24 @@
|
||||||
A MPD client in Rust
|
A MPD client in Rust
|
||||||
|
|
||||||
# Keys
|
### Keys
|
||||||
|
- 'q' OR 'Ctr+C' to quit
|
||||||
|
- 'p' to toggle pause
|
||||||
|
- '+' to increase volume
|
||||||
|
- '-' to decrease volume
|
||||||
|
- 'D' to get dmenu prompt
|
||||||
|
- 'j' to scroll down
|
||||||
|
- 'k' to scroll up
|
||||||
|
- 'l' add song to playlist or go inside the directory
|
||||||
|
- 'h' to go back to previous directory
|
||||||
|
- `Tab` to cycle through tabs
|
||||||
|
- '1' to go to directory tree
|
||||||
|
- '2' to go to current playing queue
|
||||||
|
- '3' to go to playlists view
|
||||||
|
- 'Enter' to add song/playlist to current playlist
|
||||||
|
- 'a' to append the song to current playing queue
|
||||||
|
- 'f' to go forward
|
||||||
|
- 'b' to go backwords
|
||||||
|
- '>' to play next song from queue
|
||||||
|
- '<' to play previous song from queue
|
||||||
|
- 'U' to update the MPD database
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@ impl App {
|
||||||
pub fn tick(&mut self) {
|
pub fn tick(&mut self) {
|
||||||
self.conn.update_state();
|
self.conn.update_state();
|
||||||
self.conn.update_progress();
|
self.conn.update_progress();
|
||||||
|
self.conn.update_volume();
|
||||||
self.update_queue();
|
self.update_queue();
|
||||||
self.browser.update_directory(&mut self.conn).unwrap();
|
self.browser.update_directory(&mut self.conn).unwrap();
|
||||||
}
|
}
|
||||||
|
|
@ -97,7 +98,6 @@ impl App {
|
||||||
|
|
||||||
pub fn get_playlist(conn: &mut Client) -> AppResult<Vec<String>> {
|
pub fn get_playlist(conn: &mut Client) -> AppResult<Vec<String>> {
|
||||||
let list: Vec<String> = conn.playlists()?.iter().map(|p| p.clone().name).collect();
|
let list: Vec<String> = conn.playlists()?.iter().map(|p| p.clone().name).collect();
|
||||||
|
|
||||||
Ok(list)
|
Ok(list)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
use mpd::Query;
|
|
||||||
|
|
||||||
use crate::{app::AppResult, connection::Connection};
|
use crate::{app::AppResult, connection::Connection};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ pub struct Connection {
|
||||||
pub state: String,
|
pub state: String,
|
||||||
pub elapsed: Duration,
|
pub elapsed: Duration,
|
||||||
pub total_duration: Duration,
|
pub total_duration: Duration,
|
||||||
|
pub volume: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Connection {
|
impl Connection {
|
||||||
|
|
@ -26,8 +27,8 @@ impl Connection {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|x| x.file)
|
.map(|x| x.file)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let (elapsed, total) = conn.status().unwrap().time.unwrap_or_default();
|
let (elapsed, total) = conn.status().unwrap().time.unwrap_or_default();
|
||||||
|
let volume: u8 = conn.status().unwrap_or_default().volume as u8;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
conn,
|
conn,
|
||||||
|
|
@ -35,6 +36,7 @@ impl Connection {
|
||||||
state: "Stopped".to_string(),
|
state: "Stopped".to_string(),
|
||||||
elapsed,
|
elapsed,
|
||||||
total_duration: total,
|
total_duration: total,
|
||||||
|
volume,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,6 +79,10 @@ impl Connection {
|
||||||
self.total_duration = total;
|
self.total_duration = total;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_volume(&mut self) {
|
||||||
|
self.volume = self.conn.status().unwrap_or_default().volume as u8;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_progress_ratio(&self) -> f64 {
|
pub fn get_progress_ratio(&self) -> f64 {
|
||||||
let total = self.total_duration.as_secs_f64();
|
let total = self.total_duration.as_secs_f64();
|
||||||
if total == 0.0 {
|
if total == 0.0 {
|
||||||
|
|
@ -84,7 +90,7 @@ impl Connection {
|
||||||
} else {
|
} else {
|
||||||
let ratio = self.elapsed.as_secs_f64() / self.total_duration.as_secs_f64();
|
let ratio = self.elapsed.as_secs_f64() / self.total_duration.as_secs_f64();
|
||||||
if ratio > 1.0 || ratio == 0.0 {
|
if ratio > 1.0 || ratio == 0.0 {
|
||||||
1.0
|
0.0
|
||||||
} else {
|
} else {
|
||||||
ratio
|
ratio
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,6 @@ 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 rust_fuzzy_search;
|
||||||
use simple_dmenu::dmenu;
|
use simple_dmenu::dmenu;
|
||||||
|
|
||||||
|
|
@ -13,6 +11,8 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
|
||||||
KeyCode::Char('c') | KeyCode::Char('C') => {
|
KeyCode::Char('c') | KeyCode::Char('C') => {
|
||||||
if key_event.modifiers == KeyModifiers::CONTROL {
|
if key_event.modifiers == KeyModifiers::CONTROL {
|
||||||
app.quit();
|
app.quit();
|
||||||
|
} else {
|
||||||
|
app.conn.conn.clear()?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,12 +68,6 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
|
||||||
app.conn.pause();
|
app.conn.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear Queue
|
|
||||||
KeyCode::Char('x') => {
|
|
||||||
app.conn.conn.clear()?;
|
|
||||||
// app.update_queue();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dmenu prompt
|
// Dmenu prompt
|
||||||
KeyCode::Char('D') => {
|
KeyCode::Char('D') => {
|
||||||
app.conn.play_dmenu()?;
|
app.conn.play_dmenu()?;
|
||||||
|
|
@ -122,22 +116,23 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
|
||||||
app.selected_tab = SelectedTab::Playlists;
|
app.selected_tab = SelectedTab::Playlists;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
KeyCode::Char('>') => {
|
||||||
KeyCode::Char('n') => {
|
|
||||||
app.conn.conn.next()?;
|
app.conn.conn.next()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyCode::Char('N') => {
|
KeyCode::Char('<') => {
|
||||||
app.conn.conn.prev()?;
|
app.conn.conn.prev()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Volume controls
|
// Volume controls
|
||||||
KeyCode::Char('=') => {
|
KeyCode::Char('=') => {
|
||||||
app.conn.inc_volume(2);
|
app.conn.inc_volume(2);
|
||||||
|
app.conn.update_volume();
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyCode::Char('-') => {
|
KeyCode::Char('-') => {
|
||||||
app.conn.dec_volume(2);
|
app.conn.dec_volume(2);
|
||||||
|
app.conn.update_volume();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete highlighted song from the queue
|
// Delete highlighted song from the queue
|
||||||
|
|
|
||||||
118
src/ui.rs
118
src/ui.rs
|
|
@ -1,7 +1,4 @@
|
||||||
use crate::{
|
use crate::app::{App, SelectedTab};
|
||||||
app::{App, AppResult, SelectedTab},
|
|
||||||
browser::FileBrowser,
|
|
||||||
};
|
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
widgets::{block::Title, *},
|
widgets::{block::Title, *},
|
||||||
|
|
@ -17,73 +14,62 @@ pub fn render(app: &mut App, frame: &mut Frame) {
|
||||||
// Layout
|
// Layout
|
||||||
let layout = Layout::default()
|
let layout = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints(vec![
|
.constraints(vec![Constraint::Percentage(93), Constraint::Percentage(7)])
|
||||||
Constraint::Percentage(0),
|
|
||||||
// Constraint::Percentage(88),
|
|
||||||
Constraint::Percentage(93),
|
|
||||||
Constraint::Percentage(7),
|
|
||||||
])
|
|
||||||
.split(frame.size());
|
.split(frame.size());
|
||||||
//
|
|
||||||
// let outer_layout = Layout::default()
|
|
||||||
// .direction(Direction::Horizontal)
|
|
||||||
// .constraints(vec![Constraint::Percentage(50), Constraint::Percentage(50)])
|
|
||||||
// .split(main_layout[1]);
|
|
||||||
//
|
|
||||||
// let inner_layout = Layout::default()
|
|
||||||
// .direction(Direction::Vertical)
|
|
||||||
// .constraints(vec![Constraint::Percentage(50), Constraint::Percentage(50)])
|
|
||||||
// .split(outer_layout[1]);
|
|
||||||
|
|
||||||
// draw_song_list(frame, app, outer_layout[0]);
|
|
||||||
// draw_queue(frame, app, inner_layout[0]);
|
|
||||||
// draw_playlists(frame, app, inner_layout[1]);
|
|
||||||
draw_progress_bar(frame, app, layout[2]);
|
|
||||||
|
|
||||||
// let highlight_style = (Color::default(), tailwind::YELLOW.c700);
|
|
||||||
// let tab = Tabs::new(vec!["Songs List", "Play Queue", "Playlists"])
|
|
||||||
// .block(Block::default().title("Tabs").borders(Borders::ALL))
|
|
||||||
// .style(Style::default().white())
|
|
||||||
// .highlight_style(highlight_style)
|
|
||||||
// .divider(" ")
|
|
||||||
// .select(app.selected_tab.clone() as usize)
|
|
||||||
// .padding("", "");
|
|
||||||
// frame.render_widget(tab, layout[0]);
|
|
||||||
|
|
||||||
match app.selected_tab {
|
match app.selected_tab {
|
||||||
// SelectedTab::SongList => draw_song_list(frame, app, layout[1]),
|
SelectedTab::Queue => draw_queue(frame, app, layout[0]),
|
||||||
SelectedTab::Queue => draw_queue(frame, app, layout[1]),
|
SelectedTab::Playlists => draw_playlists(frame, app, layout[0]),
|
||||||
SelectedTab::Playlists => draw_playlists(frame, app, layout[1]),
|
SelectedTab::DirectoryBrowser => draw_directory_browser(frame, app, layout[0]),
|
||||||
SelectedTab::DirectoryBrowser => draw_directory_browser(frame, app, layout[1]),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
draw_progress_bar(frame, app, layout[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// draws list of songs
|
/// Draws the file tree browser
|
||||||
fn draw_song_list(frame: &mut Frame, app: &mut App, size: Rect) {
|
fn draw_directory_browser(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 total_songs = app.conn.conn.stats().unwrap().songs.to_string();
|
||||||
let list = List::new(app.conn.songs_filenames.clone())
|
let mut list: Vec<String> = vec![];
|
||||||
|
for (t, s) in app.browser.filetree.iter() {
|
||||||
|
if t == "file" {
|
||||||
|
list.push(s.to_string());
|
||||||
|
} else {
|
||||||
|
list.push(format!("[{}]", *s));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let list = List::new(list)
|
||||||
.block(
|
.block(
|
||||||
Block::default()
|
Block::default()
|
||||||
.title("Song List".green().bold())
|
.title(format!("File Browser: {}", app.browser.path.clone()).bold())
|
||||||
.title(
|
.title(
|
||||||
Title::from(format!("Total Songs: {}", total_songs).bold().green())
|
Title::from(format!("Total Songs: {}", total_songs).bold().green())
|
||||||
|
.alignment(Alignment::Center),
|
||||||
|
)
|
||||||
|
.title(
|
||||||
|
Title::from(format!("Volume: {}%", app.conn.volume).bold().green())
|
||||||
.alignment(Alignment::Right),
|
.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))
|
||||||
.highlight_symbol(">>")
|
.highlight_symbol(">>")
|
||||||
.repeat_highlight_symbol(true);
|
.repeat_highlight_symbol(true)
|
||||||
|
.scroll_padding(20);
|
||||||
|
|
||||||
song_state.select(Some(app.song_list.index));
|
song_state.select(Some(app.browser.selected));
|
||||||
frame.render_stateful_widget(list, size, &mut song_state);
|
frame.render_stateful_widget(list, size, &mut song_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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().title(Title::from("Play Queue".green().bold()));
|
let title = Block::default()
|
||||||
|
.title(Title::from("Play Queue".green().bold()))
|
||||||
|
.title(
|
||||||
|
Title::from(format!("Volume: {}%", app.conn.volume).bold().green())
|
||||||
|
.alignment(Alignment::Right),
|
||||||
|
);
|
||||||
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))
|
||||||
|
|
@ -98,7 +84,13 @@ 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().title(Title::from("Playlist".green().bold()));
|
let title = Block::default()
|
||||||
|
.title(Title::from("Playlist".green().bold()))
|
||||||
|
.title(
|
||||||
|
Title::from(format!("Volume: {}%", app.conn.volume).bold().green())
|
||||||
|
.alignment(Alignment::Right),
|
||||||
|
);
|
||||||
|
|
||||||
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))
|
||||||
|
|
@ -109,7 +101,7 @@ fn draw_playlists(frame: &mut Frame, app: &mut App, size: Rect) {
|
||||||
frame.render_stateful_widget(list, size, &mut state);
|
frame.render_stateful_widget(list, size, &mut state);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Progress Bar
|
/// Draws Progress Bar
|
||||||
fn draw_progress_bar(frame: &mut Frame, app: &mut App, size: Rect) {
|
fn draw_progress_bar(frame: &mut Frame, app: &mut App, size: Rect) {
|
||||||
let song = app
|
let song = app
|
||||||
.conn
|
.conn
|
||||||
|
|
@ -151,33 +143,3 @@ fn draw_progress_bar(frame: &mut Frame, app: &mut App, size: Rect) {
|
||||||
|
|
||||||
frame.render_widget(progress_bar, size);
|
frame.render_widget(progress_bar, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_directory_browser(frame: &mut Frame, app: &mut App, size: Rect) {
|
|
||||||
let mut song_state = ListState::default();
|
|
||||||
let total_songs = app.conn.conn.stats().unwrap().songs.to_string();
|
|
||||||
let mut list: Vec<String> = vec![];
|
|
||||||
for (t, s) in app.browser.filetree.iter() {
|
|
||||||
if t == "file" {
|
|
||||||
list.push(s.to_string());
|
|
||||||
} else {
|
|
||||||
list.push(format!("[{}]", *s));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let list = List::new(list)
|
|
||||||
.block(
|
|
||||||
Block::default()
|
|
||||||
.title(format!("File Browser: {}", app.browser.path.clone()).bold())
|
|
||||||
.title(
|
|
||||||
Title::from(format!("Total Songs: {}", total_songs).bold().green())
|
|
||||||
.alignment(Alignment::Right),
|
|
||||||
)
|
|
||||||
.borders(Borders::ALL),
|
|
||||||
)
|
|
||||||
.highlight_style(Style::new().add_modifier(Modifier::REVERSED))
|
|
||||||
.highlight_symbol(">>")
|
|
||||||
.repeat_highlight_symbol(true)
|
|
||||||
.scroll_padding(20);
|
|
||||||
|
|
||||||
song_state.select(Some(app.browser.selected));
|
|
||||||
frame.render_stateful_widget(list, size, &mut song_state);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Reference in New Issue