From 773cb511e0ff1adef9a45a0e46ffa98a2fef5c49 Mon Sep 17 00:00:00 2001 From: chmanie Date: Fri, 9 Jun 2023 18:47:27 +0200 Subject: [PATCH] Implement multi track queue, replace, append, insert --- cbd-tui/src/main.rs | 143 +++++++++++++++++++++++++++++++++----------- cbd-tui/src/rpc.rs | 30 +++++----- 2 files changed, 123 insertions(+), 50 deletions(-) diff --git a/cbd-tui/src/main.rs b/cbd-tui/src/main.rs index c69731c..c1edca1 100644 --- a/cbd-tui/src/main.rs +++ b/cbd-tui/src/main.rs @@ -31,7 +31,7 @@ use std::{ collections::HashMap, error::Error, fmt, io, - ops::Div, + ops::{Div, IndexMut}, println, thread, time::{Duration, Instant}, vec, @@ -46,7 +46,9 @@ const COLOR_PRIMARY: Color = Color::Rgb(129, 161, 193); // const COLOR_PRIMARY_DARK: Color = Color::Rgb(94, 129, 172); const COLOR_PRIMARY_DARK: Color = Color::Rgb(59, 66, 82); const COLOR_SECONDARY: Color = Color::Rgb(180, 142, 173); -// const COLOR_RED: Color = Color::Rgb(191, 97, 106); +const COLOR_RED: Color = Color::Rgb(191, 97, 106); +const COLOR_GREEN: Color = Color::Rgb(163, 190, 140); +// const COLOR_ORANGE: Color = Color::Rgb(208, 135, 112); // const COLOR_BRIGHT: Color = Color::Rgb(216, 222, 233); trait ListView { @@ -162,6 +164,8 @@ struct UiItem { uuid: String, title: String, kind: UiItemKind, + marked: bool, + is_queable: bool, } struct QueueView { @@ -197,12 +201,10 @@ impl QueueView { self.tx.send(MessageFromUi::SetCurrentTrack(pos)); } } - fn insert_track(&mut self) { - todo!(); - } fn remove_track(&mut self) { if let Some(pos) = self.selected() { - self.tx.send(MessageFromUi::RemoveTrack(pos)); + // FIXME: mark multiple tracks on queue and remove them + self.tx.send(MessageFromUi::RemoveTracks(vec![pos])); } } fn update_position(&mut self, pos: usize) { @@ -218,6 +220,8 @@ impl QueueView { uuid: t.uuid.clone(), title: format!("{} - {}", t.artist, t.title), kind: UiItemKind::Track, + marked: false, + is_queable: false, }) .collect(); @@ -256,9 +260,18 @@ impl ListView for LibraryView { } impl LibraryView { - fn get_selected(&self) -> Option<&UiItem> { + fn get_selected(&self) -> Option> { + if self.list.iter().any(|i| i.marked) { + return Some( + self.list + .iter() + .filter(|i| i.marked) + .map(|i| i.uuid.to_string()) + .collect(), + ); + } if let Some(idx) = self.list_state.selected() { - return Some(&self.list[idx]); + return Some(vec![self.list[idx].uuid.to_string()]); } None } @@ -268,7 +281,8 @@ impl LibraryView { } } fn dive(&mut self) { - if let Some(item) = self.get_selected() { + if let Some(idx) = self.list_state.selected() { + let item = &self.list[idx]; if let UiItemKind::Node = item.kind { self.tx .send(MessageFromUi::GetLibraryNode(item.uuid.clone())); @@ -276,23 +290,57 @@ impl LibraryView { } } fn queue_append(&mut self) { - if let Some(item) = self.get_selected() { - self.tx.send(MessageFromUi::AppendTrack(item.uuid.clone())); + if let Some(items) = self.get_selected() { + match self.tx.send(MessageFromUi::AppendTracks(items)) { + Ok(_) => self.remove_marks(), + Err(_) => { /* FIXME: warn */ } + } } } fn queue_queue(&mut self) { - if let Some(item) = self.get_selected() { - self.tx.send(MessageFromUi::QueueTrack(item.uuid.clone())); + if let Some(items) = self.get_selected() { + match self.tx.send(MessageFromUi::QueueTracks(items)) { + Ok(_) => self.remove_marks(), + Err(_) => { /* FIXME: warn */ } + } } } fn queue_replace(&mut self) { - if let Some(item) = self.get_selected() { - self.tx.send(MessageFromUi::ReplaceQueue(item.uuid.clone())); + if let Some(items) = self.get_selected() { + match self.tx.send(MessageFromUi::ReplaceQueue(items)) { + Ok(_) => self.remove_marks(), + Err(_) => { /* FIXME: warn */ } + } + } + } + fn queue_insert(&mut self, pos: usize) { + if let Some(items) = self.get_selected() { + match self.tx.send(MessageFromUi::InsertTracks(items, pos)) { + Ok(_) => self.remove_marks(), + Err(_) => { /* FIXME: warn */ } + } } } fn prev_selected(&self) -> usize { *self.positions.get(&self.uuid).unwrap_or(&0) } + fn toggle_mark(&mut self) { + if let Some(idx) = self.list_state.selected() { + let mut item = &mut self.list[idx]; + if !item.is_queable { + return; + } + item.marked = !item.marked; + } + } + fn remove_marks(&mut self) { + if self.list.iter().any(|i| i.marked) { + self.list + .iter_mut() + .filter(|i| i.marked) + .for_each(|i| i.marked = false); + } + } fn update(&mut self, node: LibraryNode) { if node.tracks.is_empty() && node.children.is_empty() { return; @@ -312,6 +360,8 @@ impl LibraryView { uuid: t.uuid.clone(), title: format!("{} - {}", t.artist, t.title), kind: UiItemKind::Track, + marked: false, + is_queable: true, }) .collect(); } else { @@ -323,6 +373,8 @@ impl LibraryView { uuid: c.uuid.clone(), title: c.title.clone(), kind: UiItemKind::Node, + marked: false, + is_queable: c.is_queable, }) .collect(); } @@ -416,11 +468,11 @@ enum MessageToUi { // FIXME: Rename this enum MessageFromUi { GetLibraryNode(String), - AppendTrack(String), - QueueTrack(String), - InsertTrack(String, usize), - RemoveTrack(usize), - ReplaceQueue(String), + AppendTracks(Vec), + QueueTracks(Vec), + InsertTracks(Vec, usize), + RemoveTracks(Vec), + ReplaceQueue(Vec), NextTrack, PrevTrack, RestartTrack, @@ -443,20 +495,20 @@ async fn poll( tx.send(MessageToUi::ReplaceLibraryNode(node.clone())); } }, - MessageFromUi::AppendTrack(uuid) => { - rpc_client.append_track(&uuid).await? + MessageFromUi::AppendTracks(uuids) => { + rpc_client.append_tracks(uuids).await? } - MessageFromUi::QueueTrack(uuid) => { - rpc_client.queue_track(&uuid).await? + MessageFromUi::QueueTracks(uuids) => { + rpc_client.queue_tracks(uuids).await? } - MessageFromUi::InsertTrack(uuid, pos) => { - rpc_client.insert_track(&uuid, pos).await? + MessageFromUi::InsertTracks(uuids, pos) => { + rpc_client.insert_tracks(uuids, pos).await? } - MessageFromUi::RemoveTrack(pos) => { - rpc_client.remove_track(pos).await? + MessageFromUi::RemoveTracks(positions) => { + rpc_client.remove_tracks(positions).await? } - MessageFromUi::ReplaceQueue(uuid) => { - rpc_client.replace_queue(&uuid).await? + MessageFromUi::ReplaceQueue(uuids) => { + rpc_client.replace_queue(uuids).await? } MessageFromUi::NextTrack => { rpc_client.next_track().await? @@ -502,7 +554,7 @@ async fn poll( async fn orchestrate<'a>( (tx, rx): (Sender, Receiver), ) -> Result<(), Box> { - let mut rpc_client = rpc::RpcClient::connect("http://192.168.178.32:50051").await?; + let mut rpc_client = rpc::RpcClient::connect("http://192.168.1.149:50051").await?; if let Some(root_node) = rpc_client.get_library_node("node:/").await? { tx.send(MessageToUi::ReplaceLibraryNode(root_node.clone())); @@ -599,7 +651,7 @@ fn run_ui(tx: Sender, rx: Receiver) { (_, KeyModifiers::NONE, KeyCode::Char(' ')) => { tx.send(MessageFromUi::TogglePlay); } - (_, KeyModifiers::NONE, KeyCode::Char('p')) => { + (_, KeyModifiers::NONE, KeyCode::Char('r')) => { tx.send(MessageFromUi::RestartTrack); } (_, KeyModifiers::SHIFT, KeyCode::Char('J')) => { @@ -650,6 +702,14 @@ fn run_ui(tx: Sender, rx: Receiver) { (UiFocus::Library, KeyModifiers::NONE, KeyCode::Enter) => { app.library.queue_replace(); } + (UiFocus::Library, KeyModifiers::NONE, KeyCode::Char('s')) => { + app.library.toggle_mark(); + } + (UiFocus::Queue, KeyModifiers::NONE, KeyCode::Char('p')) => { + if let Some(selected) = app.queue.selected() { + app.library.queue_insert(selected); + } + } (UiFocus::Queue, KeyModifiers::NONE, KeyCode::Char('g')) => { app.queue.first(); } @@ -711,7 +771,21 @@ fn ui(f: &mut Frame, app: &mut App) { .library .list .iter() - .map(|i| ListItem::new(Span::from(i.title.to_string()))) + .map(|i| { + let text = if i.marked { + format!("* {}", i.title) + } else { + i.title.to_string() + }; + let style = if i.marked { + Style::default() + .fg(COLOR_GREEN) + .add_modifier(Modifier::BOLD) + } else { + Style::default() + }; + return ListItem::new(Span::from(text)).style(style); + }) .collect(); let library_list = List::new(library_items) @@ -750,13 +824,14 @@ fn ui(f: &mut Frame, app: &mut App) { .enumerate() .map(|(idx, item)| { let active = idx == app.queue.current_position; + let title = if active { format!("> {}", item.title) } else { item.title.to_string() }; let style = if active { - Style::default().add_modifier(Modifier::BOLD) + Style::default().fg(COLOR_RED).add_modifier(Modifier::BOLD) } else { Style::default() }; diff --git a/cbd-tui/src/rpc.rs b/cbd-tui/src/rpc.rs index 06d574b..928a971 100644 --- a/cbd-tui/src/rpc.rs +++ b/cbd-tui/src/rpc.rs @@ -104,43 +104,41 @@ impl RpcClient { Err(Box::new(RpcClientError::NotFound)) } - pub async fn append_track(&mut self, uuid: &str) -> Result<(), Box> { - let append_request = Request::new(AppendRequest { - uuid: vec![uuid.to_string()], - }); + pub async fn append_tracks(&mut self, uuids: Vec) -> Result<(), Box> { + let append_request = Request::new(AppendRequest { uuids }); self.client.append(append_request).await?; Ok(()) } - pub async fn queue_track(&mut self, uuid: &str) -> Result<(), Box> { - let queue_request = Request::new(QueueRequest { - uuid: vec![uuid.to_string()], - }); + pub async fn queue_tracks(&mut self, uuids: Vec) -> Result<(), Box> { + let queue_request = Request::new(QueueRequest { uuids }); self.client.queue(queue_request).await?; Ok(()) } - pub async fn insert_track(&mut self, uuid: &str, pos: usize) -> Result<(), Box> { + pub async fn insert_tracks( + &mut self, + uuids: Vec, + pos: usize, + ) -> Result<(), Box> { let insert_request = Request::new(InsertRequest { - uuid: vec![uuid.to_string()], + uuids, position: pos as u32, }); self.client.insert(insert_request).await?; Ok(()) } - pub async fn remove_track(&mut self, pos: usize) -> Result<(), Box> { + pub async fn remove_tracks(&mut self, positions: Vec) -> Result<(), Box> { let remove_request = Request::new(RemoveRequest { - positions: vec![pos as u32], + positions: positions.iter().map(|p| *p as u32).collect(), }); self.client.remove(remove_request).await?; Ok(()) } - pub async fn replace_queue(&mut self, uuid: &str) -> Result<(), Box> { - let replace_request = Request::new(ReplaceRequest { - uuid: vec![uuid.to_string()], - }); + pub async fn replace_queue(&mut self, uuids: Vec) -> Result<(), Box> { + let replace_request = Request::new(ReplaceRequest { uuids }); self.client.replace(replace_request).await?; Ok(()) }