From b8e729d81ccc41664a7eb435d615725573b77fb8 Mon Sep 17 00:00:00 2001 From: Mark <> Date: Sat, 1 Mar 2025 12:06:45 +0100 Subject: [PATCH] add reshuffling and update queue ui in client --- musicdb-client/src/gui_queue.rs | 120 ++++++++++++++++---------- musicdb-lib/src/data/cache_manager.rs | 4 +- musicdb-lib/src/data/database.rs | 49 +++++++---- musicdb-lib/src/data/queue.rs | 60 +++++++++++-- musicdb-lib/src/server/mod.rs | 30 ++++--- musicdb-server/src/main.rs | 2 +- musicdb-server/src/web.rs | 30 ++++--- 7 files changed, 195 insertions(+), 100 deletions(-) diff --git a/musicdb-client/src/gui_queue.rs b/musicdb-client/src/gui_queue.rs index b49ae35..d580571 100755 --- a/musicdb-client/src/gui_queue.rs +++ b/musicdb-client/src/gui_queue.rs @@ -59,7 +59,7 @@ impl QueueViewer { QueueContent::Folder(musicdb_lib::data::queue::QueueFolder { index: 0, content: vec![], - name: "in loop".to_string(), + name: String::new(), order: None, }) .into(), @@ -79,7 +79,7 @@ impl QueueViewer { QueueContent::Folder(musicdb_lib::data::queue::QueueFolder { index: 0, content: vec![], - name: "in loop".to_string(), + name: String::new(), order: None, }) .into(), @@ -255,7 +255,7 @@ impl GuiElem for QueueViewer { &mut h, vec![], true, - true, + false, ); let scroll_box = &mut self.c_scroll_box; scroll_box.children = c; @@ -279,20 +279,32 @@ fn queue_gui( target_h: &mut Vec, path: Vec, current: bool, - skip_folder: bool, -) { + skip_first: bool, +) -> Option> { + let mut out = None; + let mut push = |target: &mut Vec<_>, e| { + if skip_first && out.is_none() { + out = Some(e); + } else { + target.push(e); + } + }; + let is_root = path.is_empty(); let cfg = GuiElemCfg::at(Rectangle::from_tuples((depth, 0.0), (1.0, 1.0))); match queue.content() { QueueContent::Song(id) => { if let Some(s) = db.songs().get(id) { - target.push(Box::new(QueueSong::new( - cfg, - path, - s.clone(), - current, - db, - depth_inc_by * 0.33, - ))); + push( + target, + Box::new(QueueSong::new( + cfg, + path, + s.clone(), + current, + db, + depth_inc_by * 0.33, + )), + ); target_h.push(line_height * 1.75); } } @@ -303,15 +315,12 @@ fn queue_gui( name: _, order: _, } = qf; - if !skip_folder { - target.push(Box::new(QueueFolder::new( - cfg.clone(), - path.clone(), - qf.clone(), - current, - ))); - target_h.push(line_height * 0.8); + let mut folder = QueueFolder::new(cfg.clone(), path.clone(), qf.clone(), current); + if skip_first || is_root { + folder.no_ins_before = true; } + push(target, Box::new(folder)); + target_h.push(line_height * 0.8); for (i, q) in qf.iter().enumerate() { let mut p = path.clone(); p.push(i); @@ -328,26 +337,22 @@ fn queue_gui( false, ); } - if !skip_folder { + if !is_root { let mut p1 = path; let p2 = p1.pop().unwrap_or(0) + 1; - target.push(Box::new(QueueIndentEnd::new(cfg, (p1, p2)))); + push(target, Box::new(QueueIndentEnd::new(cfg, (p1, p2)))); target_h.push(line_height * 0.4); } } QueueContent::Loop(_, _, inner) => { let mut p = path.clone(); p.push(0); - let mut p1 = path.clone(); - let p2 = p1.pop().unwrap_or(0) + 1; - target.push(Box::new(QueueLoop::new( - cfg.clone(), - path, - queue.clone(), - current, - ))); - target_h.push(line_height * 0.8); - queue_gui( + let i = target.len(); + push( + target, + Box::new(QueueLoop::new(cfg.clone(), path, queue.clone(), current)), + ); + if let Some(mut inner) = queue_gui( &inner, db, depth, @@ -358,11 +363,17 @@ fn queue_gui( p, current, true, - ); - target.push(Box::new(QueueIndentEnd::new(cfg, (p1, p2)))); - target_h.push(line_height * 0.4); + ) { + inner.config_mut().pos = Rectangle::from_tuples((0.5, 0.0), (1.0, 1.0)); + target[i] + .any_mut() + .downcast_mut::() + .unwrap() + .inner = Some(inner); + } } } + out } struct QueueEmptySpaceDragHandler { @@ -658,6 +669,7 @@ struct QueueFolder { queue: musicdb_lib::data::queue::QueueFolder, current: bool, insert_into: bool, + no_ins_before: bool, mouse: bool, mouse_pos: Vec2, copy: bool, @@ -678,12 +690,7 @@ impl QueueFolder { order, } = &queue; Self { - config: if path.is_empty() { - config - } else { - config.w_mouse().w_keyboard_watch() - } - .w_drag_target(), + config: config.w_mouse().w_keyboard_watch().w_drag_target(), c_name: Label::new( GuiElemCfg::default(), format!( @@ -704,6 +711,7 @@ impl QueueFolder { queue, current, insert_into: false, + no_ins_before: false, mouse: false, mouse_pos: Vec2::ZERO, copy: false, @@ -741,7 +749,8 @@ impl GuiElem for QueueFolder { self } fn draw(&mut self, info: &mut DrawInfo, g: &mut speedy2d::Graphics2D) { - self.insert_into = info.mouse_pos.y > info.pos.top_left().y + info.pos.height() * 0.5; + self.insert_into = self.no_ins_before + || info.mouse_pos.y > info.pos.top_left().y + info.pos.height() * 0.5; if !self.always_copy && info.dragging.is_some() && info.pos.contains(info.mouse_pos) { g.draw_rectangle( if self.insert_into { @@ -789,7 +798,7 @@ impl GuiElem for QueueFolder { return vec![GuiAction::SendToServer(if self.queue.order.is_some() { Action::QueueUnshuffle(self.path.clone()) } else { - Action::QueueShuffle(self.path.clone()) + Action::QueueShuffle(self.path.clone(), 1) })]; } vec![] @@ -816,7 +825,7 @@ impl GuiElem for QueueFolder { _key: Option, _scan: speedy2d::window::KeyScancode, ) -> Vec { - self.copy = modifiers.ctrl(); + self.copy = self.always_copy || modifiers.ctrl(); vec![] } fn dragged(&mut self, e: &mut EventInfo, dragged: Dragging) -> Vec { @@ -923,6 +932,7 @@ struct QueueLoop { copy: bool, always_copy: bool, copy_on_mouse_down: bool, + inner: Option>, } impl QueueLoop { pub fn new(config: GuiElemCfg, path: Vec, queue: Queue, current: bool) -> Self { @@ -948,6 +958,7 @@ impl QueueLoop { copy: false, always_copy: false, copy_on_mouse_down: false, + inner: None, } } fn alwayscopy(mut self) -> Self { @@ -979,7 +990,15 @@ impl GuiElem for QueueLoop { &mut self.config } fn children(&mut self) -> Box + '_> { - Box::new(self.children.iter_mut().map(|v| v.elem_mut())) + if let Some(inner) = &mut self.inner { + Box::new( + [inner.elem_mut()] + .into_iter() + .chain(self.children.iter_mut().map(|v| v.elem_mut())), + ) + } else { + Box::new(self.children.iter_mut().map(|v| v.elem_mut())) + } } fn any(&self) -> &dyn std::any::Any { self @@ -1018,6 +1037,14 @@ impl GuiElem for QueueLoop { info.mouse_pos.y - self.config.pixel_pos.top_left().y, ); } + let pos = Rectangle::new( + *info.pos.top_left(), + Vec2::new( + (info.pos.top_left().x + info.pos.bottom_right().x) / 2.0, + info.pos.bottom_right().y, + ), + ); + let ppos = std::mem::replace(&mut info.pos, pos); generic_queue_draw( info, &self.path, @@ -1025,6 +1052,7 @@ impl GuiElem for QueueLoop { &mut self.mouse, self.copy_on_mouse_down, ); + info.pos = ppos; } fn mouse_down(&mut self, e: &mut EventInfo, button: MouseButton) -> Vec { if button == MouseButton::Left && e.take() { diff --git a/musicdb-lib/src/data/cache_manager.rs b/musicdb-lib/src/data/cache_manager.rs index 19147f8..8c11199 100644 --- a/musicdb-lib/src/data/cache_manager.rs +++ b/musicdb-lib/src/data/cache_manager.rs @@ -89,7 +89,7 @@ impl CacheManager { let mut queue = db.queue.clone(); let queue_current_song = queue.get_current_song().copied(); - queue.advance_index_inner(); + queue.advance_index_inner(&mut Vec::new(), &mut Vec::new()); let queue_next_song = queue.get_current_song().copied(); let mut ids_to_cache = queue_current_song @@ -98,7 +98,7 @@ impl CacheManager { .collect::>(); for _ in 2..songs_to_cache { - queue.advance_index_inner(); + queue.advance_index_inner(&mut Vec::new(), &mut Vec::new()); if let Some(id) = queue.get_current_song() { if !ids_to_cache.contains(id) { ids_to_cache.push(*id); diff --git a/musicdb-lib/src/data/database.rs b/musicdb-lib/src/data/database.rs index 9b5f750..e3fb51f 100755 --- a/musicdb-lib/src/data/database.rs +++ b/musicdb-lib/src/data/database.rs @@ -569,7 +569,7 @@ impl Database { // some commands shouldn't be broadcast. these will broadcast a different command in their specific implementation. match &action { // Will broadcast `QueueSetShuffle` - Action::QueueShuffle(_) => (), + Action::QueueShuffle(_, _) => (), Action::NextSong if self.queue.is_almost_empty() => (), Action::Pause if !self.playing => (), Action::Resume if self.playing => (), @@ -679,26 +679,31 @@ impl Database { } } Action::QueueGoto(index) => Queue::set_index_db(self, &index), - Action::QueueShuffle(path) => { - if let Some(elem) = self.queue.get_item_at_index_mut(&path, 0) { - if let QueueContent::Folder(QueueFolder { - index: _, - content, - name: _, - order: _, - }) = elem.content_mut() - { - let mut ord: Vec = (0..content.len()).collect(); - ord.shuffle(&mut thread_rng()); - self.apply_action_unchecked_seq(Action::QueueSetShuffle(path, ord), client); + Action::QueueShuffle(path, set_index) => { + if !self.is_client() { + if let Some(elem) = self.queue.get_item_at_index_mut(&path, 0) { + if let QueueContent::Folder(QueueFolder { + index: _, + content, + name: _, + order: _, + }) = elem.content_mut() + { + let mut ord: Vec = (0..content.len()).collect(); + ord.shuffle(&mut thread_rng()); + self.apply_action_unchecked_seq( + Action::QueueSetShuffle(path, ord, set_index), + client, + ); + } else { + eprintln!("(QueueShuffle) QueueElement at {path:?} not a folder!"); + } } else { - eprintln!("(QueueShuffle) QueueElement at {path:?} not a folder!"); + eprintln!("(QueueShuffle) No QueueElement at {path:?}"); } - } else { - eprintln!("(QueueShuffle) No QueueElement at {path:?}"); } } - Action::QueueSetShuffle(path, ord) => { + Action::QueueSetShuffle(path, ord, set_index) => { if let Some(elem) = self.queue.get_item_at_index_mut(&path, 0) { if let QueueContent::Folder(QueueFolder { index, @@ -708,8 +713,14 @@ impl Database { }) = elem.content_mut() { if ord.len() == content.len() { - if let Some(ni) = ord.iter().position(|v| *v == *index) { - *index = ni; + match set_index { + 0 => {} + 1 => { + if let Some(ni) = ord.iter().position(|v| *v == *index) { + *index = ni; + } + } + _ => {} } *order = Some(ord); } else { diff --git a/musicdb-lib/src/data/queue.rs b/musicdb-lib/src/data/queue.rs index 27acde9..71b1b19 100755 --- a/musicdb-lib/src/data/queue.rs +++ b/musicdb-lib/src/data/queue.rs @@ -1,6 +1,6 @@ use std::ops::AddAssign; -use crate::load::ToFromBytes; +use crate::{load::ToFromBytes, server::Action}; use super::{database::Database, SongId}; @@ -210,7 +210,11 @@ impl Queue { } pub fn advance_index_db(db: &mut Database) -> bool { - let o = db.queue.advance_index_inner(); + let mut actions = Vec::new(); + let o = db.queue.advance_index_inner(&mut Vec::new(), &mut actions); + for action in actions { + db.apply_action_unchecked_seq(action, None); + } o } pub fn init(&mut self) { @@ -225,20 +229,50 @@ impl Queue { QueueContent::Loop(_, _, inner) => inner.init(), } } - pub fn advance_index_inner(&mut self) -> bool { + pub fn done(&mut self, path: &Vec, actions: &mut Vec) { match &mut self.content { - QueueContent::Song(_) => false, - QueueContent::Folder(folder) => folder.advance_index_inner(), - QueueContent::Loop(total, current, inner) => { - if inner.advance_index_inner() { + QueueContent::Song(..) => {} + QueueContent::Folder(folder) => { + if folder.order.is_some() { + actions.push(Action::QueueShuffle(path.clone(), 0)); + } + } + QueueContent::Loop(_, _, _) => {} + } + } + pub fn advance_index_inner( + &mut self, + path: &mut Vec, + actions: &mut Vec, + ) -> bool { + match &mut self.content { + QueueContent::Song(_) => { + self.done(path, actions); + false + } + QueueContent::Folder(folder) => { + if folder.advance_index_inner(path, actions) { true } else { + self.done(path, actions); + false + } + } + QueueContent::Loop(total, current, inner) => { + path.push(0); + if inner.advance_index_inner(path, actions) { + path.pop(); + true + } else { + inner.done(path, actions); + path.pop(); *current += 1; if *total == 0 || *current < *total { inner.init(); true } else { *current = 0; + self.done(path, actions); false } } @@ -481,12 +515,20 @@ impl QueueFolder { self.content.first() } } - pub fn advance_index_inner(&mut self) -> bool { + pub fn advance_index_inner( + &mut self, + path: &mut Vec, + actions: &mut Vec, + ) -> bool { + let index = self.index; if let Some(c) = self.get_current_mut() { - if c.advance_index_inner() { + path.push(index); + if c.advance_index_inner(path, actions) { + path.pop(); // inner value could advance index, do nothing. true } else { + path.pop(); loop { if self.index + 1 < self.content.len() { // can advance diff --git a/musicdb-lib/src/server/mod.rs b/musicdb-lib/src/server/mod.rs index 5921374..ee6fd93 100755 --- a/musicdb-lib/src/server/mod.rs +++ b/musicdb-lib/src/server/mod.rs @@ -86,8 +86,8 @@ impl Action { | Self::QueueMove(_, _) | Self::QueueMoveInto(_, _) | Self::QueueGoto(_) - | Self::QueueShuffle(_) - | Self::QueueSetShuffle(_, _) + | Self::QueueShuffle(_, _) + | Self::QueueSetShuffle(_, _, _) | Self::QueueUnshuffle(_) | Self::RemoveSong(_) | Self::RemoveAlbum(_) @@ -186,10 +186,12 @@ pub enum Action { /// Take an element from A and add it to the end of the folder B QueueMoveInto(Vec, Vec), QueueGoto(Vec), - // sent by clients when they want to shuffle a folder - QueueShuffle(Vec), - // sent by the server when the folder was shuffled - QueueSetShuffle(Vec, Vec), + /// sent by clients when they want to shuffle a folder + /// last parameter: 0 = don't change index, 1 = set folder index to new index of previously active element + QueueShuffle(Vec, u8), + /// sent by the server when the folder was shuffled + /// last parameter, see QueueShuffle + QueueSetShuffle(Vec, Vec, u8), QueueUnshuffle(Vec), /// .id field is ignored! @@ -615,16 +617,18 @@ impl ToFromBytes for Action { s.write_all(&[BYTE_QUEUE_GOTO])?; index.to_bytes(s)?; } - Self::QueueShuffle(path) => { + Self::QueueShuffle(path, set_index) => { s.write_all(&[BYTE_QUEUE_ACTION])?; s.write_all(&[SUBBYTE_ACTION_SHUFFLE])?; path.to_bytes(s)?; + set_index.to_bytes(s)?; } - Self::QueueSetShuffle(path, map) => { + Self::QueueSetShuffle(path, map, set_index) => { s.write_all(&[BYTE_QUEUE_ACTION])?; s.write_all(&[SUBBYTE_ACTION_SET_SHUFFLE])?; path.to_bytes(s)?; map.to_bytes(s)?; + set_index.to_bytes(s)?; } Self::QueueUnshuffle(path) => { s.write_all(&[BYTE_QUEUE_ACTION])?; @@ -813,8 +817,10 @@ impl ToFromBytes for Action { BYTE_QUEUE_MOVE_INTO => Self::QueueMoveInto(from_bytes!(), from_bytes!()), BYTE_QUEUE_GOTO => Self::QueueGoto(from_bytes!()), BYTE_QUEUE_ACTION => match s.read_byte()? { - SUBBYTE_ACTION_SHUFFLE => Self::QueueShuffle(from_bytes!()), - SUBBYTE_ACTION_SET_SHUFFLE => Self::QueueSetShuffle(from_bytes!(), from_bytes!()), + SUBBYTE_ACTION_SHUFFLE => Self::QueueShuffle(from_bytes!(), from_bytes!()), + SUBBYTE_ACTION_SET_SHUFFLE => { + Self::QueueSetShuffle(from_bytes!(), from_bytes!(), from_bytes!()) + } SUBBYTE_ACTION_UNSHUFFLE => Self::QueueUnshuffle(from_bytes!()), _ => { eprintln!( @@ -943,8 +949,8 @@ fn test_to_from_bytes() { Action::QueueMove(vec![], vec![]), Action::QueueMoveInto(vec![], vec![]), Action::QueueGoto(vec![]), - Action::QueueShuffle(vec![]), - Action::QueueSetShuffle(vec![], vec![]), + Action::QueueShuffle(vec![], 1), + Action::QueueSetShuffle(vec![], vec![], 0), Action::QueueUnshuffle(vec![]), // Action::AddSong(Song, Req), // Action::AddAlbum(Album, Req), diff --git a/musicdb-server/src/main.rs b/musicdb-server/src/main.rs index 5565999..ff5d16d 100755 --- a/musicdb-server/src/main.rs +++ b/musicdb-server/src/main.rs @@ -148,7 +148,7 @@ fn main() { let mut con = TcpStream::connect(addr).unwrap(); writeln!(con, "main").unwrap(); loop { - let mut cmd = musicdb_lib::server::Command::from_bytes(&mut con).unwrap(); + let cmd = musicdb_lib::server::Command::from_bytes(&mut con).unwrap(); use musicdb_lib::server::Action::{self, *}; fn sanitize_actions(action: Action) -> Option { match action { diff --git a/musicdb-server/src/web.rs b/musicdb-server/src/web.rs index 0d0778a..4197114 100755 --- a/musicdb-server/src/web.rs +++ b/musicdb-server/src/web.rs @@ -9,10 +9,8 @@ use musicdb_lib::data::song::Song; use musicdb_lib::data::SongId; use musicdb_lib::server::{Action, Command, Req}; use rocket::futures::{SinkExt, StreamExt}; -use rocket::http::ContentType; use rocket::response::content::RawHtml; -use rocket::response::Responder; -use rocket::{get, routes, Config, Response, State}; +use rocket::{get, routes, Config, State}; use rocket_seek_stream::SeekStream; use rocket_ws::{Message, WebSocket}; use tokio::select; @@ -53,7 +51,6 @@ struct Data { #[get("/")] fn index(data: &State) -> RawHtml { - dbg!(()); let script = r#""#; + +"#; let script2 = r#""#; +updateLivePlaybackIds(); +runLoop(); + +"#; let buttons = ""; let search = "
"; @@ -272,7 +273,6 @@ runLoop();"#; let now_playing = gen_now_playing(&db); let mut queue = String::new(); gen_queue_html(&db.queue, &mut queue, &db); - dbg!(&queue); drop(db); RawHtml(format!( "{HTML_START}MusicDb{script}{HTML_SEP}
no javascript? reload to see updated information.
{now_playing}
{buttons}
{playback_live}
{search}
{queue}
{script2}{HTML_END}", @@ -301,8 +301,15 @@ fn now_playing_ids(data: &State) -> String { } } -#[get("/song//")] -fn song(data: &State, id: SongId, name: String) -> Option { +#[get("/song/")] +fn song1(data: &State, id: SongId) -> Option { + song(data, id) +} +#[get("/song//<_>")] +fn song2(data: &State, id: SongId) -> Option { + song(data, id) +} +fn song(data: &State, id: SongId) -> Option { let db = data.db.lock().unwrap(); if let Some(song) = db.get_song(&id) { song.cached_data().cache_data_start_thread(&*db, song); @@ -901,7 +908,7 @@ pub fn main( .unwrap() .block_on(async_main(data, addr)); } -pub async fn async_main(data: Data, addr: SocketAddr) { +async fn async_main(data: Data, addr: SocketAddr) { rocket::build() .configure(Config { address: addr.ip(), @@ -925,7 +932,8 @@ pub async fn async_main(data: Data, addr: SocketAddr) { search, now_playing_html, now_playing_ids, - song, + song1, + song2, queue_html, ], )