include shuffle functionality in folders, remove shuffle and random elements, change ToFromBytes impls

This commit is contained in:
Mark
2024-03-30 22:13:59 +01:00
parent 8b8f13f98a
commit 0fe2648efe
14 changed files with 855 additions and 1136 deletions

View File

@@ -24,7 +24,7 @@ default = ["gui", "mers", "merscfg"]
# mers:
# enables the run-mers mode
# playback:
# enables Symcplayer modes, where the client mirrors the server's playback
# enables syncplayer modes, where the client mirrors the server's playback
gui = ["speedy2d"]
merscfg = ["mers_lib", "musicdb-mers", "speedy2d"]
mers = ["mers_lib", "musicdb-mers"]

View File

@@ -366,7 +366,9 @@ impl Gui {
| Command::QueueInsert(..)
| Command::QueueRemove(..)
| Command::QueueGoto(..)
| Command::QueueSetShuffle(..) => {
| Command::QueueShuffle(..)
| Command::QueueSetShuffle(..)
| Command::QueueUnshuffle(..) => {
if let Some(s) = &*event_sender_arc.lock().unwrap() {
_ = s.send_event(GuiEvent::UpdatedQueue);
}

View File

@@ -2082,6 +2082,8 @@ impl FilterType {
}
mod selected {
use musicdb_lib::data::queue::QueueFolder;
use super::*;
#[derive(Clone)]
pub struct Selected(
@@ -2187,28 +2189,30 @@ mod selected {
}
if album_selected {
local_artist.push(
QueueContent::Folder(
0,
local_album_owned,
match db.albums().get(album) {
QueueContent::Folder(QueueFolder {
index: 0,
content: local_album_owned,
name: match db.albums().get(album) {
Some(v) => v.name.clone(),
None => "< unknown album >".to_owned(),
},
)
order: None,
})
.into(),
);
}
}
if artist_selected {
out.push(
QueueContent::Folder(
0,
local_artist_owned,
match db.artists().get(artist) {
QueueContent::Folder(QueueFolder {
index: 0,
content: local_artist_owned,
name: match db.artists().get(artist) {
Some(v) => v.name.to_owned(),
None => "< unknown artist >".to_owned(),
},
)
order: None,
})
.into(),
);
}

View File

@@ -1,9 +1,7 @@
use std::collections::VecDeque;
use musicdb_lib::{
data::{
database::Database,
queue::{Queue, QueueContent, QueueDuration, ShuffleState},
queue::{Queue, QueueContent, QueueDuration},
song::Song,
AlbumId, ArtistId,
},
@@ -18,8 +16,8 @@ use speedy2d::{
use crate::{
gui::{Dragging, DrawInfo, GuiAction, GuiElem, GuiElemCfg},
gui_base::{Panel, ScrollBox},
gui_text::{self, AdvancedLabel, Label},
gui_base::{Button, Panel, ScrollBox, ScrollBoxSizeUnit},
gui_text::{self, AdvancedLabel, Label, TextField},
};
/*
@@ -35,16 +33,21 @@ pub struct QueueViewer {
config: GuiElemCfg,
c_scroll_box: ScrollBox<Vec<Box<dyn GuiElem>>>,
c_empty_space_drag_handler: QueueEmptySpaceDragHandler,
c_control_flow_elements: Panel<(QueueLoop, QueueLoop, QueueRandom, QueueShuffle)>,
c_control_flow_elements: Panel<(QueueLoop, QueueLoop, QueueFolder, TextField)>,
c_duration: AdvancedLabel,
recv: std::sync::mpsc::Receiver<QVMsg>,
queue_updated: bool,
}
pub enum QVMsg {
ControlFlowElementsSetFolderName(String),
}
const QP_QUEUE1: f32 = 0.0;
const QP_QUEUE2: f32 = 0.95;
const QP_INV1: f32 = QP_QUEUE2;
const QP_INV2: f32 = 1.0;
impl QueueViewer {
pub fn new(config: GuiElemCfg) -> Self {
let (sender, recv) = std::sync::mpsc::channel();
let control_flow_elements = (
QueueLoop::new(
GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (0.5, 0.5))).w_mouse(),
@@ -52,7 +55,15 @@ impl QueueViewer {
QueueContent::Loop(
0,
0,
Box::new(QueueContent::Folder(0, vec![], "in loop".to_string()).into()),
Box::new(
QueueContent::Folder(musicdb_lib::data::queue::QueueFolder {
index: 0,
content: vec![],
name: "in loop".to_string(),
order: None,
})
.into(),
),
)
.into(),
false,
@@ -64,30 +75,46 @@ impl QueueViewer {
QueueContent::Loop(
2,
0,
Box::new(QueueContent::Folder(0, vec![], "in loop".to_string()).into()),
Box::new(
QueueContent::Folder(musicdb_lib::data::queue::QueueFolder {
index: 0,
content: vec![],
name: "in loop".to_string(),
order: None,
})
.into(),
),
)
.into(),
false,
)
.alwayscopy(),
QueueRandom::new(
QueueFolder::new(
GuiElemCfg::at(Rectangle::from_tuples((0.5, 0.0), (1.0, 0.5))).w_mouse(),
vec![],
QueueContent::Random(VecDeque::new()).into(),
false,
)
.alwayscopy(),
QueueShuffle::new(
GuiElemCfg::at(Rectangle::from_tuples((0.5, 0.5), (1.0, 1.0))).w_mouse(),
vec![],
QueueContent::Shuffle {
inner: Box::new(QueueContent::Folder(0, vec![], String::new()).into()),
state: ShuffleState::NotShuffled,
}
.into(),
musicdb_lib::data::queue::QueueFolder {
index: 0,
content: vec![],
name: format!("folder name"),
order: None,
},
false,
)
.alwayscopy(),
{
let mut tf = TextField::new(
GuiElemCfg::at(Rectangle::from_tuples((0.5, 0.5), (1.0, 1.0))),
format!("folder name"),
Color::from_rgb(0.0, 0.33, 0.0),
Color::from_rgb(0.0, 0.67, 0.0),
);
tf.on_changed = Some(Box::new(move |folder_name| {
_ = sender.send(QVMsg::ControlFlowElementsSetFolderName(
folder_name.to_owned(),
));
}));
tf
},
);
Self {
config,
@@ -111,6 +138,7 @@ impl QueueViewer {
vec![],
),
queue_updated: false,
recv,
}
}
}
@@ -145,6 +173,20 @@ impl GuiElem for QueueViewer {
self
}
fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) {
while let Ok(msg) = self.recv.try_recv() {
match msg {
QVMsg::ControlFlowElementsSetFolderName(name) => {
*self
.c_control_flow_elements
.children
.2
.c_name
.content
.text() = name.clone();
self.c_control_flow_elements.children.2.queue.name = name;
}
}
}
if self.queue_updated {
self.queue_updated = false;
let label = &mut self.c_duration;
@@ -254,17 +296,23 @@ fn queue_gui(
target_h.push(line_height * 1.75);
}
}
QueueContent::Folder(ia, q, _) => {
QueueContent::Folder(qf) => {
let musicdb_lib::data::queue::QueueFolder {
index: ia,
content: _,
name: _,
order: _,
} = qf;
if !skip_folder {
target.push(Box::new(QueueFolder::new(
cfg.clone(),
path.clone(),
queue.clone(),
qf.clone(),
current,
)));
target_h.push(line_height * 0.8);
}
for (i, q) in q.iter().enumerate() {
for (i, q) in qf.iter().enumerate() {
let mut p = path.clone();
p.push(i);
queue_gui(
@@ -314,62 +362,6 @@ fn queue_gui(
target.push(Box::new(QueueIndentEnd::new(cfg, (p1, p2))));
target_h.push(line_height * 0.4);
}
QueueContent::Random(q) => {
target.push(Box::new(QueueRandom::new(
cfg.clone(),
path.clone(),
queue.clone(),
current,
)));
target_h.push(line_height);
for (i, inner) in q.iter().enumerate() {
let mut p = path.clone();
p.push(i);
queue_gui(
inner,
db,
depth + depth_inc_by,
depth_inc_by,
line_height,
target,
target_h,
p,
current && i == q.len().saturating_sub(2),
false,
);
}
let mut p1 = path.clone();
let p2 = p1.pop().unwrap_or(0) + 1;
target.push(Box::new(QueueIndentEnd::new(cfg, (p1, p2))));
target_h.push(line_height * 0.4);
}
QueueContent::Shuffle { inner, state: _ } => {
target.push(Box::new(QueueShuffle::new(
cfg.clone(),
path.clone(),
queue.clone(),
current,
)));
target_h.push(line_height * 0.8);
let mut p = path.clone();
p.push(0);
queue_gui(
inner,
db,
depth + depth_inc_by,
depth_inc_by,
line_height,
target,
target_h,
p,
current,
true,
);
let mut p1 = path.clone();
let p2 = p1.pop().unwrap_or(0) + 1;
target.push(Box::new(QueueIndentEnd::new(cfg, (p1, p2))));
target_h.push(line_height * 0.4);
}
}
}
@@ -638,9 +630,9 @@ impl GuiElem for QueueSong {
struct QueueFolder {
config: GuiElemCfg,
children: Vec<Box<dyn GuiElem>>,
c_name: Label,
path: Vec<usize>,
queue: Queue,
queue: musicdb_lib::data::queue::QueueFolder,
current: bool,
insert_into: bool,
mouse: bool,
@@ -650,7 +642,18 @@ struct QueueFolder {
copy_on_mouse_down: bool,
}
impl QueueFolder {
pub fn new(config: GuiElemCfg, path: Vec<usize>, queue: Queue, current: bool) -> Self {
pub fn new(
config: GuiElemCfg,
path: Vec<usize>,
queue: musicdb_lib::data::queue::QueueFolder,
current: bool,
) -> Self {
let musicdb_lib::data::queue::QueueFolder {
index: _,
content,
name,
order,
} = &queue;
Self {
config: if path.is_empty() {
config
@@ -658,24 +661,22 @@ impl QueueFolder {
config.w_mouse().w_keyboard_watch()
}
.w_drag_target(),
children: vec![Box::new(Label::new(
c_name: Label::new(
GuiElemCfg::default(),
match queue.content() {
QueueContent::Folder(_, q, n) => format!(
"{} ({})",
if path.is_empty() && n.is_empty() {
"Queue"
} else {
n
},
q.len()
),
_ => "[???]".to_string(),
},
format!(
"{} ({}){}",
if path.is_empty() && name.is_empty() {
"Queue"
} else {
name
},
content.len(),
if order.is_some() { " [shuffled]" } else { "" },
),
Color::from_int_rgb(52, 132, 50),
None,
Vec2::new(0.0, 0.5),
))],
),
path,
queue,
current,
@@ -687,6 +688,12 @@ impl QueueFolder {
copy_on_mouse_down: false,
}
}
fn alwayscopy(mut self) -> Self {
self.always_copy = true;
self.copy = true;
self.config.scroll_events = true;
self
}
}
impl GuiElem for QueueFolder {
fn config(&self) -> &GuiElemCfg {
@@ -696,7 +703,7 @@ impl GuiElem for QueueFolder {
&mut self.config
}
fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
Box::new(self.children.iter_mut().map(|v| v.elem_mut()))
Box::new([self.c_name.elem_mut()].into_iter())
}
fn any(&self) -> &dyn std::any::Any {
self
@@ -742,7 +749,7 @@ impl GuiElem for QueueFolder {
}
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()),
Dragging::Queue(QueueContent::Folder(self.queue.clone()).into()),
None,
))));
}
@@ -751,6 +758,15 @@ impl GuiElem for QueueFolder {
if button == MouseButton::Left {
self.mouse = true;
self.copy_on_mouse_down = self.copy;
} else if button == MouseButton::Right {
// return vec![GuiAction::ContextMenu(Some(vec![Box::new(
// Panel::with_background(GuiElemCfg::default(), (), Color::DARK_GRAY),
// )]))];
return vec![GuiAction::SendToServer(if self.queue.order.is_some() {
Command::QueueUnshuffle(self.path.clone())
} else {
Command::QueueShuffle(self.path.clone())
})];
}
vec![]
}
@@ -1007,259 +1023,6 @@ impl GuiElem for QueueLoop {
}
}
struct QueueRandom {
config: GuiElemCfg,
children: Vec<Box<dyn GuiElem>>,
path: Vec<usize>,
queue: Queue,
current: bool,
mouse: bool,
mouse_pos: Vec2,
copy: bool,
always_copy: bool,
copy_on_mouse_down: bool,
}
impl QueueRandom {
pub fn new(config: GuiElemCfg, path: Vec<usize>, queue: Queue, current: bool) -> Self {
Self {
config: if path.is_empty() {
config
} else {
config.w_mouse().w_keyboard_watch()
}
.w_drag_target(),
children: vec![Box::new(Label::new(
GuiElemCfg::default(),
match queue.content() {
QueueContent::Random(_) => {
format!("random")
}
_ => "[???]".to_string(),
},
Color::from_int_rgb(32, 27, 179),
None,
Vec2::new(0.0, 0.5),
))],
path,
queue,
current,
mouse: false,
mouse_pos: Vec2::ZERO,
copy: false,
always_copy: false,
copy_on_mouse_down: false,
}
}
fn alwayscopy(mut self) -> Self {
self.always_copy = true;
self.copy = true;
self
}
}
impl GuiElem for QueueRandom {
fn config(&self) -> &GuiElemCfg {
&self.config
}
fn config_mut(&mut self) -> &mut GuiElemCfg {
&mut self.config
}
fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
Box::new(self.children.iter_mut().map(|v| v.elem_mut()))
}
fn any(&self) -> &dyn std::any::Any {
self
}
fn any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn elem(&self) -> &dyn GuiElem {
self
}
fn elem_mut(&mut self) -> &mut dyn GuiElem {
self
}
fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) {
if !self.mouse {
self.mouse_pos = Vec2::new(
info.mouse_pos.x - self.config.pixel_pos.top_left().x,
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,
))));
}
}
fn mouse_down(&mut self, button: MouseButton) -> Vec<GuiAction> {
if button == MouseButton::Left {
self.mouse = true;
self.copy_on_mouse_down = self.copy;
}
vec![]
}
fn mouse_up(&mut self, button: MouseButton) -> Vec<GuiAction> {
if self.mouse && button == MouseButton::Left {
self.mouse = false;
if !self.always_copy {
vec![GuiAction::SendToServer(Command::QueueGoto(
self.path.clone(),
))]
} else {
vec![]
}
} else {
vec![]
}
}
fn key_watch(
&mut self,
modifiers: ModifiersState,
_down: bool,
_key: Option<VirtualKeyCode>,
_scan: speedy2d::window::KeyScancode,
) -> Vec<GuiAction> {
self.copy = modifiers.ctrl();
vec![]
}
fn dragged(&mut self, dragged: Dragging) -> Vec<GuiAction> {
if !self.always_copy {
let p = self.path.clone();
dragged_add_to_queue(dragged, move |q| Command::QueueAdd(p.clone(), q))
} else {
vec![]
}
}
}
struct QueueShuffle {
config: GuiElemCfg,
children: Vec<Box<dyn GuiElem>>,
path: Vec<usize>,
queue: Queue,
current: bool,
mouse: bool,
mouse_pos: Vec2,
copy: bool,
always_copy: bool,
copy_on_mouse_down: bool,
}
impl QueueShuffle {
pub fn new(config: GuiElemCfg, path: Vec<usize>, queue: Queue, current: bool) -> Self {
Self {
config: if path.is_empty() {
config
} else {
config.w_mouse().w_keyboard_watch()
}
.w_drag_target(),
children: vec![Box::new(Label::new(
GuiElemCfg::default(),
match queue.content() {
QueueContent::Shuffle { .. } => {
format!("shuffle")
}
_ => "[???]".to_string(),
},
Color::from_int_rgb(92, 52, 194),
None,
Vec2::new(0.0, 0.5),
))],
path,
queue,
current,
mouse: false,
mouse_pos: Vec2::ZERO,
copy: false,
always_copy: false,
copy_on_mouse_down: false,
}
}
fn alwayscopy(mut self) -> Self {
self.always_copy = true;
self.copy = true;
self
}
}
impl GuiElem for QueueShuffle {
fn config(&self) -> &GuiElemCfg {
&self.config
}
fn config_mut(&mut self) -> &mut GuiElemCfg {
&mut self.config
}
fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
Box::new(self.children.iter_mut().map(|v| v.elem_mut()))
}
fn any(&self) -> &dyn std::any::Any {
self
}
fn any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn elem(&self) -> &dyn GuiElem {
self
}
fn elem_mut(&mut self) -> &mut dyn GuiElem {
self
}
fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) {
if !self.mouse {
self.mouse_pos = Vec2::new(
info.mouse_pos.x - self.config.pixel_pos.top_left().x,
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,
))));
}
}
fn mouse_down(&mut self, button: MouseButton) -> Vec<GuiAction> {
if button == MouseButton::Left {
self.mouse = true;
self.copy_on_mouse_down = self.copy;
}
vec![]
}
fn mouse_up(&mut self, button: MouseButton) -> Vec<GuiAction> {
if self.mouse && button == MouseButton::Left {
self.mouse = false;
if !self.always_copy {
vec![GuiAction::SendToServer(Command::QueueGoto(
self.path.clone(),
))]
} else {
vec![]
}
} else {
vec![]
}
}
fn key_watch(
&mut self,
modifiers: ModifiersState,
_down: bool,
_key: Option<VirtualKeyCode>,
_scan: speedy2d::window::KeyScancode,
) -> Vec<GuiAction> {
self.copy = modifiers.ctrl();
vec![]
}
fn dragged(&mut self, dragged: Dragging) -> Vec<GuiAction> {
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))
} else {
vec![]
}
}
}
fn dragged_add_to_queue<F: FnOnce(Vec<Queue>) -> Command + 'static>(
dragged: Dragging,
f: F,
@@ -1297,15 +1060,16 @@ fn dragged_add_to_queue<F: FnOnce(Vec<Queue>) -> Command + 'static>(
fn add_to_queue_album_by_id(id: AlbumId, db: &Database) -> Option<Queue> {
if let Some(album) = db.albums().get(&id) {
Some(
QueueContent::Folder(
0,
album
QueueContent::Folder(musicdb_lib::data::queue::QueueFolder {
index: 0,
content: album
.songs
.iter()
.map(|id| QueueContent::Song(*id).into())
.collect(),
album.name.clone(),
)
name: album.name.clone(),
order: None,
})
.into(),
)
} else {
@@ -1315,9 +1079,9 @@ fn add_to_queue_album_by_id(id: AlbumId, db: &Database) -> Option<Queue> {
fn add_to_queue_artist_by_id(id: ArtistId, db: &Database) -> Option<Queue> {
if let Some(artist) = db.artists().get(&id) {
Some(
QueueContent::Folder(
0,
artist
QueueContent::Folder(musicdb_lib::data::queue::QueueFolder {
index: 0,
content: artist
.singles
.iter()
.map(|id| QueueContent::Song(*id).into())
@@ -1328,8 +1092,9 @@ fn add_to_queue_artist_by_id(id: ArtistId, db: &Database) -> Option<Queue> {
.filter_map(|id| add_to_queue_album_by_id(*id, db)),
)
.collect(),
artist.name.clone(),
)
name: artist.name.clone(),
order: None,
})
.into(),
)
} else {

View File

@@ -1,6 +1,9 @@
use std::time::Instant;
use musicdb_lib::{data::queue::QueueContent, server::Command};
use musicdb_lib::{
data::queue::{QueueContent, QueueFolder},
server::Command,
};
use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle, window::VirtualKeyCode, Graphics2D};
use crate::{
@@ -117,9 +120,7 @@ impl GuiScreen {
musicdb_lib::server::Command::QueueUpdate(
vec![],
musicdb_lib::data::queue::QueueContent::Folder(
0,
vec![],
String::new(),
musicdb_lib::data::queue::QueueFolder::default(),
)
.into(),
),
@@ -337,7 +338,7 @@ impl GuiElem for GuiScreen {
self.not_idle();
}
if !(!info.database.playing
|| matches!(info.database.queue.content(), QueueContent::Folder(_, v, _) if v.is_empty()))
|| matches!(info.database.queue.content(), QueueContent::Folder(QueueFolder { content: v, .. }) if v.is_empty()))
{
// skip idle_check if paused or queue is empty
self.idle_check();