rmptui9e2b51c
use tabs instead of layouts
+145 −55
4 files changed
- M src/app.rs +28 −1
- M src/connection.rs +10 −8
- M src/handler.rs +67 −28
- M src/ui.rs +40 −18
| @@ -1,7 +1,6 @@ | |||
| 1 | 1 | use crate::connection::Connection; | |
| 2 | 2 | use crate::list::ContentList; | |
| 3 | 3 | use mpd::Client; | |
| 4 | − | use std::collections::VecDeque; | |
| 5 | 4 | ||
| 6 | 5 | // Application result type | |
| 7 | 6 | pub type AppResult<T> = std::result::Result<T, Box<dyn std::error::Error>>; | |
| @@ -15,6 +14,25 @@ pub struct App { | |||
| 15 | 14 | pub song_list: ContentList<String>, | |
| 16 | 15 | pub queue_list: ContentList<String>, | |
| 17 | 16 | pub pl_list: ContentList<String>, | |
| 17 | + | pub selected_tab: SelectedTab, | |
| 18 | + | } | |
| 19 | + | ||
| 20 | + | ||
| 21 | + | #[derive(Debug, PartialEq, Clone)] | |
| 22 | + | pub enum SelectedTab { | |
| 23 | + | SongList, | |
| 24 | + | Queue, | |
| 25 | + | Playlists, | |
| 26 | + | } | |
| 27 | + | ||
| 28 | + | impl SelectedTab { | |
| 29 | + | fn as_usize(&self) { | |
| 30 | + | match self { | |
| 31 | + | SelectedTab::SongList => 0, | |
| 32 | + | SelectedTab::Queue => 1, | |
| 33 | + | SelectedTab::Playlists => 2, | |
| 34 | + | }; | |
| 35 | + | } | |
| 18 | 36 | } | |
| 19 | 37 | ||
| 20 | 38 | impl App { | |
| @@ -35,6 +53,7 @@ impl App { | |||
| 35 | 53 | song_list, | |
| 36 | 54 | queue_list, | |
| 37 | 55 | pl_list, | |
| 56 | + | selected_tab: SelectedTab::SongList, | |
| 38 | 57 | }) | |
| 39 | 58 | } | |
| 40 | 59 | ||
| @@ -73,4 +92,12 @@ impl App { | |||
| 73 | 92 | Self::get_playlist(&mut self.conn.conn)?; | |
| 74 | 93 | Ok(()) | |
| 75 | 94 | } | |
| 95 | + | ||
| 96 | + | pub fn cycle_tabls(&mut self) { | |
| 97 | + | self.selected_tab = match self.selected_tab { | |
| 98 | + | SelectedTab::SongList => SelectedTab::Queue, | |
| 99 | + | SelectedTab::Queue => SelectedTab::Playlists, | |
| 100 | + | SelectedTab::Playlists=> SelectedTab::SongList, | |
| 101 | + | }; | |
| 102 | + | } | |
| 76 | 103 | } | |
| @@ -158,15 +158,17 @@ impl Connection { | |||
| 158 | 158 | } | |
| 159 | 159 | ||
| 160 | 160 | // Volume controls | |
| 161 | − | /// Sets the volume | |
| 162 | − | pub fn set_volume(&mut self, u: String) { | |
| 161 | + | pub fn inc_volume(&mut self, v: i8) { | |
| 163 | 162 | let cur = self.conn.status().unwrap().volume; | |
| 164 | − | let sym = u.get(0..1).unwrap(); | |
| 165 | − | let u: i8 = u.parse::<i8>().unwrap(); | |
| 166 | − | if sym == "+" || sym == "-" { | |
| 167 | − | self.conn.volume(cur + u).unwrap(); | |
| 168 | − | } else { | |
| 169 | − | self.conn.volume(u).unwrap(); | |
| 163 | + | if cur + v <= 100 { | |
| 164 | + | self.conn.volume(cur + v).unwrap(); | |
| 165 | + | } | |
| 166 | + | } | |
| 167 | + | ||
| 168 | + | pub fn dec_volume(&mut self, v: i8) { | |
| 169 | + | let cur = self.conn.status().unwrap().volume; | |
| 170 | + | if cur - v >= 0 { | |
| 171 | + | self.conn.volume(cur - v).unwrap(); | |
| 170 | 172 | } | |
| 171 | 173 | } | |
| 172 | 174 | } | |
| @@ -1,7 +1,8 @@ | |||
| 1 | 1 | use std::time::Duration; | |
| 2 | 2 | ||
| 3 | − | use crate::app::{App, AppResult}; | |
| 3 | + | use crate::app::{App, AppResult, SelectedTab}; | |
| 4 | 4 | use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; | |
| 5 | + | use ratatui::style::Modifier; | |
| 5 | 6 | ||
| 6 | 7 | pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> { | |
| 7 | 8 | match key_event.code { | |
| @@ -12,36 +13,53 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> { | |||
| 12 | 13 | } | |
| 13 | 14 | } | |
| 14 | 15 | ||
| 15 | − | KeyCode::Char('j') => { | |
| 16 | − | app.song_list.next(); | |
| 17 | − | } | |
| 16 | + | KeyCode::Char('j') | KeyCode::Down => match app.selected_tab { | |
| 17 | + | SelectedTab::SongList => app.song_list.next(), | |
| 18 | + | SelectedTab::Queue => app.queue_list.next(), | |
| 19 | + | SelectedTab::Playlists => app.pl_list.next(), | |
| 20 | + | }, | |
| 18 | 21 | ||
| 19 | − | KeyCode::Char('k') => { | |
| 20 | − | app.song_list.prev(); | |
| 21 | − | } | |
| 22 | + | KeyCode::Char('k') | KeyCode::Up => match app.selected_tab { | |
| 23 | + | SelectedTab::SongList => app.song_list.prev(), | |
| 24 | + | SelectedTab::Queue => app.queue_list.prev(), | |
| 25 | + | SelectedTab::Playlists => app.pl_list.prev(), | |
| 26 | + | }, | |
| 22 | 27 | ||
| 23 | 28 | KeyCode::Enter | KeyCode::Char('l') => { | |
| 24 | − | let song = app.conn.get_song_with_only_filename( | |
| 25 | − | app.conn.songs_filenames.get(app.song_list.index).unwrap(), | |
| 26 | − | ); | |
| 27 | − | app.conn.push(&song)?; | |
| 28 | 29 | // app.update_queue(); | |
| 30 | + | ||
| 31 | + | match app.selected_tab { | |
| 32 | + | SelectedTab::SongList => { | |
| 33 | + | let song = app.conn.get_song_with_only_filename( | |
| 34 | + | app.conn.songs_filenames.get(app.song_list.index).unwrap(), | |
| 35 | + | ); | |
| 36 | + | app.conn.push(&song)?; | |
| 37 | + | } | |
| 38 | + | SelectedTab::Queue => {} | |
| 39 | + | SelectedTab::Playlists => { | |
| 40 | + | app.conn | |
| 41 | + | .push_playlist(app.pl_list.list.get(app.pl_list.index).unwrap())?; | |
| 42 | + | } | |
| 43 | + | } | |
| 29 | 44 | } | |
| 30 | 45 | ||
| 31 | 46 | // Playback controls | |
| 32 | 47 | // Toggle Pause | |
| 33 | 48 | KeyCode::Char('p') => { | |
| 34 | 49 | app.conn.toggle_pause(); | |
| 50 | + | app.conn.update_state(); | |
| 35 | 51 | } | |
| 36 | 52 | ||
| 37 | 53 | // Pause | |
| 38 | 54 | KeyCode::Char('s') => { | |
| 39 | 55 | app.conn.pause(); | |
| 56 | + | app.conn.update_state(); | |
| 40 | 57 | } | |
| 41 | 58 | ||
| 42 | − | // Clearn Queue | |
| 59 | + | // Clear Queue | |
| 43 | 60 | KeyCode::Char('x') => { | |
| 44 | 61 | app.conn.conn.clear()?; | |
| 62 | + | app.conn.update_state(); | |
| 45 | 63 | // app.update_queue(); | |
| 46 | 64 | } | |
| 47 | 65 | ||
| @@ -49,22 +67,6 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> { | |||
| 49 | 67 | app.conn.play_dmenu()?; | |
| 50 | 68 | } | |
| 51 | 69 | ||
| 52 | − | KeyCode::Down => { | |
| 53 | − | if key_event.modifiers == KeyModifiers::SHIFT { | |
| 54 | − | app.queue_list.next(); | |
| 55 | − | } else { | |
| 56 | − | app.pl_list.next(); | |
| 57 | − | } | |
| 58 | − | } | |
| 59 | − | ||
| 60 | − | KeyCode::Up => { | |
| 61 | − | if key_event.modifiers == KeyModifiers::SHIFT { | |
| 62 | − | app.queue_list.prev(); | |
| 63 | − | } else { | |
| 64 | − | app.pl_list.prev(); | |
| 65 | − | } | |
| 66 | − | } | |
| 67 | − | ||
| 68 | 70 | KeyCode::Right => { | |
| 69 | 71 | app.conn | |
| 70 | 72 | .push_playlist(app.pl_list.list.get(app.pl_list.index).unwrap())?; | |
| @@ -83,6 +85,43 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> { | |||
| 83 | 85 | let pos = Duration::from_secs(pos.as_secs().wrapping_add(2)); | |
| 84 | 86 | app.conn.conn.seek(place, pos)?; | |
| 85 | 87 | } | |
| 88 | + | ||
| 89 | + | KeyCode::Tab => { | |
| 90 | + | app.cycle_tabls(); | |
| 91 | + | } | |
| 92 | + | ||
| 93 | + | KeyCode::Char('1') => { | |
| 94 | + | app.selected_tab = SelectedTab::SongList; | |
| 95 | + | } | |
| 96 | + | ||
| 97 | + | KeyCode::Char('2') => { | |
| 98 | + | app.selected_tab = SelectedTab::Queue; | |
| 99 | + | } | |
| 100 | + | ||
| 101 | + | KeyCode::Char('3') => { | |
| 102 | + | app.selected_tab = SelectedTab::Playlists; | |
| 103 | + | } | |
| 104 | + | ||
| 105 | + | ||
| 106 | + | KeyCode::Char('n') => { | |
| 107 | + | app.conn.conn.next()?; | |
| 108 | + | } | |
| 109 | + | ||
| 110 | + | KeyCode::Char('N') => { | |
| 111 | + | app.conn.conn.prev()?; | |
| 112 | + | } | |
| 113 | + | ||
| 114 | + | // Volume controls | |
| 115 | + | KeyCode::Char('=') => { | |
| 116 | + | app.conn.inc_volume(2); | |
| 117 | + | } | |
| 118 | + | ||
| 119 | + | KeyCode::Char('-') => { | |
| 120 | + | app.conn.dec_volume(2); | |
| 121 | + | } | |
| 122 | + | ||
| 123 | + | ||
| 124 | + | ||
| 86 | 125 | _ => {} | |
| 87 | 126 | } | |
| 88 | 127 | ||
| @@ -1,6 +1,7 @@ | |||
| 1 | − | use crate::app::{App, AppResult}; | |
| 1 | + | use crate::app::{App, AppResult, SelectedTab}; | |
| 2 | 2 | use ratatui::{ | |
| 3 | 3 | prelude::*, | |
| 4 | + | style::palette::tailwind, | |
| 4 | 5 | widgets::{block::Title, *}, | |
| 5 | 6 | }; | |
| 6 | 7 | ||
| @@ -12,25 +13,45 @@ pub fn render(app: &mut App, frame: &mut Frame) { | |||
| 12 | 13 | // - https://github.com/ratatui-org/ratatui/tree/master/examples | |
| 13 | 14 | ||
| 14 | 15 | // Layout | |
| 15 | − | let main_layout = Layout::default() | |
| 16 | + | let layout = Layout::default() | |
| 16 | 17 | .direction(Direction::Vertical) | |
| 17 | − | .constraints(vec![Constraint::Percentage(93), Constraint::Percentage(7)]) | |
| 18 | + | .constraints(vec![ | |
| 19 | + | Constraint::Percentage(5), | |
| 20 | + | Constraint::Percentage(88), | |
| 21 | + | Constraint::Percentage(7), | |
| 22 | + | ]) | |
| 18 | 23 | .split(frame.size()); | |
| 19 | − | ||
| 20 | − | let outer_layout = Layout::default() | |
| 21 | − | .direction(Direction::Horizontal) | |
| 22 | − | .constraints(vec![Constraint::Percentage(50), Constraint::Percentage(50)]) | |
| 23 | − | .split(main_layout[0]); | |
| 24 | − | ||
| 25 | − | let inner_layout = Layout::default() | |
| 26 | − | .direction(Direction::Vertical) | |
| 27 | − | .constraints(vec![Constraint::Percentage(50), Constraint::Percentage(50)]) | |
| 28 | − | .split(outer_layout[1]); | |
| 29 | − | ||
| 30 | − | draw_song_list(frame, app, outer_layout[0]); | |
| 31 | − | draw_queue(frame, app, inner_layout[0]); | |
| 32 | − | draw_playlists(frame, app, inner_layout[1]); | |
| 33 | − | draw_progress_bar(frame, app, main_layout[1]); | |
| 24 | + | // | |
| 25 | + | // let outer_layout = Layout::default() | |
| 26 | + | // .direction(Direction::Horizontal) | |
| 27 | + | // .constraints(vec![Constraint::Percentage(50), Constraint::Percentage(50)]) | |
| 28 | + | // .split(main_layout[1]); | |
| 29 | + | // | |
| 30 | + | // let inner_layout = Layout::default() | |
| 31 | + | // .direction(Direction::Vertical) | |
| 32 | + | // .constraints(vec![Constraint::Percentage(50), Constraint::Percentage(50)]) | |
| 33 | + | // .split(outer_layout[1]); | |
| 34 | + | ||
| 35 | + | // draw_song_list(frame, app, outer_layout[0]); | |
| 36 | + | // draw_queue(frame, app, inner_layout[0]); | |
| 37 | + | // draw_playlists(frame, app, inner_layout[1]); | |
| 38 | + | draw_progress_bar(frame, app, layout[2]); | |
| 39 | + | ||
| 40 | + | let highlight_style = (Color::default(), tailwind::YELLOW.c700); | |
| 41 | + | let tab = Tabs::new(vec!["Songs List", "Play Queue", "Playlists"]) | |
| 42 | + | .block(Block::default().title("Tabs").borders(Borders::ALL)) | |
| 43 | + | .style(Style::default().white()) | |
| 44 | + | .highlight_style(highlight_style) | |
| 45 | + | .divider(" ") | |
| 46 | + | .select(app.selected_tab.clone() as usize) | |
| 47 | + | .padding("", ""); | |
| 48 | + | frame.render_widget(tab, layout[0]); | |
| 49 | + | ||
| 50 | + | match app.selected_tab { | |
| 51 | + | SelectedTab::SongList => draw_song_list(frame, app, layout[1]), | |
| 52 | + | SelectedTab::Queue => draw_queue(frame, app, layout[1]), | |
| 53 | + | SelectedTab::Playlists => draw_playlists(frame, app, layout[1]), | |
| 54 | + | } | |
| 34 | 55 | } | |
| 35 | 56 | ||
| 36 | 57 | /// draws list of songs | |
| @@ -98,6 +119,7 @@ fn draw_progress_bar(frame: &mut Frame, app: &mut App, size: Rect) { | |||
| 98 | 119 | let title = Block::default() | |
| 99 | 120 | .title(Title::from(format!("{}: ", state).red().bold())) | |
| 100 | 121 | .title(Title::from(song.green().bold())); | |
| 122 | + | // .title(Title::from(app.conn.conn.status().unwrap_or_default().volume.to_string().yellow())).title_alignment(Alignment::Right); | |
| 101 | 123 | let progress_bar = LineGauge::default() | |
| 102 | 124 | .block(title.borders(Borders::ALL)) | |
| 103 | 125 | .gauge_style( | |