mirror of
https://github.com/Dummi26/musicdb.git
synced 2025-03-10 05:43:53 +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:
parent
55590a1549
commit
0e5e33367d
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
*/Cargo.lock
|
||||
*/target
|
||||
TODO.txt
|
||||
|
@ -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 {
|
||||
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 {
|
||||
@ -1156,10 +1262,16 @@ fn add_to_queue_artist_by_id(id: ArtistId, db: &Database) -> Option<Queue> {
|
||||
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))
|
||||
.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;
|
||||
|
@ -48,8 +48,16 @@ impl Song {
|
||||
cached_data: Arc::new(Mutex::new(None)),
|
||||
}
|
||||
}
|
||||
pub fn uncache_data(&self) {
|
||||
*self.cached_data.lock().unwrap() = None;
|
||||
pub fn uncache_data(&self) -> Result<(), ()> {
|
||||
let mut cached = self.cached_data.lock().unwrap();
|
||||
match cached.as_ref() {
|
||||
Some(Ok(_data)) => {
|
||||
*cached = None;
|
||||
Ok(())
|
||||
}
|
||||
Some(Err(_thread)) => Err(()),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
/// If no data is cached yet and no caching thread is running, starts a thread to cache the data.
|
||||
pub fn cache_data_start_thread(&self, db: &Database) -> bool {
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::sync::Arc;
|
||||
use std::{collections::HashSet, sync::Arc};
|
||||
|
||||
use awedio::{
|
||||
backends::CpalBackend,
|
||||
@ -23,6 +23,7 @@ pub struct Player {
|
||||
)>,
|
||||
manager: Manager,
|
||||
current_song_id: SongOpt,
|
||||
cached: HashSet<SongId>,
|
||||
}
|
||||
|
||||
pub enum SongOpt {
|
||||
@ -40,6 +41,7 @@ impl Player {
|
||||
backend,
|
||||
source: None,
|
||||
current_song_id: SongOpt::None,
|
||||
cached: HashSet::new(),
|
||||
})
|
||||
}
|
||||
pub fn handle_command(&mut self, command: &Command) {
|
||||
@ -121,6 +123,7 @@ impl Player {
|
||||
};
|
||||
if db.playing {
|
||||
if let Some(bytes) = song.cached_data_now(db) {
|
||||
self.cached.insert(song.id);
|
||||
match Self::sound_from_bytes(ext, bytes) {
|
||||
Ok(v) => {
|
||||
let (sound, notif) =
|
||||
@ -140,6 +143,7 @@ impl Player {
|
||||
} else {
|
||||
self.source = None;
|
||||
song.cache_data_start_thread(&db);
|
||||
self.cached.insert(song.id);
|
||||
}
|
||||
} else {
|
||||
panic!("invalid song ID: current_song_id not found in DB!");
|
||||
@ -148,8 +152,25 @@ impl Player {
|
||||
} else {
|
||||
self.current_song_id = SongOpt::None;
|
||||
}
|
||||
if let Some(Some(song)) = db.queue.get_next_song().map(|v| db.get_song(v)) {
|
||||
let next_song = db.queue.get_next_song().and_then(|v| db.get_song(v));
|
||||
for &id in &self.cached {
|
||||
if Some(id) != next_song.map(|v| v.id)
|
||||
&& !matches!(self.current_song_id, SongOpt::Some(v) if v == id)
|
||||
{
|
||||
if let Some(song) = db.songs().get(&id) {
|
||||
if let Ok(()) = song.uncache_data() {
|
||||
self.cached.remove(&id);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
self.cached.remove(&id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(song) = next_song {
|
||||
song.cache_data_start_thread(&db);
|
||||
self.cached.insert(song.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -305,9 +305,9 @@ impl ToFromBytes for Command {
|
||||
0b01010000 => Self::AddSong(ToFromBytes::from_bytes(s)?),
|
||||
0b01010011 => Self::AddAlbum(ToFromBytes::from_bytes(s)?),
|
||||
0b01011100 => Self::AddArtist(ToFromBytes::from_bytes(s)?),
|
||||
0b10010000 => Self::AddSong(ToFromBytes::from_bytes(s)?),
|
||||
0b10010011 => Self::AddAlbum(ToFromBytes::from_bytes(s)?),
|
||||
0b10011100 => Self::AddArtist(ToFromBytes::from_bytes(s)?),
|
||||
0b10010000 => Self::ModifySong(ToFromBytes::from_bytes(s)?),
|
||||
0b10010011 => Self::ModifyAlbum(ToFromBytes::from_bytes(s)?),
|
||||
0b10011100 => Self::ModifyArtist(ToFromBytes::from_bytes(s)?),
|
||||
0b01011101 => Self::AddCover(ToFromBytes::from_bytes(s)?),
|
||||
0b00110001 => Self::SetLibraryDirectory(ToFromBytes::from_bytes(s)?),
|
||||
_ => {
|
||||
|
Loading…
Reference in New Issue
Block a user