musicdb/musicdb-client/src/gui_queue.rs
2023-11-04 20:06:54 +01:00

1422 lines
45 KiB
Rust
Executable File

use std::collections::VecDeque;
use musicdb_lib::{
data::{
database::Database,
queue::{Queue, QueueContent, QueueDuration, ShuffleState},
song::Song,
AlbumId, ArtistId,
},
server::Command,
};
use speedy2d::{
color::Color,
dimen::Vec2,
shape::Rectangle,
window::{ModifiersState, MouseButton, VirtualKeyCode},
};
use crate::{
gui::{Dragging, DrawInfo, GuiAction, GuiElem, GuiElemCfg, GuiElemTrait},
gui_base::{Panel, ScrollBox},
gui_text::{self, AdvancedLabel, Label},
};
/*
This is responsible for showing the current queue,
with drag-n-drop only if the mouse leaves the element before it is released,
because simple clicks have to be GoTo events.
*/
#[derive(Clone)]
pub struct QueueViewer {
config: GuiElemCfg,
children: Vec<GuiElem>,
queue_updated: bool,
}
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 {
Self {
config,
children: vec![
GuiElem::new(ScrollBox::new(
GuiElemCfg::at(Rectangle::from_tuples((0.0, QP_QUEUE1), (1.0, QP_QUEUE2))),
crate::gui_base::ScrollBoxSizeUnit::Pixels,
vec![(
GuiElem::new(Label::new(
GuiElemCfg::default(),
"loading...".to_string(),
Color::DARK_GRAY,
None,
Vec2::new(0.5, 0.5),
)),
1.0,
)],
)),
GuiElem::new(QueueEmptySpaceDragHandler::new(GuiElemCfg::at(
Rectangle::from_tuples((0.0, QP_QUEUE1), (1.0, QP_QUEUE2)),
))),
GuiElem::new(Panel::new(
GuiElemCfg::at(Rectangle::from_tuples((0.0, QP_INV1), (0.5, QP_INV2))),
vec![
GuiElem::new(
QueueLoop::new(
GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (0.5, 0.5)))
.w_mouse(),
vec![],
QueueContent::Loop(
0,
0,
Box::new(
QueueContent::Folder(0, vec![], "in loop".to_string())
.into(),
),
)
.into(),
false,
)
.alwayscopy(),
),
GuiElem::new(
QueueLoop::new(
GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.5), (0.5, 1.0)))
.w_mouse(),
vec![],
QueueContent::Loop(
2,
0,
Box::new(
QueueContent::Folder(0, vec![], "in loop".to_string())
.into(),
),
)
.into(),
false,
)
.alwayscopy(),
),
GuiElem::new(
QueueRandom::new(
GuiElemCfg::at(Rectangle::from_tuples((0.5, 0.0), (1.0, 0.5)))
.w_mouse(),
vec![],
QueueContent::Random(VecDeque::new()).into(),
false,
)
.alwayscopy(),
),
GuiElem::new(
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(),
false,
)
.alwayscopy(),
),
],
)),
GuiElem::new(AdvancedLabel::new(
GuiElemCfg::at(Rectangle::from_tuples((0.5, QP_INV1), (1.0, QP_INV2))),
Vec2::new(0.0, 0.5),
vec![],
)),
],
queue_updated: false,
}
}
}
impl GuiElemTrait for QueueViewer {
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 GuiElem> + '_> {
Box::new(self.children.iter_mut())
}
fn any(&self) -> &dyn std::any::Any {
self
}
fn any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn clone_gui(&self) -> Box<dyn GuiElemTrait> {
Box::new(self.clone())
}
fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) {
if self.queue_updated {
self.queue_updated = false;
let label = self.children[3]
.inner
.any_mut()
.downcast_mut::<AdvancedLabel>()
.unwrap();
fn fmt_dur(dur: QueueDuration) -> String {
if dur.infinite {
"".to_owned()
} else {
let seconds = dur.millis / 1000;
let minutes = seconds / 60;
let h = minutes / 60;
let m = minutes % 60;
let s = seconds % 60;
if dur.random_counter == 0 {
if h > 0 {
format!("{h}:{m:0>2}:{s:0>2}")
} else {
format!("{m:0>2}:{s:0>2}")
}
} else {
let r = dur.random_counter;
if dur.millis > 0 {
if h > 0 {
format!("{h}:{m:0>2}:{s:0>2} + {r} random songs")
} else {
format!("{m:0>2}:{s:0>2} + {r} random songs")
}
} else {
format!("{r} random songs")
}
}
}
}
let dt = fmt_dur(info.database.queue.duration_total(&info.database));
let dr = fmt_dur(info.database.queue.duration_remaining(&info.database));
label.content = vec![
vec![(
gui_text::Content::new(format!("Total: {dt}"), Color::GRAY),
1.0,
1.0,
)],
vec![(
gui_text::Content::new(format!("Remaining: {dr}"), Color::GRAY),
1.0,
1.0,
)],
];
label.config_mut().redraw = true;
}
if self.config.redraw || info.pos.size() != self.config.pixel_pos.size() {
self.config.redraw = false;
let mut c = vec![];
queue_gui(
&info.database.queue,
&info.database,
0.0,
0.02,
info.line_height,
&mut c,
vec![],
true,
true,
);
let mut scroll_box = self.children[0].try_as_mut::<ScrollBox>().unwrap();
scroll_box.children = c;
scroll_box.config_mut().redraw = true;
}
}
fn updated_queue(&mut self) {
self.queue_updated = true;
self.config.redraw = true;
}
}
fn queue_gui(
queue: &Queue,
db: &Database,
depth: f32,
depth_inc_by: f32,
line_height: f32,
target: &mut Vec<(GuiElem, f32)>,
path: Vec<usize>,
current: bool,
skip_folder: bool,
) {
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((
GuiElem::new(QueueSong::new(
cfg,
path,
s.clone(),
current,
db,
depth_inc_by * 0.33,
)),
line_height * 1.75,
));
}
}
QueueContent::Folder(ia, q, _) => {
if !skip_folder {
target.push((
GuiElem::new(QueueFolder::new(
cfg.clone(),
path.clone(),
queue.clone(),
current,
)),
line_height * 0.8,
));
}
for (i, q) in q.iter().enumerate() {
let mut p = path.clone();
p.push(i);
queue_gui(
q,
db,
depth + depth_inc_by,
depth_inc_by,
line_height,
target,
p,
current && *ia == i,
false,
);
}
if !skip_folder {
let mut p1 = path;
let p2 = p1.pop().unwrap_or(0) + 1;
target.push((
GuiElem::new(QueueIndentEnd::new(cfg, (p1, p2))),
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((
GuiElem::new(QueueLoop::new(cfg.clone(), path, queue.clone(), current)),
line_height * 0.8,
));
queue_gui(
&inner,
db,
depth,
depth_inc_by,
line_height,
target,
p,
current,
true,
);
target.push((
GuiElem::new(QueueIndentEnd::new(cfg, (p1, p2))),
line_height * 0.4,
));
}
QueueContent::Random(q) => {
target.push((
GuiElem::new(QueueRandom::new(
cfg.clone(),
path.clone(),
queue.clone(),
current,
)),
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,
p,
current && i == q.len().saturating_sub(2),
false,
);
}
let mut p1 = path.clone();
let p2 = p1.pop().unwrap_or(0) + 1;
target.push((
GuiElem::new(QueueIndentEnd::new(cfg, (p1, p2))),
line_height * 0.4,
));
}
QueueContent::Shuffle { inner, state: _ } => {
target.push((
GuiElem::new(QueueShuffle::new(
cfg.clone(),
path.clone(),
queue.clone(),
current,
)),
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,
p,
current,
true,
);
let mut p1 = path.clone();
let p2 = p1.pop().unwrap_or(0) + 1;
target.push((
GuiElem::new(QueueIndentEnd::new(cfg, (p1, p2))),
line_height * 0.4,
));
}
}
}
#[derive(Clone)]
struct QueueEmptySpaceDragHandler {
config: GuiElemCfg,
children: Vec<GuiElem>,
}
impl QueueEmptySpaceDragHandler {
pub fn new(config: GuiElemCfg) -> Self {
Self {
config: config.w_drag_target(),
children: vec![],
}
}
}
impl GuiElemTrait for QueueEmptySpaceDragHandler {
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 GuiElem> + '_> {
Box::new(self.children.iter_mut())
}
fn any(&self) -> &dyn std::any::Any {
self
}
fn any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn clone_gui(&self) -> Box<dyn GuiElemTrait> {
Box::new(self.clone())
}
fn dragged(&mut self, dragged: Dragging) -> Vec<GuiAction> {
dragged_add_to_queue(dragged, |q, _| Command::QueueAdd(vec![], q))
}
}
fn generic_queue_draw(
info: &mut DrawInfo,
path: &Vec<usize>,
mouse: &mut bool,
copy_on_mouse_down: bool,
) -> bool {
if *mouse && !info.pos.contains(info.mouse_pos) {
*mouse = false;
if !copy_on_mouse_down {
info.actions
.push(GuiAction::SendToServer(Command::QueueRemove(path.clone())));
}
true
} else {
false
}
}
#[derive(Clone)]
struct QueueSong {
config: GuiElemCfg,
children: Vec<GuiElem>,
path: Vec<usize>,
song: Song,
current: bool,
insert_below: bool,
mouse: bool,
mouse_pos: Vec2,
copy: bool,
always_copy: bool,
copy_on_mouse_down: bool,
}
impl QueueSong {
pub fn new(
config: GuiElemCfg,
path: Vec<usize>,
song: Song,
current: bool,
db: &Database,
sub_offset: f32,
) -> Self {
Self {
config: config.w_mouse().w_keyboard_watch().w_drag_target(),
children: vec![
GuiElem::new(AdvancedLabel::new(
GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (1.0, 0.57))),
Vec2::new(0.0, 0.5),
vec![vec![
(
gui_text::Content::new(
song.title.clone(),
if current {
Color::from_int_rgb(194, 76, 178)
} else {
Color::from_int_rgb(120, 76, 194)
},
),
1.0,
1.0,
),
(
gui_text::Content::new(
{
let duration = song.duration_millis / 1000;
format!(" {}:{:0>2}", duration / 60, duration % 60)
},
if current {
Color::GRAY
} else {
Color::DARK_GRAY
},
),
0.6,
1.0,
),
]],
)),
GuiElem::new(Label::new(
GuiElemCfg::at(Rectangle::from_tuples((sub_offset, 0.57), (1.0, 1.0))),
match (
db.artists().get(&song.artist),
song.album.as_ref().and_then(|id| db.albums().get(id)),
) {
(None, None) => String::new(),
(Some(artist), None) => format!("by {}", artist.name),
(None, Some(album)) => {
if let Some(artist) = db.artists().get(&album.artist) {
format!("on {} by {}", album.name, artist.name)
} else {
format!("on {}", album.name)
}
}
(Some(artist), Some(album)) => {
format!("by {} on {}", artist.name, album.name)
}
},
if current {
Color::from_int_rgb(97, 38, 89)
} else {
Color::from_int_rgb(60, 38, 97)
},
None,
Vec2::new(0.0, 0.5),
)),
],
path,
song,
current,
insert_below: false,
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 GuiElemTrait for QueueSong {
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 GuiElem> + '_> {
Box::new(self.children.iter_mut())
}
fn any(&self) -> &dyn std::any::Any {
self
}
fn any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn clone_gui(&self) -> Box<dyn GuiElemTrait> {
Box::new(self.clone())
}
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 draw(&mut self, info: &mut DrawInfo, g: &mut speedy2d::Graphics2D) {
self.insert_below = 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_below {
Rectangle::new(
Vec2::new(
info.pos.top_left().x,
info.pos.top_left().y + info.pos.height() * 0.75,
),
*info.pos.bottom_right(),
)
} else {
Rectangle::new(
*info.pos.top_left(),
Vec2::new(
info.pos.bottom_right().x,
info.pos.top_left().y + info.pos.height() * 0.25,
),
)
},
Color::from_rgba(1.0, 1.0, 1.0, 0.25),
);
}
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) {
let mouse_pos = self.mouse_pos;
let w = self.config.pixel_pos.width();
let h = self.config.pixel_pos.height();
let mut el = GuiElem::new(self.clone());
info.actions.push(GuiAction::SetDragging(Some((
Dragging::Queue(QueueContent::Song(self.song.id).into()),
Some(Box::new(move |i, g| {
let sw = i.pos.width();
let sh = i.pos.height();
let x = (i.mouse_pos.x - mouse_pos.x) / sw;
let y = (i.mouse_pos.y - mouse_pos.y) / sh;
el.inner.config_mut().pos =
Rectangle::from_tuples((x, y), (x + w / sw, y + h / sh));
el.draw(i, g)
})),
))));
}
}
fn key_watch(
&mut self,
modifiers: ModifiersState,
_down: bool,
_key: Option<VirtualKeyCode>,
_scan: speedy2d::window::KeyScancode,
) -> Vec<GuiAction> {
self.copy = self.always_copy || modifiers.ctrl();
vec![]
}
fn dragged(&mut self, dragged: Dragging) -> Vec<GuiAction> {
if !self.always_copy {
let mut p = self.path.clone();
let insert_below = self.insert_below;
dragged_add_to_queue(dragged, move |q, i| {
if let Some(j) = p.pop() {
Command::QueueInsert(p.clone(), if insert_below { j + 1 } else { j } + i, q)
} else {
Command::QueueAdd(p.clone(), q)
}
})
} else {
vec![]
}
}
}
#[derive(Clone)]
struct QueueFolder {
config: GuiElemCfg,
children: Vec<GuiElem>,
path: Vec<usize>,
queue: Queue,
current: bool,
insert_into: bool,
mouse: bool,
mouse_pos: Vec2,
copy: bool,
always_copy: bool,
copy_on_mouse_down: bool,
}
impl QueueFolder {
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![GuiElem::new(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(),
},
Color::from_int_rgb(52, 132, 50),
None,
Vec2::new(0.0, 0.5),
))],
path,
queue,
current,
insert_into: false,
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 GuiElemTrait for QueueFolder {
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 GuiElem> + '_> {
Box::new(self.children.iter_mut())
}
fn any(&self) -> &dyn std::any::Any {
self
}
fn any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn clone_gui(&self) -> Box<dyn GuiElemTrait> {
Box::new(self.clone())
}
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;
if !self.always_copy && info.dragging.is_some() && info.pos.contains(info.mouse_pos) {
g.draw_rectangle(
if self.insert_into {
Rectangle::new(
Vec2::new(
info.pos.top_left().x,
info.pos.top_left().y + info.pos.height() * 0.5,
),
*info.pos.bottom_right(),
)
} else {
Rectangle::new(
*info.pos.top_left(),
Vec2::new(
info.pos.bottom_right().x,
info.pos.top_left().y + info.pos.height() * 0.25,
),
)
},
Color::from_rgba(1.0, 1.0, 1.0, 0.25),
);
}
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) {
let mouse_pos = self.mouse_pos;
let w = self.config.pixel_pos.width();
let h = self.config.pixel_pos.height();
let mut el = GuiElem::new(self.clone());
info.actions.push(GuiAction::SetDragging(Some((
Dragging::Queue(self.queue.clone()),
Some(Box::new(move |i, g| {
let sw = i.pos.width();
let sh = i.pos.height();
let x = (i.mouse_pos.x - mouse_pos.x) / sw;
let y = (i.mouse_pos.y - mouse_pos.y) / sh;
el.inner.config_mut().pos =
Rectangle::from_tuples((x, y), (x + w / sw, y + h / sh));
el.draw(i, g)
})),
))));
}
}
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 {
if self.insert_into {
let p = self.path.clone();
dragged_add_to_queue(dragged, move |q, _| Command::QueueAdd(p.clone(), q))
} else {
let mut p = self.path.clone();
let j = p.pop().unwrap_or(0);
dragged_add_to_queue(dragged, move |q, i| {
Command::QueueInsert(p.clone(), j + i, q)
})
}
} else {
vec![]
}
}
}
#[derive(Clone)]
pub struct QueueIndentEnd {
config: GuiElemCfg,
children: Vec<GuiElem>,
path_insert: (Vec<usize>, usize),
}
impl QueueIndentEnd {
pub fn new(config: GuiElemCfg, path_insert: (Vec<usize>, usize)) -> Self {
Self {
config: config.w_drag_target(),
children: vec![],
path_insert,
}
}
}
impl GuiElemTrait for QueueIndentEnd {
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 GuiElem> + '_> {
Box::new(self.children.iter_mut())
}
fn any(&self) -> &dyn std::any::Any {
self
}
fn any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn clone_gui(&self) -> Box<dyn GuiElemTrait> {
Box::new(self.clone())
}
fn draw(&mut self, info: &mut DrawInfo, g: &mut speedy2d::Graphics2D) {
if info.dragging.is_some() {
g.draw_rectangle(
info.pos.clone(),
Color::from_rgba(
1.0,
1.0,
1.0,
if info.pos.contains(info.mouse_pos) {
0.3
} else {
0.2
},
),
);
}
}
fn dragged(&mut self, dragged: Dragging) -> Vec<GuiAction> {
let (p, j) = self.path_insert.clone();
dragged_add_to_queue(dragged, move |q, i| {
Command::QueueInsert(p.clone(), j + i, q)
})
}
}
#[derive(Clone)]
struct QueueLoop {
config: GuiElemCfg,
children: Vec<GuiElem>,
path: Vec<usize>,
queue: Queue,
current: bool,
mouse: bool,
mouse_pos: Vec2,
copy: bool,
always_copy: bool,
copy_on_mouse_down: bool,
}
impl QueueLoop {
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![GuiElem::new(Label::new(
GuiElemCfg::default(),
Self::get_label_text(&queue),
Color::from_int_rgb(217, 197, 65),
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.config.scroll_events = true;
self
}
fn get_label_text(queue: &Queue) -> String {
match queue.content() {
QueueContent::Loop(total, _current, _) => {
if *total == 0 {
format!("repeat forever")
} else if *total == 1 {
format!("repeat 1 time")
} else {
format!("repeat {total} times")
}
}
_ => "[???]".to_string(),
}
}
}
impl GuiElemTrait for QueueLoop {
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 GuiElem> + '_> {
Box::new(self.children.iter_mut())
}
fn any(&self) -> &dyn std::any::Any {
self
}
fn any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn clone_gui(&self) -> Box<dyn GuiElemTrait> {
Box::new(self.clone())
}
fn mouse_wheel(&mut self, diff: f32) -> Vec<GuiAction> {
if self.always_copy {
if let QueueContent::Loop(total, _, _) = self.queue.content_mut() {
if diff > 0.0 {
*total += 1;
} else if diff < 0.0 && *total > 0 {
*total -= 1;
}
}
*self.children[0]
.inner
.any_mut()
.downcast_mut::<Label>()
.unwrap()
.content
.text() = Self::get_label_text(&self.queue);
}
vec![]
}
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) {
let mouse_pos = self.mouse_pos;
let w = self.config.pixel_pos.width();
let h = self.config.pixel_pos.height();
let mut el = GuiElem::new(self.clone());
info.actions.push(GuiAction::SetDragging(Some((
Dragging::Queue(self.queue.clone()),
Some(Box::new(move |i, g| {
let sw = i.pos.width();
let sh = i.pos.height();
let x = (i.mouse_pos.x - mouse_pos.x) / sw;
let y = (i.mouse_pos.y - mouse_pos.y) / sh;
el.inner.config_mut().pos =
Rectangle::from_tuples((x, y), (x + w / sw, y + h / sh));
el.draw(i, g)
})),
))));
}
}
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![]
}
}
}
#[derive(Clone)]
struct QueueRandom {
config: GuiElemCfg,
children: Vec<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![GuiElem::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 GuiElemTrait 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 GuiElem> + '_> {
Box::new(self.children.iter_mut())
}
fn any(&self) -> &dyn std::any::Any {
self
}
fn any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn clone_gui(&self) -> Box<dyn GuiElemTrait> {
Box::new(self.clone())
}
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) {
let mouse_pos = self.mouse_pos;
let w = self.config.pixel_pos.width();
let h = self.config.pixel_pos.height();
let mut el = GuiElem::new(self.clone());
info.actions.push(GuiAction::SetDragging(Some((
Dragging::Queue(self.queue.clone()),
Some(Box::new(move |i, g| {
let sw = i.pos.width();
let sh = i.pos.height();
let x = (i.mouse_pos.x - mouse_pos.x) / sw;
let y = (i.mouse_pos.y - mouse_pos.y) / sh;
el.inner.config_mut().pos =
Rectangle::from_tuples((x, y), (x + w / sw, y + h / sh));
el.draw(i, g)
})),
))));
}
}
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![]
}
}
}
#[derive(Clone)]
struct QueueShuffle {
config: GuiElemCfg,
children: Vec<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![GuiElem::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 GuiElemTrait 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 GuiElem> + '_> {
Box::new(self.children.iter_mut())
}
fn any(&self) -> &dyn std::any::Any {
self
}
fn any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn clone_gui(&self) -> Box<dyn GuiElemTrait> {
Box::new(self.clone())
}
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) {
let mouse_pos = self.mouse_pos;
let w = self.config.pixel_pos.width();
let h = self.config.pixel_pos.height();
let mut el = GuiElem::new(self.clone());
info.actions.push(GuiAction::SetDragging(Some((
Dragging::Queue(self.queue.clone()),
Some(Box::new(move |i, g| {
let sw = i.pos.width();
let sh = i.pos.height();
let x = (i.mouse_pos.x - mouse_pos.x) / sw;
let y = (i.mouse_pos.y - mouse_pos.y) / sh;
el.inner.config_mut().pos =
Rectangle::from_tuples((x, y), (x + w / sw, y + h / sh));
el.draw(i, g)
})),
))));
}
}
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: FnMut(Queue, usize) -> Command + 'static>(
dragged: Dragging,
mut f: F,
) -> Vec<GuiAction> {
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(q, 0))]
} else {
vec![]
}
}))]
}
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(q, 0))]
} else {
vec![]
}
}))]
}
Dragging::Song(id) => {
let q = QueueContent::Song(id).into();
vec![GuiAction::SendToServer(f(q, 0))]
}
Dragging::Queue(q) => {
vec![GuiAction::SendToServer(f(q, 0))]
}
Dragging::Queues(q) => q
.into_iter()
.enumerate()
.map(|(i, q)| GuiAction::SendToServer(f(q, i)))
.collect(),
}
}
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
.songs
.iter()
.map(|id| QueueContent::Song(*id).into())
.collect(),
album.name.clone(),
)
.into(),
)
} else {
None
}
}
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
.singles
.iter()
.map(|id| QueueContent::Song(*id).into())
.chain(
artist
.albums
.iter()
.filter_map(|id| add_to_queue_album_by_id(*id, db)),
)
.collect(),
artist.name.clone(),
)
.into(),
)
} else {
None
}
}