mirror of
https://github.com/Dummi26/musicdb.git
synced 2025-12-14 11:56:16 +01:00
added edit panel and fixed bug where Modify* command would be decoded as Add* command by the server
This commit is contained in:
@@ -367,6 +367,8 @@ pub enum GuiAction {
|
||||
OpenMain,
|
||||
SetIdle(bool),
|
||||
OpenSettings(bool),
|
||||
OpenEditPanel(GuiElem),
|
||||
CloseEditPanel,
|
||||
/// Build the GuiAction(s) later, when we have access to the Database (can turn an AlbumId into a QueueContent::Folder, etc)
|
||||
Build(Box<dyn FnOnce(&mut Database) -> Vec<Self>>),
|
||||
SendToServer(Command),
|
||||
@@ -739,6 +741,38 @@ impl Gui {
|
||||
}
|
||||
}
|
||||
}
|
||||
GuiAction::OpenEditPanel(p) => {
|
||||
if let Some(gui) = self
|
||||
.gui
|
||||
.inner
|
||||
.any_mut()
|
||||
.downcast_mut::<WithFocusHotkey<GuiScreen>>()
|
||||
{
|
||||
if gui.inner.idle.0 {
|
||||
gui.inner.idle = (false, Some(Instant::now()));
|
||||
}
|
||||
if gui.inner.settings.0 {
|
||||
gui.inner.settings = (false, Some(Instant::now()));
|
||||
}
|
||||
gui.inner.open_edit(p);
|
||||
}
|
||||
}
|
||||
GuiAction::CloseEditPanel => {
|
||||
if let Some(gui) = self
|
||||
.gui
|
||||
.inner
|
||||
.any_mut()
|
||||
.downcast_mut::<WithFocusHotkey<GuiScreen>>()
|
||||
{
|
||||
if gui.inner.idle.0 {
|
||||
gui.inner.idle = (false, Some(Instant::now()));
|
||||
}
|
||||
if gui.inner.settings.0 {
|
||||
gui.inner.settings = (false, Some(Instant::now()));
|
||||
}
|
||||
gui.inner.close_edit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
359
musicdb-client/src/gui_edit.rs
Executable file
359
musicdb-client/src/gui_edit.rs
Executable file
@@ -0,0 +1,359 @@
|
||||
use std::sync::{atomic::AtomicBool, mpsc, Arc};
|
||||
|
||||
use musicdb_lib::{
|
||||
data::{album::Album, artist::Artist, song::Song, AlbumId, ArtistId, SongId},
|
||||
server::Command,
|
||||
};
|
||||
use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle};
|
||||
|
||||
use crate::{
|
||||
gui::{GuiAction, GuiElem, GuiElemCfg, GuiElemTrait},
|
||||
gui_base::{Button, Panel, ScrollBox},
|
||||
gui_text::{Label, TextField},
|
||||
};
|
||||
|
||||
pub struct GuiEdit {
|
||||
config: GuiElemCfg,
|
||||
children: Vec<GuiElem>,
|
||||
editable: Editable,
|
||||
editing: Editing,
|
||||
reload: bool,
|
||||
send: bool,
|
||||
apply_change: mpsc::Sender<Box<dyn FnOnce(&mut Self)>>,
|
||||
change_recv: mpsc::Receiver<Box<dyn FnOnce(&mut Self)>>,
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub enum Editable {
|
||||
Artist(ArtistId),
|
||||
Album(AlbumId),
|
||||
Song(SongId),
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub enum Editing {
|
||||
NotLoaded,
|
||||
Artist(Artist),
|
||||
Album(Album),
|
||||
Song(Song),
|
||||
}
|
||||
impl GuiEdit {
|
||||
pub fn new(config: GuiElemCfg, edit: Editable) -> Self {
|
||||
let (apply_change, change_recv) = mpsc::channel();
|
||||
let ac1 = apply_change.clone();
|
||||
let ac2 = apply_change.clone();
|
||||
Self {
|
||||
config,
|
||||
editable: edit,
|
||||
editing: Editing::NotLoaded,
|
||||
reload: true,
|
||||
send: false,
|
||||
apply_change,
|
||||
change_recv,
|
||||
children: vec![
|
||||
GuiElem::new(Button::new(
|
||||
GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.95), (0.33, 1.0))),
|
||||
|_| vec![GuiAction::CloseEditPanel],
|
||||
vec![GuiElem::new(Label::new(
|
||||
GuiElemCfg::default(),
|
||||
"Back".to_string(),
|
||||
Color::WHITE,
|
||||
None,
|
||||
Vec2::new(0.5, 0.5),
|
||||
))],
|
||||
)),
|
||||
GuiElem::new(Button::new(
|
||||
GuiElemCfg::at(Rectangle::from_tuples((0.33, 0.95), (0.67, 1.0))),
|
||||
move |_| {
|
||||
_ = ac1.send(Box::new(|s| s.reload = true));
|
||||
vec![]
|
||||
},
|
||||
vec![GuiElem::new(Label::new(
|
||||
GuiElemCfg::default(),
|
||||
"Reload".to_string(),
|
||||
Color::WHITE,
|
||||
None,
|
||||
Vec2::new(0.5, 0.5),
|
||||
))],
|
||||
)),
|
||||
GuiElem::new(Button::new(
|
||||
GuiElemCfg::at(Rectangle::from_tuples((0.67, 0.95), (1.0, 1.0))),
|
||||
move |_| {
|
||||
_ = ac2.send(Box::new(|s| s.send = true));
|
||||
vec![]
|
||||
},
|
||||
vec![GuiElem::new(Label::new(
|
||||
GuiElemCfg::default(),
|
||||
"Send".to_string(),
|
||||
Color::WHITE,
|
||||
None,
|
||||
Vec2::new(0.5, 0.5),
|
||||
))],
|
||||
)),
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
impl GuiElemTrait for GuiEdit {
|
||||
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 crate::gui::DrawInfo, g: &mut speedy2d::Graphics2D) {
|
||||
loop {
|
||||
if let Ok(func) = self.change_recv.try_recv() {
|
||||
func(self);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if self.send {
|
||||
self.send = false;
|
||||
match &self.editing {
|
||||
Editing::NotLoaded => {}
|
||||
Editing::Artist(v) => info
|
||||
.actions
|
||||
.push(GuiAction::SendToServer(Command::ModifyArtist(v.clone()))),
|
||||
Editing::Album(v) => info
|
||||
.actions
|
||||
.push(GuiAction::SendToServer(Command::ModifyAlbum(v.clone()))),
|
||||
Editing::Song(v) => info
|
||||
.actions
|
||||
.push(GuiAction::SendToServer(Command::ModifySong(v.clone()))),
|
||||
}
|
||||
}
|
||||
if self.reload {
|
||||
self.reload = false;
|
||||
self.editing = match &self.editable {
|
||||
Editable::Artist(id) => {
|
||||
if let Some(v) = info.database.artists().get(id).cloned() {
|
||||
Editing::Artist(v)
|
||||
} else {
|
||||
Editing::NotLoaded
|
||||
}
|
||||
}
|
||||
Editable::Album(id) => {
|
||||
if let Some(v) = info.database.albums().get(id).cloned() {
|
||||
Editing::Album(v)
|
||||
} else {
|
||||
Editing::NotLoaded
|
||||
}
|
||||
}
|
||||
Editable::Song(id) => {
|
||||
if let Some(v) = info.database.songs().get(id).cloned() {
|
||||
Editing::Song(v)
|
||||
} else {
|
||||
Editing::NotLoaded
|
||||
}
|
||||
}
|
||||
};
|
||||
self.config.redraw = true;
|
||||
}
|
||||
if self.config.redraw {
|
||||
self.config.redraw = false;
|
||||
let scrollbox = if self.children.len() > 3 {
|
||||
let o = self.children.pop();
|
||||
while self.children.len() > 3 {
|
||||
self.children.pop();
|
||||
}
|
||||
o
|
||||
} else {
|
||||
None
|
||||
};
|
||||
match &self.editing {
|
||||
Editing::NotLoaded => {
|
||||
self.children.push(GuiElem::new(Label::new(
|
||||
GuiElemCfg::default(),
|
||||
"nothing here".to_string(),
|
||||
Color::WHITE,
|
||||
None,
|
||||
Vec2::new(0.5, 0.5),
|
||||
)));
|
||||
}
|
||||
Editing::Artist(artist) => {
|
||||
self.children.push(GuiElem::new(Label::new(
|
||||
GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (0.8, 0.08))),
|
||||
artist.name.clone(),
|
||||
Color::WHITE,
|
||||
None,
|
||||
Vec2::new(0.1, 0.5),
|
||||
)));
|
||||
self.children.push(GuiElem::new(Label::new(
|
||||
GuiElemCfg::at(Rectangle::from_tuples((0.8, 0.0), (1.0, 0.04))),
|
||||
"Artist".to_string(),
|
||||
Color::WHITE,
|
||||
None,
|
||||
Vec2::new(0.8, 0.5),
|
||||
)));
|
||||
self.children.push(GuiElem::new(Label::new(
|
||||
GuiElemCfg::at(Rectangle::from_tuples((0.8, 0.04), (1.0, 0.08))),
|
||||
format!("#{}", artist.id),
|
||||
Color::WHITE,
|
||||
None,
|
||||
Vec2::new(0.8, 0.5),
|
||||
)));
|
||||
let mut elems = vec![];
|
||||
elems.push((
|
||||
GuiElem::new(Panel::new(
|
||||
GuiElemCfg::default(),
|
||||
vec![
|
||||
GuiElem::new(Label::new(
|
||||
GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (0.6, 1.0))),
|
||||
format!(
|
||||
"{} album{}",
|
||||
artist.albums.len(),
|
||||
if artist.albums.len() != 1 { "s" } else { "" }
|
||||
),
|
||||
Color::LIGHT_GRAY,
|
||||
None,
|
||||
Vec2::new(0.0, 0.5),
|
||||
)),
|
||||
GuiElem::new(TextField::new(
|
||||
GuiElemCfg::at(Rectangle::from_tuples((0.6, 0.0), (0.8, 1.0))),
|
||||
"id".to_string(),
|
||||
Color::DARK_GRAY,
|
||||
Color::WHITE,
|
||||
)),
|
||||
GuiElem::new(Button::new(
|
||||
GuiElemCfg::at(Rectangle::from_tuples((0.8, 0.0), (1.0, 1.0))),
|
||||
{
|
||||
let apply_change = self.apply_change.clone();
|
||||
let my_index = elems.len();
|
||||
move |_| {
|
||||
_ = apply_change.send(Box::new(move |s| {
|
||||
s.config.redraw = true;
|
||||
if let Ok(id) = s
|
||||
.children
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.inner
|
||||
.any_mut()
|
||||
.downcast_mut::<ScrollBox>()
|
||||
.unwrap()
|
||||
.children[my_index]
|
||||
.0
|
||||
.inner
|
||||
.children()
|
||||
.nth(1)
|
||||
.unwrap()
|
||||
.inner
|
||||
.children()
|
||||
.next()
|
||||
.unwrap()
|
||||
.inner
|
||||
.any()
|
||||
.downcast_ref::<Label>()
|
||||
.unwrap()
|
||||
.content
|
||||
.get_text()
|
||||
.parse::<AlbumId>()
|
||||
{
|
||||
if let Editing::Artist(artist) = &mut s.editing
|
||||
{
|
||||
artist.albums.push(id);
|
||||
}
|
||||
}
|
||||
}));
|
||||
vec![]
|
||||
}
|
||||
},
|
||||
vec![GuiElem::new(Label::new(
|
||||
GuiElemCfg::default(),
|
||||
"add".to_string(),
|
||||
Color::LIGHT_GRAY,
|
||||
None,
|
||||
Vec2::new(0.0, 0.5),
|
||||
))],
|
||||
)),
|
||||
],
|
||||
)),
|
||||
info.line_height,
|
||||
));
|
||||
for &album in &artist.albums {
|
||||
elems.push((
|
||||
GuiElem::new(Button::new(
|
||||
GuiElemCfg::default(),
|
||||
move |_| {
|
||||
vec![GuiAction::OpenEditPanel(GuiElem::new(GuiEdit::new(
|
||||
GuiElemCfg::default(),
|
||||
Editable::Album(album),
|
||||
)))]
|
||||
},
|
||||
vec![GuiElem::new(Label::new(
|
||||
GuiElemCfg::default(),
|
||||
if let Some(a) = info.database.albums().get(&album) {
|
||||
format!("Album: {}", a.name)
|
||||
} else {
|
||||
format!("Album #{album}")
|
||||
},
|
||||
Color::WHITE,
|
||||
None,
|
||||
Vec2::new(0.0, 0.5),
|
||||
))],
|
||||
)),
|
||||
info.line_height,
|
||||
));
|
||||
}
|
||||
self.children.push(if let Some(mut sb) = scrollbox {
|
||||
if let Some(s) = sb.inner.any_mut().downcast_mut::<ScrollBox>() {
|
||||
s.children = elems;
|
||||
s.config_mut().redraw = true;
|
||||
sb
|
||||
} else {
|
||||
GuiElem::new(ScrollBox::new(
|
||||
GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.08), (1.0, 1.0))),
|
||||
crate::gui_base::ScrollBoxSizeUnit::Pixels,
|
||||
elems,
|
||||
))
|
||||
}
|
||||
} else {
|
||||
GuiElem::new(ScrollBox::new(
|
||||
GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.08), (1.0, 1.0))),
|
||||
crate::gui_base::ScrollBoxSizeUnit::Pixels,
|
||||
elems,
|
||||
))
|
||||
});
|
||||
}
|
||||
Editing::Album(album) => {
|
||||
self.children.push(GuiElem::new(Label::new(
|
||||
GuiElemCfg::default(),
|
||||
format!("Album: {}", album.name),
|
||||
Color::WHITE,
|
||||
None,
|
||||
Vec2::new(0.5, 0.5),
|
||||
)));
|
||||
}
|
||||
Editing::Song(song) => {
|
||||
self.children.push(GuiElem::new(Label::new(
|
||||
GuiElemCfg::default(),
|
||||
format!("Song: {}", song.title),
|
||||
Color::WHITE,
|
||||
None,
|
||||
Vec2::new(0.5, 0.5),
|
||||
)));
|
||||
}
|
||||
}
|
||||
};
|
||||
match self.editing {
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for GuiEdit {
|
||||
fn clone(&self) -> Self {
|
||||
Self::new(self.config.clone(), self.editable.clone())
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ use speedy2d::{
|
||||
use crate::{
|
||||
gui::{Dragging, DrawInfo, GuiAction, GuiElem, GuiElemCfg, GuiElemTrait},
|
||||
gui_base::ScrollBox,
|
||||
gui_edit::GuiEdit,
|
||||
gui_text::{Label, TextField},
|
||||
gui_wrappers::WithFocusHotkey,
|
||||
};
|
||||
@@ -268,6 +269,7 @@ struct ListArtist {
|
||||
config: GuiElemCfg,
|
||||
id: ArtistId,
|
||||
children: Vec<GuiElem>,
|
||||
mouse: bool,
|
||||
mouse_pos: Vec2,
|
||||
}
|
||||
impl ListArtist {
|
||||
@@ -283,6 +285,7 @@ impl ListArtist {
|
||||
config: config.w_mouse(),
|
||||
id,
|
||||
children: vec![GuiElem::new(label)],
|
||||
mouse: false,
|
||||
mouse_pos: Vec2::ZERO,
|
||||
}
|
||||
}
|
||||
@@ -307,6 +310,13 @@ impl GuiElemTrait for ListArtist {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) {
|
||||
if self.mouse {
|
||||
if info.pos.contains(info.mouse_pos) {
|
||||
return;
|
||||
} else {
|
||||
self.mouse = false;
|
||||
}
|
||||
}
|
||||
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,
|
||||
@@ -314,6 +324,7 @@ impl GuiElemTrait for ListArtist {
|
||||
}
|
||||
fn mouse_down(&mut self, button: MouseButton) -> Vec<GuiAction> {
|
||||
if button == MouseButton::Left {
|
||||
self.mouse = true;
|
||||
let mouse_pos = self.mouse_pos;
|
||||
let w = self.config.pixel_pos.width();
|
||||
let h = self.config.pixel_pos.height();
|
||||
@@ -334,6 +345,17 @@ impl GuiElemTrait for ListArtist {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
fn mouse_up(&mut self, button: MouseButton) -> Vec<GuiAction> {
|
||||
if self.mouse && button == MouseButton::Left {
|
||||
self.mouse = false;
|
||||
vec![GuiAction::OpenEditPanel(GuiElem::new(GuiEdit::new(
|
||||
GuiElemCfg::default(),
|
||||
crate::gui_edit::Editable::Artist(self.id),
|
||||
)))]
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -341,6 +363,7 @@ struct ListAlbum {
|
||||
config: GuiElemCfg,
|
||||
id: AlbumId,
|
||||
children: Vec<GuiElem>,
|
||||
mouse: bool,
|
||||
mouse_pos: Vec2,
|
||||
}
|
||||
impl ListAlbum {
|
||||
@@ -356,6 +379,7 @@ impl ListAlbum {
|
||||
config: config.w_mouse(),
|
||||
id,
|
||||
children: vec![GuiElem::new(label)],
|
||||
mouse: false,
|
||||
mouse_pos: Vec2::ZERO,
|
||||
}
|
||||
}
|
||||
@@ -380,6 +404,13 @@ impl GuiElemTrait for ListAlbum {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) {
|
||||
if self.mouse {
|
||||
if info.pos.contains(info.mouse_pos) {
|
||||
return;
|
||||
} else {
|
||||
self.mouse = false;
|
||||
}
|
||||
}
|
||||
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,
|
||||
@@ -387,6 +418,7 @@ impl GuiElemTrait for ListAlbum {
|
||||
}
|
||||
fn mouse_down(&mut self, button: MouseButton) -> Vec<GuiAction> {
|
||||
if button == MouseButton::Left {
|
||||
self.mouse = true;
|
||||
let mouse_pos = self.mouse_pos;
|
||||
let w = self.config.pixel_pos.width();
|
||||
let h = self.config.pixel_pos.height();
|
||||
@@ -407,6 +439,17 @@ impl GuiElemTrait for ListAlbum {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
fn mouse_up(&mut self, button: MouseButton) -> Vec<GuiAction> {
|
||||
if self.mouse && button == MouseButton::Left {
|
||||
self.mouse = false;
|
||||
vec![GuiAction::OpenEditPanel(GuiElem::new(GuiEdit::new(
|
||||
GuiElemCfg::default(),
|
||||
crate::gui_edit::Editable::Album(self.id),
|
||||
)))]
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -414,6 +457,7 @@ struct ListSong {
|
||||
config: GuiElemCfg,
|
||||
id: SongId,
|
||||
children: Vec<GuiElem>,
|
||||
mouse: bool,
|
||||
mouse_pos: Vec2,
|
||||
}
|
||||
impl ListSong {
|
||||
@@ -429,6 +473,7 @@ impl ListSong {
|
||||
config: config.w_mouse(),
|
||||
id,
|
||||
children: vec![GuiElem::new(label)],
|
||||
mouse: false,
|
||||
mouse_pos: Vec2::ZERO,
|
||||
}
|
||||
}
|
||||
@@ -453,6 +498,13 @@ impl GuiElemTrait for ListSong {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) {
|
||||
if self.mouse {
|
||||
if info.pos.contains(info.mouse_pos) {
|
||||
return;
|
||||
} else {
|
||||
self.mouse = false;
|
||||
}
|
||||
}
|
||||
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,
|
||||
@@ -460,6 +512,7 @@ impl GuiElemTrait for ListSong {
|
||||
}
|
||||
fn mouse_down(&mut self, button: MouseButton) -> Vec<GuiAction> {
|
||||
if button == MouseButton::Left {
|
||||
self.mouse = true;
|
||||
let mouse_pos = self.mouse_pos;
|
||||
let w = self.config.pixel_pos.width();
|
||||
let h = self.config.pixel_pos.height();
|
||||
@@ -480,4 +533,15 @@ impl GuiElemTrait for ListSong {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
fn mouse_up(&mut self, button: MouseButton) -> Vec<GuiAction> {
|
||||
if self.mouse && button == MouseButton::Left {
|
||||
self.mouse = false;
|
||||
vec![GuiAction::OpenEditPanel(GuiElem::new(GuiEdit::new(
|
||||
GuiElemCfg::default(),
|
||||
crate::gui_edit::Editable::Song(self.id),
|
||||
)))]
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,7 +198,12 @@ fn queue_gui(
|
||||
QueueContent::Folder(ia, q, _) => {
|
||||
if !skip_folder {
|
||||
target.push((
|
||||
GuiElem::new(QueueFolder::new(cfg, path.clone(), queue.clone(), current)),
|
||||
GuiElem::new(QueueFolder::new(
|
||||
cfg.clone(),
|
||||
path.clone(),
|
||||
queue.clone(),
|
||||
current,
|
||||
)),
|
||||
line_height * 0.8,
|
||||
));
|
||||
}
|
||||
@@ -217,12 +222,22 @@ fn queue_gui(
|
||||
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();
|
||||
let mut p1 = path.clone();
|
||||
let p2 = p1.pop().unwrap_or(0) + 1;
|
||||
p.push(0);
|
||||
target.push((
|
||||
GuiElem::new(QueueLoop::new(cfg, path, queue.clone(), current)),
|
||||
GuiElem::new(QueueLoop::new(cfg.clone(), path, queue.clone(), current)),
|
||||
line_height * 0.8,
|
||||
));
|
||||
queue_gui(
|
||||
@@ -236,6 +251,10 @@ fn queue_gui(
|
||||
current,
|
||||
true,
|
||||
);
|
||||
target.push((
|
||||
GuiElem::new(QueueIndentEnd::new(cfg, (p1, p2))),
|
||||
line_height * 0.4,
|
||||
));
|
||||
}
|
||||
QueueContent::Random(q) => {
|
||||
target.push((
|
||||
@@ -543,6 +562,7 @@ struct QueueFolder {
|
||||
path: Vec<usize>,
|
||||
queue: Queue,
|
||||
current: bool,
|
||||
insert_into: bool,
|
||||
mouse: bool,
|
||||
mouse_pos: Vec2,
|
||||
copy: bool,
|
||||
@@ -579,6 +599,7 @@ impl QueueFolder {
|
||||
path,
|
||||
queue,
|
||||
current,
|
||||
insert_into: false,
|
||||
mouse: false,
|
||||
mouse_pos: Vec2::ZERO,
|
||||
copy: false,
|
||||
@@ -612,6 +633,29 @@ impl GuiElemTrait for QueueFolder {
|
||||
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,
|
||||
@@ -666,13 +710,75 @@ impl GuiElemTrait for QueueFolder {
|
||||
}
|
||||
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, q))
|
||||
if self.insert_into {
|
||||
let p = self.path.clone();
|
||||
dragged_add_to_queue(dragged, move |q| Command::QueueAdd(p, q))
|
||||
} else {
|
||||
let mut p = self.path.clone();
|
||||
let i = p.pop();
|
||||
dragged_add_to_queue(dragged, move |q| Command::QueueInsert(p, i.unwrap_or(0), 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, i) = self.path_insert.clone();
|
||||
dragged_add_to_queue(dragged, move |q| Command::QueueInsert(p, i, q))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct QueueLoop {
|
||||
@@ -1157,9 +1263,15 @@ fn add_to_queue_artist_by_id(id: ArtistId, db: &Database) -> Option<Queue> {
|
||||
QueueContent::Folder(
|
||||
0,
|
||||
artist
|
||||
.albums
|
||||
.singles
|
||||
.iter()
|
||||
.filter_map(|id| add_to_queue_album_by_id(*id, db))
|
||||
.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(),
|
||||
)
|
||||
|
||||
@@ -40,14 +40,36 @@ pub struct GuiScreen {
|
||||
/// 0: StatusBar / Idle display
|
||||
/// 1: Settings
|
||||
/// 2: Panel for Main view
|
||||
/// 3: Edit Panel
|
||||
children: Vec<GuiElem>,
|
||||
pub idle: (bool, Option<Instant>),
|
||||
pub settings: (bool, Option<Instant>),
|
||||
pub edit_panel: (bool, Option<Instant>),
|
||||
pub last_interaction: Instant,
|
||||
idle_timeout: Option<f64>,
|
||||
pub prev_mouse_pos: Vec2,
|
||||
}
|
||||
impl GuiScreen {
|
||||
pub fn open_edit(&mut self, mut edit: GuiElem) {
|
||||
if !self.edit_panel.0 {
|
||||
self.edit_panel = (true, Some(Instant::now()));
|
||||
edit.inner.config_mut().pos = Rectangle::from_tuples((-0.5, 0.0), (0.0, 0.9));
|
||||
} else {
|
||||
edit.inner.config_mut().pos = Rectangle::from_tuples((0.0, 0.0), (0.5, 0.9));
|
||||
}
|
||||
if let Some(prev) = self.children.get_mut(3) {
|
||||
prev.inner.config_mut().enabled = false;
|
||||
}
|
||||
self.children.insert(3, edit);
|
||||
}
|
||||
pub fn close_edit(&mut self) {
|
||||
if self.children.len() > 4 {
|
||||
self.children.remove(3);
|
||||
self.children[3].inner.config_mut().enabled = true;
|
||||
} else if self.edit_panel.0 {
|
||||
self.edit_panel = (false, Some(Instant::now()));
|
||||
}
|
||||
}
|
||||
pub fn new<T: Read + Write + 'static + Sync + Send>(
|
||||
config: GuiElemCfg,
|
||||
get_con: get::Client<T>,
|
||||
@@ -109,6 +131,7 @@ impl GuiScreen {
|
||||
],
|
||||
idle: (false, None),
|
||||
settings: (false, None),
|
||||
edit_panel: (false, None),
|
||||
last_interaction: Instant::now(),
|
||||
idle_timeout: Some(60.0),
|
||||
prev_mouse_pos: Vec2::ZERO,
|
||||
@@ -193,7 +216,7 @@ impl GuiElemTrait for GuiScreen {
|
||||
}
|
||||
self.idle_check();
|
||||
// request_redraw for animations
|
||||
if self.idle.1.is_some() | self.settings.1.is_some() {
|
||||
if self.idle.1.is_some() || self.settings.1.is_some() || self.edit_panel.1.is_some() {
|
||||
if let Some(h) = &info.helper {
|
||||
h.request_redraw()
|
||||
}
|
||||
@@ -208,6 +231,11 @@ impl GuiElemTrait for GuiScreen {
|
||||
if self.settings.0 {
|
||||
self.children[1].inner.config_mut().enabled = !self.idle.0;
|
||||
}
|
||||
if self.edit_panel.0 {
|
||||
if let Some(c) = self.children.get_mut(3) {
|
||||
c.inner.config_mut().enabled = !self.idle.0;
|
||||
}
|
||||
}
|
||||
self.children[2].inner.config_mut().enabled = !self.idle.0;
|
||||
}
|
||||
}
|
||||
@@ -229,6 +257,23 @@ impl GuiElemTrait for GuiScreen {
|
||||
cfg.enabled = p > 0.0;
|
||||
cfg.pos = Rectangle::from_tuples((0.0, 0.9 - 0.9 * p), (1.0, 0.9));
|
||||
}
|
||||
// animations: edit_panel
|
||||
if self.edit_panel.1.is_some() {
|
||||
let p1 = Self::get_prog(&mut self.edit_panel, 0.3);
|
||||
let p = transition(p1);
|
||||
if let Some(c) = self.children.get_mut(3) {
|
||||
c.inner.config_mut().enabled = p > 0.0;
|
||||
c.inner.config_mut().pos =
|
||||
Rectangle::from_tuples((-0.5 + 0.5 * p, 0.0), (0.5 * p, 0.9));
|
||||
}
|
||||
if !self.edit_panel.0 && p == 0.0 {
|
||||
while self.children.len() > 3 {
|
||||
self.children.pop();
|
||||
}
|
||||
}
|
||||
self.children[2].inner.config_mut().pos =
|
||||
Rectangle::from_tuples((0.5 * p, 0.0), (1.0 + 0.5 * p, 0.9));
|
||||
}
|
||||
// set idle timeout (only when settings are open)
|
||||
if self.settings.0 || self.settings.1.is_some() {
|
||||
self.idle_timeout = self.children[1]
|
||||
|
||||
@@ -148,7 +148,7 @@ impl TextField {
|
||||
hint,
|
||||
color_hint,
|
||||
None,
|
||||
Vec2::new(0.0, 0.5),
|
||||
Vec2::new(0.5, 0.5),
|
||||
)),
|
||||
],
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ mod gui;
|
||||
#[cfg(feature = "speedy2d")]
|
||||
mod gui_base;
|
||||
#[cfg(feature = "speedy2d")]
|
||||
mod gui_edit;
|
||||
#[cfg(feature = "speedy2d")]
|
||||
mod gui_library;
|
||||
#[cfg(feature = "speedy2d")]
|
||||
mod gui_playback;
|
||||
|
||||
Reference in New Issue
Block a user