aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkrolxon <krolyxon@tutanota.com>2024-04-30 22:40:56 +0530
committerkrolxon <krolyxon@tutanota.com>2024-04-30 22:40:56 +0530
commit32a3c604717c99e498a20069bc8895ae9deb0cb9 (patch)
treee63936fae671986b599ee8c6cc818f57ae885b51
parente95e1bbb362e1ed4ad40a620bf6569f2ceafb8ae (diff)
add rename playlists feature
-rwxr-xr-xREADME.md4
-rwxr-xr-xsrc/app.rs126
-rwxr-xr-xsrc/handler.rs51
-rwxr-xr-xsrc/ui.rs65
4 files changed, 181 insertions, 65 deletions
diff --git a/README.md b/README.md
index e65e051..2b25aeb 100755
--- a/README.md
+++ b/README.md
@@ -39,5 +39,5 @@ A MPD client in Rust
- [x] search for songs
- [x] Human readable time format
- [x] metadata based tree view
-- [ ] view playlist
-- [ ] change playlist name
+- [x] view playlist
+- [x] change playlist name
diff --git a/src/app.rs b/src/app.rs
index 35661e1..be55e7d 100755
--- a/src/app.rs
+++ b/src/app.rs
@@ -21,9 +21,11 @@ pub struct App {
pub selected_tab: SelectedTab, // Used to switch between tabs
// Search
- pub inputmode: InputMode, // Defines input mode, Normal or Search
- pub search_input: String, // Stores the userinput to be searched
- pub cursor_position: usize, // Stores the cursor position
+ pub inputmode: InputMode, // Defines input mode, Normal or Search
+ pub search_input: String, // Stores the userinput to be searched
+ pub search_cursor_pos: usize, // Stores the cursor position for searching
+ pub pl_newname_input: String, // Stores the new name of the playlist
+ pub pl_cursor_pos: usize, // Stores the cursor position for renaming playlist
// playlist variables
// used to show playlist popup
@@ -59,7 +61,9 @@ impl App {
browser,
inputmode: InputMode::Normal,
search_input: String::new(),
- cursor_position: 0,
+ pl_newname_input: String::new(),
+ search_cursor_pos: 0,
+ pl_cursor_pos: 0,
playlist_popup: false,
append_list,
})
@@ -97,7 +101,7 @@ impl App {
Self::get_queue(&mut self.conn, &mut self.queue_list.list);
}
- 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();
Ok(list)
}
@@ -220,49 +224,111 @@ impl App {
// Cursor movements
pub fn move_cursor_left(&mut self) {
- let cursor_moved_left = self.cursor_position.saturating_sub(1);
- self.cursor_position = self.clamp_cursor(cursor_moved_left);
+ match self.inputmode {
+ InputMode::PlaylistRename => {
+ let cursor_moved_left = self.pl_cursor_pos.saturating_sub(1);
+ self.pl_cursor_pos = self.clamp_cursor(cursor_moved_left);
+ }
+ InputMode::Editing => {
+ let cursor_moved_left = self.search_cursor_pos.saturating_sub(1);
+ self.search_cursor_pos = self.clamp_cursor(cursor_moved_left);
+ }
+ _ => {}
+ }
}
pub fn move_cursor_right(&mut self) {
- let cursor_moved_right = self.cursor_position.saturating_add(1);
- self.cursor_position = self.clamp_cursor(cursor_moved_right);
+ match self.inputmode {
+ InputMode::PlaylistRename => {
+ let cursor_moved_right = self.pl_cursor_pos.saturating_add(1);
+ self.pl_cursor_pos = self.clamp_cursor(cursor_moved_right);
+ }
+ InputMode::Editing => {
+ let cursor_moved_right = self.search_cursor_pos.saturating_add(1);
+ self.search_cursor_pos = self.clamp_cursor(cursor_moved_right);
+ }
+ _ => {}
+ }
}
pub fn enter_char(&mut self, new_char: char) {
- self.search_input.insert(self.cursor_position, new_char);
-
- self.move_cursor_right();
+ match self.inputmode {
+ InputMode::PlaylistRename => {
+ self.pl_newname_input.insert(self.pl_cursor_pos, new_char);
+ self.move_cursor_right();
+ }
+ InputMode::Editing => {
+ self.search_input.insert(self.search_cursor_pos, new_char);
+ self.move_cursor_right();
+ }
+ _ => {}
+ }
}
pub fn delete_char(&mut self) {
- let is_not_cursor_leftmost = self.cursor_position != 0;
+ let is_not_cursor_leftmost = match self.inputmode {
+ InputMode::PlaylistRename => self.pl_cursor_pos != 0,
+ InputMode::Editing => self.search_cursor_pos != 0,
+ _ => false,
+ };
+
if is_not_cursor_leftmost {
// Method "remove" is not used on the saved text for deleting the selected char.
// Reason: Using remove on String works on bytes instead of the chars.
// Using remove would require special care because of char boundaries.
- let current_index = self.cursor_position;
- let from_left_to_current_index = current_index - 1;
+ let current_index = match self.inputmode {
+ InputMode::Editing => self.search_cursor_pos,
+ InputMode::PlaylistRename => self.pl_cursor_pos,
+ _ => 0,
+ };
- // Getting all characters before the selected character.
- let before_char_to_delete = self.search_input.chars().take(from_left_to_current_index);
- // Getting all characters after selected character.
- let after_char_to_delete = self.search_input.chars().skip(current_index);
+ let from_left_to_current_index = current_index - 1;
- // Put all characters together except the selected one.
- // By leaving the selected one out, it is forgotten and therefore deleted.
- self.search_input = before_char_to_delete.chain(after_char_to_delete).collect();
- self.move_cursor_left();
+ if self.inputmode == InputMode::PlaylistRename {
+ // Getting all characters before the selected character.
+ let before_char_to_delete = self
+ .pl_newname_input
+ .chars()
+ .take(from_left_to_current_index);
+ // Getting all characters after selected character.
+ let after_char_to_delete = self.pl_newname_input.chars().skip(current_index);
+ // Put all characters together except the selected one.
+ // By leaving the selected one out, it is forgotten and therefore deleted.
+ self.pl_newname_input = before_char_to_delete.chain(after_char_to_delete).collect();
+ self.move_cursor_left();
+ } else if self.inputmode == InputMode::Editing {
+ // Getting all characters before the selected character.
+ let before_char_to_delete =
+ self.search_input.chars().take(from_left_to_current_index);
+ // Getting all characters after selected character.
+ let after_char_to_delete = self.search_input.chars().skip(current_index);
+ // Put all characters together except the selected one.
+ // By leaving the selected one out, it is forgotten and therefore deleted.
+ self.search_input = before_char_to_delete.chain(after_char_to_delete).collect();
+ self.move_cursor_left();
+ }
}
}
pub fn clamp_cursor(&self, new_cursor_pos: usize) -> usize {
- new_cursor_pos.clamp(0, self.search_input.len())
+ match self.inputmode {
+ InputMode::PlaylistRename => new_cursor_pos.clamp(0, self.pl_newname_input.len()),
+ InputMode::Editing => new_cursor_pos.clamp(0, self.search_input.len()),
+ _ => 0,
+ }
}
pub fn reset_cursor(&mut self) {
- self.cursor_position = 0;
+ match self.inputmode {
+ InputMode::Editing => {
+ self.search_cursor_pos = 0;
+ }
+ InputMode::PlaylistRename => {
+ self.pl_cursor_pos = 0;
+ }
+ _ => {}
+ }
}
/// Given time in seconds, convert it to hh:mm:ss
@@ -277,4 +343,14 @@ impl App {
format!("{:02}:{:02}:{:02}", h, m, s)
}
}
+
+ pub fn change_playlist_name(&mut self) -> AppResult<()> {
+ match self.selected_tab {
+ SelectedTab::Playlists => {
+ self.inputmode = InputMode::PlaylistRename;
+ }
+ _ => {}
+ }
+ Ok(())
+ }
}
diff --git a/src/handler.rs b/src/handler.rs
index a5c4edb..86ab7cb 100755
--- a/src/handler.rs
+++ b/src/handler.rs
@@ -92,8 +92,8 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
}
app.search_input.clear();
- app.inputmode = InputMode::Normal;
app.reset_cursor();
+ app.inputmode = InputMode::Normal;
}
KeyCode::Backspace => {
@@ -110,10 +110,41 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
_ => {}
}
+ } else if app.inputmode == InputMode::PlaylistRename {
+ match key_event.code {
+ KeyCode::Esc => {
+ app.pl_newname_input.clear();
+ app.reset_cursor();
+ app.inputmode = InputMode::Normal;
+ }
+ KeyCode::Char(to_insert) => {
+ app.enter_char(to_insert);
+ }
+ KeyCode::Enter => {
+ app.conn.conn.pl_rename(app.pl_list.list.get(app.pl_list.index).unwrap(), &app.pl_newname_input)?;
+ app.pl_list.list = App::get_playlist(&mut app.conn.conn)?;
+ app.pl_newname_input.clear();
+ app.reset_cursor();
+ app.inputmode = InputMode::Normal;
+ }
+
+ KeyCode::Backspace => {
+ app.delete_char();
+ }
- // Playlist popup keybinds
- //
- // Keybind for when the "append to playlist" popup is visible
+ KeyCode::Left => {
+ app.move_cursor_left();
+ }
+
+ KeyCode::Right => {
+ app.move_cursor_right();
+ }
+
+ _ => {}
+ }
+ // Playlist popup keybinds
+ //
+ // Keybind for when the "append to playlist" popup is visible
} else if app.playlist_popup {
match key_event.code {
KeyCode::Char('q') | KeyCode::Esc => {
@@ -358,7 +389,11 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
// Search for songs
KeyCode::Char('/') => {
- app.inputmode = InputMode::toggle_editing_states(&app.inputmode);
+ if app.inputmode == InputMode::Normal {
+ app.inputmode = InputMode::Editing;
+ } else {
+ app.inputmode = InputMode::Normal;
+ }
}
// Remove from Current Playlsit
@@ -366,9 +401,6 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
app.handle_add_or_remove_from_current_playlist()?;
}
- // Change playlist name
- KeyCode::Char('e') => if app.selected_tab == SelectedTab::Playlists {},
-
// go to top of list
KeyCode::Char('g') => match app.selected_tab {
SelectedTab::Queue => app.queue_list.index = 0,
@@ -384,6 +416,9 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
}
SelectedTab::Playlists => app.pl_list.index = app.pl_list.list.len() - 1,
},
+
+ // Change playlist name
+ KeyCode::Char('e') => app.change_playlist_name()?,
_ => {}
}
}
diff --git a/src/ui.rs b/src/ui.rs
index 9acf6a1..51a0fed 100755
--- a/src/ui.rs
+++ b/src/ui.rs
@@ -11,15 +11,7 @@ use ratatui::{
pub enum InputMode {
Editing,
Normal,
-}
-
-impl InputMode {
- pub fn toggle_editing_states(state: &InputMode) -> InputMode {
- match state {
- InputMode::Editing => return InputMode::Normal,
- InputMode::Normal => return InputMode::Editing,
- };
- }
+ PlaylistRename,
}
/// Renders the user interface widgets
@@ -32,7 +24,6 @@ pub fn render(app: &mut App, frame: &mut Frame) {
match app.selected_tab {
SelectedTab::Queue => draw_queue(frame, app, layout[0]),
- // SelectedTab::Playlists => draw_playlists(frame, app, layout[0]),
SelectedTab::Playlists => draw_playlist_viewer(frame, app, layout[0]),
SelectedTab::DirectoryBrowser => draw_directory_browser(frame, app, layout[0]),
}
@@ -44,6 +35,9 @@ pub fn render(app: &mut App, frame: &mut Frame) {
InputMode::Editing => {
draw_search_bar(frame, app, layout[1]);
}
+ InputMode::PlaylistRename => {
+ draw_rename_playlist(frame, app, layout[1]);
+ }
}
if app.playlist_popup {
@@ -254,27 +248,18 @@ fn draw_queue(frame: &mut Frame, app: &mut App, size: Rect) {
frame.render_stateful_widget(table, size, &mut state);
}
-
// Draw search bar
fn draw_search_bar(frame: &mut Frame, app: &mut App, size: Rect) {
- match app.inputmode {
- InputMode::Normal =>
- // Hide the cursor. `Frame` does this by default, so we don't need to do anything here
- {}
-
- InputMode::Editing => {
- // Make the cursor visible and ask ratatui to put it at the specified coordinates after
- // rendering
- #[allow(clippy::cast_possible_truncation)]
- frame.set_cursor(
- // Draw the cursor at the current position in the input field.
- // This position is can be controlled via the left and right arrow key
- size.x + app.cursor_position as u16 + 2,
- // Move one line down, from the border to the input line
- size.y + 1,
- );
- }
- }
+ // Make the cursor visible and ask ratatui to put it at the specified coordinates after
+ // rendering
+ #[allow(clippy::cast_possible_truncation)]
+ frame.set_cursor(
+ // Draw the cursor at the current position in the input field.
+ // This position is can be controlled via the left and right arrow key
+ size.x + app.search_cursor_pos as u16 + 2,
+ // Move one line down, from the border to the input line
+ size.y + 1,
+ );
let input = Paragraph::new("/".to_string() + &app.search_input)
.style(Style::default())
@@ -399,7 +384,7 @@ fn draw_playlist_viewer(frame: &mut Frame, app: &mut App, area: Rect) {
let table = Table::new(
rows,
vec![
- Constraint::Percentage(40),
+ Constraint::Min(40),
Constraint::Percentage(40),
Constraint::Percentage(20),
],
@@ -439,6 +424,26 @@ fn draw_add_to_playlist(frame: &mut Frame, app: &mut App, area: Rect) {
frame.render_stateful_widget(list, area, &mut state);
}
+fn draw_rename_playlist(frame: &mut Frame, app: &mut App, area: Rect) {
+ #[allow(clippy::cast_possible_truncation)]
+ frame.set_cursor(
+ // Draw the cursor at the current position in the input field.
+ // This position is can be controlled via the left and right arrow key
+ area.x + app.pl_cursor_pos as u16 + 2,
+ // Move one line down, from the border to the input line
+ area.y + 1,
+ );
+
+ let input = Paragraph::new("/".to_string() + &app.pl_newname_input)
+ .style(Style::default())
+ .block(
+ Block::default()
+ .borders(Borders::ALL)
+ .title("Enter New Name: ".bold().green()),
+ );
+ frame.render_widget(input, area);
+}
+
fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
let popup_layout = Layout::vertical([
Constraint::Percentage((100 - percent_y) / 2),