Implement multi track queue, replace, append, insert

This commit is contained in:
chmanie 2023-06-09 18:47:27 +02:00
parent 06c02ac75c
commit 773cb511e0
2 changed files with 123 additions and 50 deletions

View File

@ -31,7 +31,7 @@ use std::{
collections::HashMap, collections::HashMap,
error::Error, error::Error,
fmt, io, fmt, io,
ops::Div, ops::{Div, IndexMut},
println, thread, println, thread,
time::{Duration, Instant}, time::{Duration, Instant},
vec, 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(94, 129, 172);
const COLOR_PRIMARY_DARK: Color = Color::Rgb(59, 66, 82); const COLOR_PRIMARY_DARK: Color = Color::Rgb(59, 66, 82);
const COLOR_SECONDARY: Color = Color::Rgb(180, 142, 173); 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); // const COLOR_BRIGHT: Color = Color::Rgb(216, 222, 233);
trait ListView { trait ListView {
@ -162,6 +164,8 @@ struct UiItem {
uuid: String, uuid: String,
title: String, title: String,
kind: UiItemKind, kind: UiItemKind,
marked: bool,
is_queable: bool,
} }
struct QueueView { struct QueueView {
@ -197,12 +201,10 @@ impl QueueView {
self.tx.send(MessageFromUi::SetCurrentTrack(pos)); self.tx.send(MessageFromUi::SetCurrentTrack(pos));
} }
} }
fn insert_track(&mut self) {
todo!();
}
fn remove_track(&mut self) { fn remove_track(&mut self) {
if let Some(pos) = self.selected() { 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) { fn update_position(&mut self, pos: usize) {
@ -218,6 +220,8 @@ impl QueueView {
uuid: t.uuid.clone(), uuid: t.uuid.clone(),
title: format!("{} - {}", t.artist, t.title), title: format!("{} - {}", t.artist, t.title),
kind: UiItemKind::Track, kind: UiItemKind::Track,
marked: false,
is_queable: false,
}) })
.collect(); .collect();
@ -256,9 +260,18 @@ impl ListView for LibraryView {
} }
impl LibraryView { impl LibraryView {
fn get_selected(&self) -> Option<&UiItem> { fn get_selected(&self) -> Option<Vec<String>> {
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() { if let Some(idx) = self.list_state.selected() {
return Some(&self.list[idx]); return Some(vec![self.list[idx].uuid.to_string()]);
} }
None None
} }
@ -268,7 +281,8 @@ impl LibraryView {
} }
} }
fn dive(&mut self) { 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 { if let UiItemKind::Node = item.kind {
self.tx self.tx
.send(MessageFromUi::GetLibraryNode(item.uuid.clone())); .send(MessageFromUi::GetLibraryNode(item.uuid.clone()));
@ -276,23 +290,57 @@ impl LibraryView {
} }
} }
fn queue_append(&mut self) { fn queue_append(&mut self) {
if let Some(item) = self.get_selected() { if let Some(items) = self.get_selected() {
self.tx.send(MessageFromUi::AppendTrack(item.uuid.clone())); match self.tx.send(MessageFromUi::AppendTracks(items)) {
Ok(_) => self.remove_marks(),
Err(_) => { /* FIXME: warn */ }
}
} }
} }
fn queue_queue(&mut self) { fn queue_queue(&mut self) {
if let Some(item) = self.get_selected() { if let Some(items) = self.get_selected() {
self.tx.send(MessageFromUi::QueueTrack(item.uuid.clone())); match self.tx.send(MessageFromUi::QueueTracks(items)) {
Ok(_) => self.remove_marks(),
Err(_) => { /* FIXME: warn */ }
}
} }
} }
fn queue_replace(&mut self) { fn queue_replace(&mut self) {
if let Some(item) = self.get_selected() { if let Some(items) = self.get_selected() {
self.tx.send(MessageFromUi::ReplaceQueue(item.uuid.clone())); 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 { fn prev_selected(&self) -> usize {
*self.positions.get(&self.uuid).unwrap_or(&0) *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) { fn update(&mut self, node: LibraryNode) {
if node.tracks.is_empty() && node.children.is_empty() { if node.tracks.is_empty() && node.children.is_empty() {
return; return;
@ -312,6 +360,8 @@ impl LibraryView {
uuid: t.uuid.clone(), uuid: t.uuid.clone(),
title: format!("{} - {}", t.artist, t.title), title: format!("{} - {}", t.artist, t.title),
kind: UiItemKind::Track, kind: UiItemKind::Track,
marked: false,
is_queable: true,
}) })
.collect(); .collect();
} else { } else {
@ -323,6 +373,8 @@ impl LibraryView {
uuid: c.uuid.clone(), uuid: c.uuid.clone(),
title: c.title.clone(), title: c.title.clone(),
kind: UiItemKind::Node, kind: UiItemKind::Node,
marked: false,
is_queable: c.is_queable,
}) })
.collect(); .collect();
} }
@ -416,11 +468,11 @@ enum MessageToUi {
// FIXME: Rename this // FIXME: Rename this
enum MessageFromUi { enum MessageFromUi {
GetLibraryNode(String), GetLibraryNode(String),
AppendTrack(String), AppendTracks(Vec<String>),
QueueTrack(String), QueueTracks(Vec<String>),
InsertTrack(String, usize), InsertTracks(Vec<String>, usize),
RemoveTrack(usize), RemoveTracks(Vec<usize>),
ReplaceQueue(String), ReplaceQueue(Vec<String>),
NextTrack, NextTrack,
PrevTrack, PrevTrack,
RestartTrack, RestartTrack,
@ -443,20 +495,20 @@ async fn poll(
tx.send(MessageToUi::ReplaceLibraryNode(node.clone())); tx.send(MessageToUi::ReplaceLibraryNode(node.clone()));
} }
}, },
MessageFromUi::AppendTrack(uuid) => { MessageFromUi::AppendTracks(uuids) => {
rpc_client.append_track(&uuid).await? rpc_client.append_tracks(uuids).await?
} }
MessageFromUi::QueueTrack(uuid) => { MessageFromUi::QueueTracks(uuids) => {
rpc_client.queue_track(&uuid).await? rpc_client.queue_tracks(uuids).await?
} }
MessageFromUi::InsertTrack(uuid, pos) => { MessageFromUi::InsertTracks(uuids, pos) => {
rpc_client.insert_track(&uuid, pos).await? rpc_client.insert_tracks(uuids, pos).await?
} }
MessageFromUi::RemoveTrack(pos) => { MessageFromUi::RemoveTracks(positions) => {
rpc_client.remove_track(pos).await? rpc_client.remove_tracks(positions).await?
} }
MessageFromUi::ReplaceQueue(uuid) => { MessageFromUi::ReplaceQueue(uuids) => {
rpc_client.replace_queue(&uuid).await? rpc_client.replace_queue(uuids).await?
} }
MessageFromUi::NextTrack => { MessageFromUi::NextTrack => {
rpc_client.next_track().await? rpc_client.next_track().await?
@ -502,7 +554,7 @@ async fn poll(
async fn orchestrate<'a>( async fn orchestrate<'a>(
(tx, rx): (Sender<MessageToUi>, Receiver<MessageFromUi>), (tx, rx): (Sender<MessageToUi>, Receiver<MessageFromUi>),
) -> Result<(), Box<dyn Error>> { ) -> Result<(), Box<dyn Error>> {
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? { if let Some(root_node) = rpc_client.get_library_node("node:/").await? {
tx.send(MessageToUi::ReplaceLibraryNode(root_node.clone())); tx.send(MessageToUi::ReplaceLibraryNode(root_node.clone()));
@ -599,7 +651,7 @@ fn run_ui(tx: Sender<MessageFromUi>, rx: Receiver<MessageToUi>) {
(_, KeyModifiers::NONE, KeyCode::Char(' ')) => { (_, KeyModifiers::NONE, KeyCode::Char(' ')) => {
tx.send(MessageFromUi::TogglePlay); tx.send(MessageFromUi::TogglePlay);
} }
(_, KeyModifiers::NONE, KeyCode::Char('p')) => { (_, KeyModifiers::NONE, KeyCode::Char('r')) => {
tx.send(MessageFromUi::RestartTrack); tx.send(MessageFromUi::RestartTrack);
} }
(_, KeyModifiers::SHIFT, KeyCode::Char('J')) => { (_, KeyModifiers::SHIFT, KeyCode::Char('J')) => {
@ -650,6 +702,14 @@ fn run_ui(tx: Sender<MessageFromUi>, rx: Receiver<MessageToUi>) {
(UiFocus::Library, KeyModifiers::NONE, KeyCode::Enter) => { (UiFocus::Library, KeyModifiers::NONE, KeyCode::Enter) => {
app.library.queue_replace(); 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')) => { (UiFocus::Queue, KeyModifiers::NONE, KeyCode::Char('g')) => {
app.queue.first(); app.queue.first();
} }
@ -711,7 +771,21 @@ fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) {
.library .library
.list .list
.iter() .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(); .collect();
let library_list = List::new(library_items) let library_list = List::new(library_items)
@ -750,13 +824,14 @@ fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) {
.enumerate() .enumerate()
.map(|(idx, item)| { .map(|(idx, item)| {
let active = idx == app.queue.current_position; let active = idx == app.queue.current_position;
let title = if active { let title = if active {
format!("> {}", item.title) format!("> {}", item.title)
} else { } else {
item.title.to_string() item.title.to_string()
}; };
let style = if active { let style = if active {
Style::default().add_modifier(Modifier::BOLD) Style::default().fg(COLOR_RED).add_modifier(Modifier::BOLD)
} else { } else {
Style::default() Style::default()
}; };

View File

@ -104,43 +104,41 @@ impl RpcClient {
Err(Box::new(RpcClientError::NotFound)) Err(Box::new(RpcClientError::NotFound))
} }
pub async fn append_track(&mut self, uuid: &str) -> Result<(), Box<dyn Error>> { pub async fn append_tracks(&mut self, uuids: Vec<String>) -> Result<(), Box<dyn Error>> {
let append_request = Request::new(AppendRequest { let append_request = Request::new(AppendRequest { uuids });
uuid: vec![uuid.to_string()],
});
self.client.append(append_request).await?; self.client.append(append_request).await?;
Ok(()) Ok(())
} }
pub async fn queue_track(&mut self, uuid: &str) -> Result<(), Box<dyn Error>> { pub async fn queue_tracks(&mut self, uuids: Vec<String>) -> Result<(), Box<dyn Error>> {
let queue_request = Request::new(QueueRequest { let queue_request = Request::new(QueueRequest { uuids });
uuid: vec![uuid.to_string()],
});
self.client.queue(queue_request).await?; self.client.queue(queue_request).await?;
Ok(()) Ok(())
} }
pub async fn insert_track(&mut self, uuid: &str, pos: usize) -> Result<(), Box<dyn Error>> { pub async fn insert_tracks(
&mut self,
uuids: Vec<String>,
pos: usize,
) -> Result<(), Box<dyn Error>> {
let insert_request = Request::new(InsertRequest { let insert_request = Request::new(InsertRequest {
uuid: vec![uuid.to_string()], uuids,
position: pos as u32, position: pos as u32,
}); });
self.client.insert(insert_request).await?; self.client.insert(insert_request).await?;
Ok(()) Ok(())
} }
pub async fn remove_track(&mut self, pos: usize) -> Result<(), Box<dyn Error>> { pub async fn remove_tracks(&mut self, positions: Vec<usize>) -> Result<(), Box<dyn Error>> {
let remove_request = Request::new(RemoveRequest { 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?; self.client.remove(remove_request).await?;
Ok(()) Ok(())
} }
pub async fn replace_queue(&mut self, uuid: &str) -> Result<(), Box<dyn Error>> { pub async fn replace_queue(&mut self, uuids: Vec<String>) -> Result<(), Box<dyn Error>> {
let replace_request = Request::new(ReplaceRequest { let replace_request = Request::new(ReplaceRequest { uuids });
uuid: vec![uuid.to_string()],
});
self.client.replace(replace_request).await?; self.client.replace(replace_request).await?;
Ok(()) Ok(())
} }