diff --git a/musicdb-client/src/gui.rs b/musicdb-client/src/gui.rs index a6fb5b7..d26bc03 100755 --- a/musicdb-client/src/gui.rs +++ b/musicdb-client/src/gui.rs @@ -365,6 +365,8 @@ impl Gui { | Command::QueueAdd(..) | Command::QueueInsert(..) | Command::QueueRemove(..) + | Command::QueueMove(..) + | Command::QueueMoveInto(..) | Command::QueueGoto(..) | Command::QueueShuffle(..) | Command::QueueSetShuffle(..) @@ -1130,7 +1132,7 @@ pub enum Dragging { Artist(ArtistId), Album(AlbumId), Song(SongId), - Queue(Queue), + Queue(Result>), Queues(Vec), } pub enum SpecificGuiElem { @@ -1430,14 +1432,25 @@ impl WindowHandler for Gui { } fn on_mouse_button_up(&mut self, helper: &mut WindowHelper, button: MouseButton) { if self.dragging.is_some() { - if let Some(a) = self.gui._release_drag( - &mut self.dragging.take().map(|v| v.0), - self.mouse_pos.clone(), - ) { + let (dr, _) = self.dragging.take().unwrap(); + let mut opt = Some(dr); + if let Some(a) = self.gui._release_drag(&mut opt, self.mouse_pos.clone()) { for a in a { self.exec_gui_action(a) } } + if let Some(dr) = opt { + match dr { + Dragging::Artist(_) + | Dragging::Album(_) + | Dragging::Song(_) + | Dragging::Queue(Ok(_)) + | Dragging::Queues(_) => (), + Dragging::Queue(Err(path)) => { + self.exec_gui_action(GuiAction::SendToServer(Command::QueueRemove(path))) + } + } + } } if let Some(a) = self .gui diff --git a/musicdb-client/src/gui_queue.rs b/musicdb-client/src/gui_queue.rs index b4f7aca..bf91983 100755 --- a/musicdb-client/src/gui_queue.rs +++ b/musicdb-client/src/gui_queue.rs @@ -16,7 +16,7 @@ use speedy2d::{ use crate::{ gui::{Dragging, DrawInfo, GuiAction, GuiElem, GuiElemCfg}, - gui_base::{Button, Panel, ScrollBox, ScrollBoxSizeUnit}, + gui_base::{Panel, ScrollBox}, gui_text::{self, AdvancedLabel, Label, TextField}, }; @@ -400,25 +400,33 @@ impl GuiElem for QueueEmptySpaceDragHandler { self } fn dragged(&mut self, dragged: Dragging) -> Vec { - dragged_add_to_queue(dragged, |q| Command::QueueAdd(vec![], q)) + dragged_add_to_queue( + dragged, + (), + |_, q| Command::QueueAdd(vec![], q), + |_, q| Command::QueueMoveInto(q, vec![]), + ) } } fn generic_queue_draw( info: &mut DrawInfo, path: &Vec, + queue: impl FnOnce() -> Queue, mouse: &mut bool, copy_on_mouse_down: bool, -) -> bool { +) { if *mouse && !info.pos.contains(info.mouse_pos) { + // mouse left our element *mouse = false; - if !copy_on_mouse_down { - info.actions - .push(GuiAction::SendToServer(Command::QueueRemove(path.clone()))); - } - true - } else { - false + info.actions.push(GuiAction::SetDragging(Some(( + Dragging::Queue(if copy_on_mouse_down { + Ok(queue()) + } else { + Err(path.clone()) + }), + None, + )))); } } @@ -594,12 +602,13 @@ impl GuiElem for QueueSong { info.mouse_pos.y - self.config.pixel_pos.top_left().y, ); } - if generic_queue_draw(info, &self.path, &mut self.mouse, self.copy_on_mouse_down) { - info.actions.push(GuiAction::SetDragging(Some(( - Dragging::Queue(QueueContent::Song(self.song.id).into()), - None, - )))); - } + generic_queue_draw( + info, + &self.path, + || QueueContent::Song(self.song.id).into(), + &mut self.mouse, + self.copy_on_mouse_down, + ); } fn key_watch( &mut self, @@ -613,15 +622,26 @@ impl GuiElem for QueueSong { } fn dragged(&mut self, dragged: Dragging) -> Vec { if !self.always_copy { - let mut p = self.path.clone(); let insert_below = self.insert_below; - dragged_add_to_queue(dragged, move |q| { - if let Some(j) = p.pop() { - Command::QueueInsert(p.clone(), if insert_below { j + 1 } else { j }, q) - } else { - Command::QueueAdd(p.clone(), q) - } - }) + dragged_add_to_queue( + dragged, + self.path.clone(), + move |mut p: Vec, q| { + if let Some(j) = p.pop() { + Command::QueueInsert(p, if insert_below { j + 1 } else { j }, q) + } else { + Command::QueueAdd(p, q) + } + }, + move |mut p, q| { + if insert_below { + if let Some(l) = p.last_mut() { + *l += 1; + } + } + Command::QueueMove(q, p) + }, + ) } else { vec![] } @@ -747,12 +767,13 @@ impl GuiElem for QueueFolder { info.mouse_pos.y - self.config.pixel_pos.top_left().y, ); } - if generic_queue_draw(info, &self.path, &mut self.mouse, self.copy_on_mouse_down) { - info.actions.push(GuiAction::SetDragging(Some(( - Dragging::Queue(QueueContent::Folder(self.queue.clone()).into()), - None, - )))); - } + generic_queue_draw( + info, + &self.path, + || QueueContent::Folder(self.queue.clone()).into(), + &mut self.mouse, + self.copy_on_mouse_down, + ); } fn mouse_down(&mut self, button: MouseButton) -> Vec { if button == MouseButton::Left { @@ -797,12 +818,22 @@ impl GuiElem for QueueFolder { fn dragged(&mut self, dragged: Dragging) -> Vec { if !self.always_copy { if self.insert_into { - let p = self.path.clone(); - dragged_add_to_queue(dragged, move |q| Command::QueueAdd(p.clone(), q)) + dragged_add_to_queue( + dragged, + self.path.clone(), + |p, q| Command::QueueAdd(p, q), + |p, q| Command::QueueMoveInto(q, p), + ) } else { - let mut p = self.path.clone(); - let j = p.pop().unwrap_or(0); - dragged_add_to_queue(dragged, move |q| Command::QueueInsert(p.clone(), j, q)) + dragged_add_to_queue( + dragged, + self.path.clone(), + |mut p, q| { + let j = p.pop().unwrap_or(0); + Command::QueueInsert(p, j, q) + }, + |p, q| Command::QueueMove(q, p), + ) } } else { vec![] @@ -863,8 +894,15 @@ impl GuiElem for QueueIndentEnd { } } fn dragged(&mut self, dragged: Dragging) -> Vec { - let (p, j) = self.path_insert.clone(); - dragged_add_to_queue(dragged, move |q| Command::QueueInsert(p.clone(), j, q)) + dragged_add_to_queue( + dragged, + self.path_insert.clone(), + |(p, j), q| Command::QueueInsert(p, j, q), + |(mut p, j), q| { + p.push(j); + Command::QueueMove(q, p) + }, + ) } } @@ -974,12 +1012,13 @@ impl GuiElem for QueueLoop { info.mouse_pos.y - self.config.pixel_pos.top_left().y, ); } - if generic_queue_draw(info, &self.path, &mut self.mouse, self.copy_on_mouse_down) { - info.actions.push(GuiAction::SetDragging(Some(( - Dragging::Queue(self.queue.clone()), - None, - )))); - } + generic_queue_draw( + info, + &self.path, + || self.queue.clone(), + &mut self.mouse, + self.copy_on_mouse_down, + ); } fn mouse_down(&mut self, button: MouseButton) -> Vec { if button == MouseButton::Left { @@ -1016,22 +1055,29 @@ impl GuiElem for QueueLoop { if !self.always_copy { let mut p = self.path.clone(); p.push(0); - dragged_add_to_queue(dragged, move |q| Command::QueueAdd(p.clone(), q)) + dragged_add_to_queue( + dragged, + p, + |p, q| Command::QueueAdd(p, q), + |p, q| Command::QueueMoveInto(q, p), + ) } else { vec![] } } } -fn dragged_add_to_queue) -> Command + 'static>( +fn dragged_add_to_queue( dragged: Dragging, - f: F, + data: T, + f_queues: impl FnOnce(T, Vec) -> Command + 'static, + f_queue_by_path: impl FnOnce(T, Vec) -> Command + 'static, ) -> Vec { match dragged { Dragging::Artist(id) => { vec![GuiAction::Build(Box::new(move |db| { if let Some(q) = add_to_queue_artist_by_id(id, db) { - vec![GuiAction::SendToServer(f(vec![q]))] + vec![GuiAction::SendToServer(f_queues(data, vec![q]))] } else { vec![] } @@ -1040,7 +1086,7 @@ fn dragged_add_to_queue) -> Command + 'static>( Dragging::Album(id) => { vec![GuiAction::Build(Box::new(move |db| { if let Some(q) = add_to_queue_album_by_id(id, db) { - vec![GuiAction::SendToServer(f(vec![q]))] + vec![GuiAction::SendToServer(f_queues(data, vec![q]))] } else { vec![] } @@ -1048,12 +1094,13 @@ fn dragged_add_to_queue) -> Command + 'static>( } Dragging::Song(id) => { let q = QueueContent::Song(id).into(); - vec![GuiAction::SendToServer(f(vec![q]))] + vec![GuiAction::SendToServer(f_queues(data, vec![q]))] } - Dragging::Queue(q) => { - vec![GuiAction::SendToServer(f(vec![q]))] - } - Dragging::Queues(q) => vec![GuiAction::SendToServer(f(q))], + Dragging::Queue(q) => vec![GuiAction::SendToServer(match q { + Ok(q) => f_queues(data, vec![q]), + Err(p) => f_queue_by_path(data, p), + })], + Dragging::Queues(q) => vec![GuiAction::SendToServer(f_queues(data, q))], } } diff --git a/musicdb-lib/src/data/database.rs b/musicdb-lib/src/data/database.rs index 2291d59..91bc741 100755 --- a/musicdb-lib/src/data/database.rs +++ b/musicdb-lib/src/data/database.rs @@ -559,6 +559,71 @@ impl Database { Command::QueueRemove(index) => { self.queue.remove_by_index(&index, 0); } + Command::QueueMove(index_from, mut index_to) => 'queue_move: { + if index_to.len() == 0 || index_to.starts_with(&index_from) { + break 'queue_move; + } + // if same parent path, perform folder move operation instead + if index_from[0..index_from.len() - 1] == index_to[0..index_to.len() - 1] { + if let Some(parent) = self + .queue + .get_item_at_index_mut(&index_from[0..index_from.len() - 1], 0) + { + if let QueueContent::Folder(folder) = parent.content_mut() { + let i1 = index_from[index_from.len() - 1]; + let mut i2 = index_to[index_to.len() - 1]; + if i2 > i1 { + i2 -= 1; + } + // this preserves "is currently active queue element" status + folder.move_elem(i1, i2); + break 'queue_move; + } + } + } + // otherwise, remove then insert + let was_current = self.queue.is_current(&index_from); + if let Some(elem) = self.queue.remove_by_index(&index_from, 0) { + if index_to.len() >= index_from.len() + && index_to.starts_with(&index_from[0..index_from.len() - 1]) + && index_to[index_from.len() - 1] > index_from[index_from.len() - 1] + { + index_to[index_from.len() - 1] -= 1; + } + if let Some(parent) = self + .queue + .get_item_at_index_mut(&index_to[0..index_to.len() - 1], 0) + { + parent.insert(vec![elem], index_to[index_to.len() - 1]); + if was_current { + self.queue.set_index_inner(&index_to, 0, vec![]); + } + } + } + } + Command::QueueMoveInto(index_from, mut parent_to) => 'queue_move_into: { + if parent_to.starts_with(&index_from) { + break 'queue_move_into; + } + // remove then insert + let was_current = self.queue.is_current(&index_from); + if let Some(elem) = self.queue.remove_by_index(&index_from, 0) { + if parent_to.len() >= index_from.len() + && parent_to.starts_with(&index_from[0..index_from.len() - 1]) + && parent_to[index_from.len() - 1] > index_from[index_from.len() - 1] + { + parent_to[index_from.len() - 1] -= 1; + } + if let Some(parent) = self.queue.get_item_at_index_mut(&parent_to, 0) { + if let Some(i) = parent.add_to_end(vec![elem]) { + if was_current { + parent_to.push(i); + self.queue.set_index_inner(&parent_to, 0, vec![]); + } + } + } + } + } Command::QueueGoto(index) => Queue::set_index_db(self, &index), Command::QueueShuffle(path) => { if let Some(elem) = self.queue.get_item_at_index_mut(&path, 0) { diff --git a/musicdb-lib/src/data/queue.rs b/musicdb-lib/src/data/queue.rs index 259f9d0..4a4e204 100755 --- a/musicdb-lib/src/data/queue.rs +++ b/musicdb-lib/src/data/queue.rs @@ -41,6 +41,21 @@ impl Queue { QueueContent::Loop(..) => None, } } + pub fn is_current(&self, index: &[usize]) -> bool { + if index.is_empty() { + return true; + } + match self.content() { + QueueContent::Song(_) => true, + QueueContent::Folder(folder) => { + folder.index == index[0] + && folder + .get_current_immut() + .is_some_and(|c| c.is_current(&index[1..])) + } + QueueContent::Loop(_, _, inner) => index[0] == 0 && inner.is_current(&index[1..]), + } + } pub fn insert(&mut self, v: Vec, index: usize) -> bool { match &mut self.content { QueueContent::Song(_) => false, @@ -198,16 +213,11 @@ impl Queue { } } - pub fn set_index_db(db: &mut Database, index: &Vec) { + pub fn set_index_db(db: &mut Database, index: &[usize]) { db.queue.reset_index(); db.queue.set_index_inner(index, 0, vec![]); } - pub fn set_index_inner( - &mut self, - index: &Vec, - depth: usize, - mut build_index: Vec, - ) { + pub fn set_index_inner(&mut self, index: &[usize], depth: usize, mut build_index: Vec) { let i = if let Some(i) = index.get(depth) { *i } else { @@ -245,7 +255,7 @@ impl Queue { } } - pub fn get_item_at_index(&self, index: &Vec, depth: usize) -> Option<&Self> { + pub fn get_item_at_index(&self, index: &[usize], depth: usize) -> Option<&Self> { if let Some(i) = index.get(depth) { match &self.content { QueueContent::Song(_) => None, @@ -262,7 +272,7 @@ impl Queue { Some(self) } } - pub fn get_item_at_index_mut(&mut self, index: &Vec, depth: usize) -> Option<&mut Self> { + pub fn get_item_at_index_mut(&mut self, index: &[usize], depth: usize) -> Option<&mut Self> { if let Some(i) = index.get(depth) { match &mut self.content { QueueContent::Song(_) => None, @@ -280,7 +290,7 @@ impl Queue { } } - pub fn remove_by_index(&mut self, index: &Vec, depth: usize) -> Option { + pub fn remove_by_index(&mut self, index: &[usize], depth: usize) -> Option { if let Some(i) = index.get(depth) { match &mut self.content { QueueContent::Song(_) => None, @@ -450,6 +460,46 @@ impl QueueFolder { false } } + + pub fn move_elem(&mut self, index_from: usize, index_to: usize) -> bool { + fn vec_move(vec: &mut Vec, from: usize, to: usize) -> bool { + if from < vec.len() && to < vec.len() { + if from == to { + return true; + } + unsafe { + if from < to { + let elem = vec.as_mut_ptr().add(from).read(); + for i in from..to { + vec[i] = vec.as_mut_ptr().add(i + 1).read(); + } + vec[to] = elem; + } else { + let elem = vec.as_mut_ptr().add(from).read(); + for i in (to..from).rev() { + vec[i + 1] = vec.as_mut_ptr().add(i).read(); + } + vec[to] = elem; + } + } + true + } else { + false + } + } + if self.index == index_from { + self.index = index_to; + } else if index_from < self.index && self.index <= index_to { + self.index -= 1; + } else if index_to <= self.index && self.index < index_from { + self.index += 1; + } + if let Some(order) = &mut self.order { + vec_move(order, index_from, index_to) + } else { + vec_move(&mut self.content, index_from, index_to) + } + } } pub struct QueueFolderIter<'a> { folder: &'a QueueFolder, diff --git a/musicdb-lib/src/server/mod.rs b/musicdb-lib/src/server/mod.rs index 03e0b12..49a607b 100755 --- a/musicdb-lib/src/server/mod.rs +++ b/musicdb-lib/src/server/mod.rs @@ -39,6 +39,10 @@ pub enum Command { QueueAdd(Vec, Vec), QueueInsert(Vec, usize, Vec), QueueRemove(Vec), + /// Move an element from A to B + QueueMove(Vec, Vec), + /// 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), @@ -283,8 +287,10 @@ const BYTE_QUEUE_UPDATE: u8 = 0b10_000_000; const BYTE_QUEUE_ADD: u8 = 0b10_000_001; const BYTE_QUEUE_INSERT: u8 = 0b10_000_010; const BYTE_QUEUE_REMOVE: u8 = 0b10_000_100; -const BYTE_QUEUE_GOTO: u8 = 0b10_001_000; -const BYTE_QUEUE_ACTION: u8 = 0b10_100; +const BYTE_QUEUE_MOVE: u8 = 0b10_001_000; +const BYTE_QUEUE_MOVE_INTO: u8 = 0b10_001_001; +const BYTE_QUEUE_GOTO: u8 = 0b10_001_010; +const BYTE_QUEUE_ACTION: u8 = 0b10_001_100; const SUBBYTE_ACTION_SHUFFLE: u8 = 0b01_000_001; const SUBBYTE_ACTION_SET_SHUFFLE: u8 = 0b01_000_010; const SUBBYTE_ACTION_UNSHUFFLE: u8 = 0b01_000_100; @@ -348,6 +354,16 @@ impl ToFromBytes for Command { s.write_all(&[BYTE_QUEUE_REMOVE])?; index.to_bytes(s)?; } + Self::QueueMove(a, b) => { + s.write_all(&[BYTE_QUEUE_MOVE])?; + a.to_bytes(s)?; + b.to_bytes(s)?; + } + Self::QueueMoveInto(a, b) => { + s.write_all(&[BYTE_QUEUE_MOVE_INTO])?; + a.to_bytes(s)?; + b.to_bytes(s)?; + } Self::QueueGoto(index) => { s.write_all(&[BYTE_QUEUE_GOTO])?; index.to_bytes(s)?; @@ -529,6 +545,8 @@ impl ToFromBytes for Command { BYTE_QUEUE_ADD => Self::QueueAdd(from_bytes!(), from_bytes!()), BYTE_QUEUE_INSERT => Self::QueueInsert(from_bytes!(), from_bytes!(), from_bytes!()), BYTE_QUEUE_REMOVE => Self::QueueRemove(from_bytes!()), + BYTE_QUEUE_MOVE => Self::QueueMove(from_bytes!(), from_bytes!()), + 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!()), diff --git a/musicdb-mers/src/lib.rs b/musicdb-mers/src/lib.rs index 79973e5..85ab0f8 100644 --- a/musicdb-mers/src/lib.rs +++ b/musicdb-mers/src/lib.rs @@ -87,6 +87,8 @@ pub fn add( | Command::QueueAdd(..) | Command::QueueInsert(..) | Command::QueueRemove(..) + | Command::QueueMove(..) + | Command::QueueMoveInto(..) | Command::QueueGoto(..) | Command::QueueShuffle(..) | Command::QueueSetShuffle(..) diff --git a/musicdb-server/src/web.rs b/musicdb-server/src/web.rs index bc4fee0..2717108 100755 --- a/musicdb-server/src/web.rs +++ b/musicdb-server/src/web.rs @@ -426,6 +426,8 @@ async fn sse_handler( | Command::QueueAdd(..) | Command::QueueInsert(..) | Command::QueueRemove(..) + | Command::QueueMove(..) + | Command::QueueMoveInto(..) | Command::QueueGoto(..) | Command::QueueShuffle(..) | Command::QueueSetShuffle(..)