mirror of
https://github.com/Dummi26/musicdb.git
synced 2025-09-13 07:06:14 +02:00
feat: better song edit menu with multiselect support
This commit is contained in:
parent
ee7c74f3a8
commit
12d85241cd
@ -1258,8 +1258,8 @@ pub enum GuiAction {
|
|||||||
Do(Box<dyn FnOnce(&mut Gui)>),
|
Do(Box<dyn FnOnce(&mut Gui)>),
|
||||||
Exit,
|
Exit,
|
||||||
EditSongs(Vec<Song>),
|
EditSongs(Vec<Song>),
|
||||||
// EditAlbums(Vec<Album>),
|
// EditAlbums(Vec<Album>, bool),
|
||||||
// EditArtists(Vec<Artist>),
|
// EditArtists(Vec<Artist>, bool),
|
||||||
OpenAddSongsMenu,
|
OpenAddSongsMenu,
|
||||||
CloseAddSongsMenu,
|
CloseAddSongsMenu,
|
||||||
}
|
}
|
||||||
|
@ -355,6 +355,14 @@ impl<C: GuiElemChildren> Button<C> {
|
|||||||
action: Arc::new(action),
|
action: Arc::new(action),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn disable(&mut self) {
|
||||||
|
self.config.mouse_events = false;
|
||||||
|
self.config.keyboard_events_focus = false;
|
||||||
|
}
|
||||||
|
pub fn enable(&mut self) {
|
||||||
|
self.config.mouse_events = true;
|
||||||
|
self.config.keyboard_events_focus = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl<C: GuiElemChildren + 'static> GuiElem for Button<C> {
|
impl<C: GuiElemChildren + 'static> GuiElem for Button<C> {
|
||||||
fn config(&self) -> &GuiElemCfg {
|
fn config(&self) -> &GuiElemCfg {
|
||||||
|
315
musicdb-client/src/gui_edit_any.rs
Normal file
315
musicdb-client/src/gui_edit_any.rs
Normal file
@ -0,0 +1,315 @@
|
|||||||
|
use std::{collections::BTreeSet, time::Instant};
|
||||||
|
|
||||||
|
use speedy2d::{
|
||||||
|
color::Color,
|
||||||
|
dimen::{Vec2, Vector2},
|
||||||
|
shape::Rectangle,
|
||||||
|
Graphics2D,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
gui::{DrawInfo, GuiElem, GuiElemCfg},
|
||||||
|
gui_anim::AnimationController,
|
||||||
|
gui_base::{Button, ScrollBox},
|
||||||
|
gui_text::{Label, TextField},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ELEM_HEIGHT: f32 = 32.0;
|
||||||
|
|
||||||
|
pub enum Event {
|
||||||
|
RemoveTag(String),
|
||||||
|
AddTag(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct EditorForAnyTagInList {
|
||||||
|
config: GuiElemCfg,
|
||||||
|
pub tag: String,
|
||||||
|
label: Label,
|
||||||
|
rm_button: Button<[IconDelete; 1]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EditorForAnyTagInList {
|
||||||
|
pub fn new<T: From<Event> + 'static>(
|
||||||
|
tag: String,
|
||||||
|
sender: std::sync::mpsc::Sender<T>,
|
||||||
|
config: GuiElemCfg,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
config,
|
||||||
|
tag: tag.clone(),
|
||||||
|
label: Label::new(
|
||||||
|
GuiElemCfg::default(),
|
||||||
|
tag.clone(),
|
||||||
|
Color::WHITE,
|
||||||
|
None,
|
||||||
|
Vector2::new(0.0, 0.5),
|
||||||
|
),
|
||||||
|
rm_button: Button::new(
|
||||||
|
GuiElemCfg::default(),
|
||||||
|
move |btn| {
|
||||||
|
btn.disable();
|
||||||
|
sender.send(Event::RemoveTag(tag.clone()).into()).unwrap();
|
||||||
|
vec![]
|
||||||
|
},
|
||||||
|
[IconDelete::new(GuiElemCfg::default())],
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GuiElem for EditorForAnyTagInList {
|
||||||
|
fn draw(&mut self, info: &mut DrawInfo, g: &mut Graphics2D) {
|
||||||
|
let rm_button_size = (info.pos.height() * 0.8).min(info.pos.width() * 0.33);
|
||||||
|
let rm_button_padding = (info.pos.height() - rm_button_size) / 2.0;
|
||||||
|
let label_padding = info.pos.height() * 0.05;
|
||||||
|
let x_split = (info.pos.width() - rm_button_size) / info.pos.width();
|
||||||
|
self.rm_button.config_mut().pos = Rectangle::from_tuples(
|
||||||
|
(x_split, rm_button_padding / info.pos.height()),
|
||||||
|
(1.0, 1.0 - rm_button_padding / info.pos.height()),
|
||||||
|
);
|
||||||
|
self.label.config_mut().pos = Rectangle::from_tuples(
|
||||||
|
(0.0, label_padding / info.pos.height()),
|
||||||
|
(x_split, 1.0 - label_padding / info.pos.height()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
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.label.elem_mut(), self.rm_button.elem_mut()].into_iter())
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct IconDelete {
|
||||||
|
config: GuiElemCfg,
|
||||||
|
}
|
||||||
|
impl IconDelete {
|
||||||
|
pub fn new(config: GuiElemCfg) -> Self {
|
||||||
|
Self { config }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl GuiElem for IconDelete {
|
||||||
|
fn draw(&mut self, info: &mut DrawInfo, g: &mut Graphics2D) {
|
||||||
|
let thickness = (info.pos.height() * 0.01).max(1.0);
|
||||||
|
g.draw_line(
|
||||||
|
*info.pos.top_left(),
|
||||||
|
*info.pos.bottom_right(),
|
||||||
|
thickness,
|
||||||
|
Color::GRAY,
|
||||||
|
);
|
||||||
|
g.draw_line(
|
||||||
|
info.pos.top_right(),
|
||||||
|
info.pos.bottom_left(),
|
||||||
|
thickness,
|
||||||
|
Color::GRAY,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
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([].into_iter())
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct EditorForAnyTagAdder<T: From<Event>> {
|
||||||
|
config: GuiElemCfg,
|
||||||
|
event_sender: std::sync::mpsc::Sender<T>,
|
||||||
|
/// `1.0` = collapsed, `self.expand_to` = expanded (shows `c_picker` of height 7-1=6)
|
||||||
|
pub open_prog: AnimationController,
|
||||||
|
expand_to: f32,
|
||||||
|
c_value: TextField,
|
||||||
|
c_picker: ScrollBox<Vec<Button<[Label; 1]>>>,
|
||||||
|
last_search: String,
|
||||||
|
}
|
||||||
|
impl<T: From<Event> + 'static> EditorForAnyTagAdder<T> {
|
||||||
|
pub fn new(event_sender: std::sync::mpsc::Sender<T>) -> Self {
|
||||||
|
let expand_to = 7.0;
|
||||||
|
Self {
|
||||||
|
config: GuiElemCfg::default(),
|
||||||
|
event_sender,
|
||||||
|
open_prog: AnimationController::new(1.0, 1.0, 4.0),
|
||||||
|
expand_to,
|
||||||
|
c_value: TextField::new(
|
||||||
|
GuiElemCfg::default(),
|
||||||
|
"artist".to_owned(),
|
||||||
|
Color::DARK_GRAY,
|
||||||
|
Color::WHITE,
|
||||||
|
),
|
||||||
|
c_picker: ScrollBox::new(
|
||||||
|
GuiElemCfg::default().disabled(),
|
||||||
|
crate::gui_base::ScrollBoxSizeUnit::Pixels,
|
||||||
|
vec![],
|
||||||
|
vec![],
|
||||||
|
ELEM_HEIGHT,
|
||||||
|
),
|
||||||
|
last_search: String::from("\n"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn clear(&mut self, now: Instant) {
|
||||||
|
self.last_search = "\n".to_owned();
|
||||||
|
self.c_value.c_input.content.text().clear();
|
||||||
|
self.open_prog.set_target(now, 1.0);
|
||||||
|
self.config_mut().redraw = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T: From<Event> + 'static> GuiElem for EditorForAnyTagAdder<T> {
|
||||||
|
fn draw(&mut self, info: &mut crate::gui::DrawInfo, _g: &mut speedy2d::Graphics2D) {
|
||||||
|
let picker_enabled = self.open_prog.value(info.time) > 1.0;
|
||||||
|
self.c_picker.config_mut().enabled = picker_enabled;
|
||||||
|
if picker_enabled {
|
||||||
|
let split = 1.0 / self.open_prog.value(info.time) as f32;
|
||||||
|
self.c_value.config_mut().pos = Rectangle::from_tuples((0.0, 0.0), (1.0, split));
|
||||||
|
self.c_picker.config_mut().pos = Rectangle::from_tuples((0.0, split), (1.0, 1.0));
|
||||||
|
} else {
|
||||||
|
self.c_value.config_mut().pos = Rectangle::from_tuples((0.0, 0.0), (1.0, 1.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
let search = self.c_value.c_input.content.get_text().to_lowercase();
|
||||||
|
let search_changed = &self.last_search != &search;
|
||||||
|
if self.config.redraw || search_changed {
|
||||||
|
*self.c_value.c_input.content.color() = Color::WHITE;
|
||||||
|
if search_changed {
|
||||||
|
if search.is_empty() {
|
||||||
|
self.open_prog.set_target(info.time, 1.0);
|
||||||
|
} else {
|
||||||
|
self.open_prog.set_target(info.time, self.expand_to as f64);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut tags = info
|
||||||
|
.database
|
||||||
|
.songs()
|
||||||
|
.values()
|
||||||
|
.flat_map(|s| s.general.tags.iter())
|
||||||
|
.chain(
|
||||||
|
info.database
|
||||||
|
.songs()
|
||||||
|
.values()
|
||||||
|
.flat_map(|s| s.general.tags.iter()),
|
||||||
|
)
|
||||||
|
.chain(
|
||||||
|
info.database
|
||||||
|
.songs()
|
||||||
|
.values()
|
||||||
|
.flat_map(|s| s.general.tags.iter()),
|
||||||
|
)
|
||||||
|
.filter(|tag| tag.to_lowercase().contains(&search))
|
||||||
|
.map(|tag| tag.clone())
|
||||||
|
.collect::<BTreeSet<_>>();
|
||||||
|
if !tags.contains(self.c_value.c_input.content.get_text()) {
|
||||||
|
tags.insert(self.c_value.c_input.content.get_text().clone());
|
||||||
|
}
|
||||||
|
self.c_picker.children = tags
|
||||||
|
.iter()
|
||||||
|
.map(|tag| {
|
||||||
|
let sender = self.event_sender.clone();
|
||||||
|
Button::new(
|
||||||
|
GuiElemCfg::default(),
|
||||||
|
{
|
||||||
|
let tag = tag.clone();
|
||||||
|
move |_| {
|
||||||
|
sender.send(Event::AddTag(tag.clone()).into()).unwrap();
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[Label::new(
|
||||||
|
GuiElemCfg::default(),
|
||||||
|
tag.clone(),
|
||||||
|
Color::LIGHT_GRAY,
|
||||||
|
None,
|
||||||
|
Vec2::new(0.0, 0.5),
|
||||||
|
)],
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
self.c_picker.config_mut().redraw = true;
|
||||||
|
self.last_search = search;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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.c_value.elem_mut(), self.c_picker.elem_mut()].into_iter())
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SpacerForScrollBox {
|
||||||
|
config: GuiElemCfg,
|
||||||
|
}
|
||||||
|
impl SpacerForScrollBox {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
config: GuiElemCfg::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl GuiElem for SpacerForScrollBox {
|
||||||
|
fn draw(&mut self, _info: &mut DrawInfo, _g: &mut Graphics2D) {}
|
||||||
|
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([].into_iter())
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
@ -11,13 +11,12 @@ use crate::{
|
|||||||
gui::{GuiAction, GuiElem, GuiElemCfg, GuiElemChildren},
|
gui::{GuiAction, GuiElem, GuiElemCfg, GuiElemChildren},
|
||||||
gui_anim::AnimationController,
|
gui_anim::AnimationController,
|
||||||
gui_base::{Button, Panel, ScrollBox},
|
gui_base::{Button, Panel, ScrollBox},
|
||||||
|
gui_edit_any::{EditorForAnyTagAdder, EditorForAnyTagInList, SpacerForScrollBox, ELEM_HEIGHT},
|
||||||
gui_text::{Label, TextField},
|
gui_text::{Label, TextField},
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Fix bug where after selecting an artist you can't mouse-click the buttons anymore (to change it)
|
// TODO: Fix bug where after selecting an artist you can't mouse-click the buttons anymore (to change it)
|
||||||
|
|
||||||
const ELEM_HEIGHT: f32 = 32.0;
|
|
||||||
|
|
||||||
pub struct EditorForSongs {
|
pub struct EditorForSongs {
|
||||||
config: GuiElemCfg,
|
config: GuiElemCfg,
|
||||||
songs: Vec<Song>,
|
songs: Vec<Song>,
|
||||||
@ -33,11 +32,21 @@ pub enum Event {
|
|||||||
Close,
|
Close,
|
||||||
Apply,
|
Apply,
|
||||||
SetArtist(String, Option<ArtistId>),
|
SetArtist(String, Option<ArtistId>),
|
||||||
|
GeneralEvent(super::gui_edit_any::Event),
|
||||||
}
|
}
|
||||||
|
impl From<super::gui_edit_any::Event> for Event {
|
||||||
|
fn from(value: super::gui_edit_any::Event) -> Self {
|
||||||
|
Self::GeneralEvent(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct EditorForSongElems {
|
pub struct EditorForSongElems {
|
||||||
c_title: TextField,
|
c_title: TextField,
|
||||||
c_artist: EditorForSongArtistChooser,
|
c_artist: EditorForSongArtistChooser,
|
||||||
c_album: Label,
|
c_album: Label,
|
||||||
|
c_tags: Vec<EditorForAnyTagInList>,
|
||||||
|
c_new_tag: EditorForAnyTagAdder<Event>,
|
||||||
|
c_spacers: [SpacerForScrollBox; 4],
|
||||||
}
|
}
|
||||||
impl GuiElemChildren for EditorForSongElems {
|
impl GuiElemChildren for EditorForSongElems {
|
||||||
fn iter(&mut self) -> Box<dyn Iterator<Item = &mut dyn crate::gui::GuiElem> + '_> {
|
fn iter(&mut self) -> Box<dyn Iterator<Item = &mut dyn crate::gui::GuiElem> + '_> {
|
||||||
@ -47,11 +56,13 @@ impl GuiElemChildren for EditorForSongElems {
|
|||||||
self.c_artist.elem_mut(),
|
self.c_artist.elem_mut(),
|
||||||
self.c_album.elem_mut(),
|
self.c_album.elem_mut(),
|
||||||
]
|
]
|
||||||
.into_iter(),
|
.into_iter()
|
||||||
|
.chain(self.c_tags.iter_mut().map(|e| e.elem_mut()))
|
||||||
|
.chain(std::iter::once(self.c_new_tag.elem_mut())),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
fn len(&self) -> usize {
|
fn len(&self) -> usize {
|
||||||
3
|
3 + self.c_tags.len() + 1 + self.c_spacers.len()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,6 +107,32 @@ impl EditorForSongs {
|
|||||||
None,
|
None,
|
||||||
Vec2::new(0.0, 0.5),
|
Vec2::new(0.0, 0.5),
|
||||||
),
|
),
|
||||||
|
c_tags: {
|
||||||
|
let mut tags = Vec::new();
|
||||||
|
for song in songs.iter() {
|
||||||
|
for tag in song.general.tags.iter() {
|
||||||
|
if !tags.contains(&tag.as_str()) {
|
||||||
|
tags.push(tag.as_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tags.into_iter()
|
||||||
|
.map(|tag| {
|
||||||
|
EditorForAnyTagInList::new(
|
||||||
|
tag.to_owned(),
|
||||||
|
sender.clone(),
|
||||||
|
GuiElemCfg::default(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
},
|
||||||
|
c_new_tag: EditorForAnyTagAdder::new(sender.clone()),
|
||||||
|
c_spacers: [
|
||||||
|
SpacerForScrollBox::new(),
|
||||||
|
SpacerForScrollBox::new(),
|
||||||
|
SpacerForScrollBox::new(),
|
||||||
|
SpacerForScrollBox::new(),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
vec![],
|
vec![],
|
||||||
ELEM_HEIGHT,
|
ELEM_HEIGHT,
|
||||||
@ -169,7 +206,8 @@ impl GuiElem for EditorForSongs {
|
|||||||
gui.gui.set_normal_ui_enabled(true);
|
gui.gui.set_normal_ui_enabled(true);
|
||||||
}))),
|
}))),
|
||||||
Event::Apply => {
|
Event::Apply => {
|
||||||
for song in &self.songs {
|
let mut actions = Vec::new();
|
||||||
|
for song in self.songs.iter() {
|
||||||
let mut song = song.clone();
|
let mut song = song.clone();
|
||||||
|
|
||||||
let new_title = self
|
let new_title = self
|
||||||
@ -188,11 +226,14 @@ impl GuiElem for EditorForSongs {
|
|||||||
song.artist = artist_id;
|
song.artist = artist_id;
|
||||||
song.album = None;
|
song.album = None;
|
||||||
}
|
}
|
||||||
|
actions.push(Action::ModifySong(song, Req::none()));
|
||||||
|
}
|
||||||
|
if actions.len() == 1 {
|
||||||
info.actions
|
info.actions
|
||||||
.push(GuiAction::SendToServer(Action::ModifySong(
|
.push(GuiAction::SendToServer(actions.pop().unwrap()));
|
||||||
song,
|
} else if actions.len() > 1 {
|
||||||
Req::none(),
|
info.actions
|
||||||
)));
|
.push(GuiAction::SendToServer(Action::Multiple(actions)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::SetArtist(name, id) => {
|
Event::SetArtist(name, id) => {
|
||||||
@ -213,6 +254,52 @@ impl GuiElem for EditorForSongs {
|
|||||||
.text() = name;
|
.text() = name;
|
||||||
self.c_scrollbox.children.c_artist.config_mut().redraw = true;
|
self.c_scrollbox.children.c_artist.config_mut().redraw = true;
|
||||||
}
|
}
|
||||||
|
Event::GeneralEvent(e) => {
|
||||||
|
use super::gui_edit_any::Event as GeneralEvent;
|
||||||
|
match e {
|
||||||
|
GeneralEvent::RemoveTag(tag) => {
|
||||||
|
for song in self.songs.iter_mut() {
|
||||||
|
if let Some(i) =
|
||||||
|
song.general.tags.iter().position(|t| *t == tag)
|
||||||
|
{
|
||||||
|
song.general.tags.remove(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(i) = (&self.c_scrollbox.children.c_tags)
|
||||||
|
.into_iter()
|
||||||
|
.position(|e| e.tag == tag)
|
||||||
|
{
|
||||||
|
self.c_scrollbox.children.c_tags.remove(i);
|
||||||
|
self.c_scrollbox.config_mut().redraw = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GeneralEvent::AddTag(tag) => {
|
||||||
|
self.c_scrollbox.children.c_new_tag.clear(info.time);
|
||||||
|
for song in self.songs.iter_mut() {
|
||||||
|
if !song.general.tags.contains(&tag) {
|
||||||
|
song.general.tags.push(tag.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !(&self.c_scrollbox.children.c_tags)
|
||||||
|
.into_iter()
|
||||||
|
.any(|e| e.tag == tag)
|
||||||
|
{
|
||||||
|
self.c_scrollbox.children_heights.insert(
|
||||||
|
3 + self.c_scrollbox.children.c_tags.len(),
|
||||||
|
ELEM_HEIGHT,
|
||||||
|
);
|
||||||
|
self.c_scrollbox.children.c_tags.push(
|
||||||
|
EditorForAnyTagInList::new(
|
||||||
|
tag,
|
||||||
|
self.event_sender.clone(),
|
||||||
|
GuiElemCfg::default(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
self.c_scrollbox.config_mut().redraw = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Err(_) => break,
|
Err(_) => break,
|
||||||
}
|
}
|
||||||
@ -251,6 +338,25 @@ impl GuiElem for EditorForSongs {
|
|||||||
h.request_redraw();
|
h.request_redraw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Ok(val) = self
|
||||||
|
.c_scrollbox
|
||||||
|
.children
|
||||||
|
.c_new_tag
|
||||||
|
.open_prog
|
||||||
|
.update(info.time, false)
|
||||||
|
{
|
||||||
|
if let Some(v) = self
|
||||||
|
.c_scrollbox
|
||||||
|
.children_heights
|
||||||
|
.get_mut(3 + self.c_scrollbox.children.c_tags.len())
|
||||||
|
{
|
||||||
|
*v = ELEM_HEIGHT * val as f32;
|
||||||
|
self.c_scrollbox.config_mut().redraw = true;
|
||||||
|
}
|
||||||
|
if let Some(h) = &info.helper {
|
||||||
|
h.request_redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fn config(&self) -> &GuiElemCfg {
|
fn config(&self) -> &GuiElemCfg {
|
||||||
&self.config
|
&self.config
|
||||||
|
@ -1275,24 +1275,54 @@ impl GuiElem for ListSong {
|
|||||||
fn mouse_pressed(&mut self, e: &mut EventInfo, button: MouseButton) -> Vec<GuiAction> {
|
fn mouse_pressed(&mut self, e: &mut EventInfo, button: MouseButton) -> Vec<GuiAction> {
|
||||||
if button == MouseButton::Right && e.take() {
|
if button == MouseButton::Right && e.take() {
|
||||||
let id = self.id;
|
let id = self.id;
|
||||||
vec![GuiAction::Build(Box::new(move |db| {
|
let mut menu_actions: Vec<Box<dyn GuiElem + 'static>> = vec![Box::new(Button::new(
|
||||||
if let Some(me) = db.songs().get(&id) {
|
GuiElemCfg::default(),
|
||||||
let me = me.clone();
|
move |_| {
|
||||||
vec![GuiAction::ContextMenu(Some(vec![Box::new(Button::new(
|
vec![GuiAction::Build(Box::new(move |db| {
|
||||||
|
if let Some(me) = db.get_song(&id) {
|
||||||
|
vec![GuiAction::EditSongs(vec![me.clone()])]
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
}))]
|
||||||
|
},
|
||||||
|
[Label::new(
|
||||||
|
GuiElemCfg::default(),
|
||||||
|
format!("Edit this song"),
|
||||||
|
Color::WHITE,
|
||||||
|
None,
|
||||||
|
Vec2::new_y(0.5),
|
||||||
|
)],
|
||||||
|
))];
|
||||||
|
if self.selected.contains_song(&id) {
|
||||||
|
menu_actions.push(Box::new(Button::new(
|
||||||
|
GuiElemCfg::default(),
|
||||||
|
{
|
||||||
|
let selected = self.selected.clone();
|
||||||
|
move |_| {
|
||||||
|
let selected = selected.clone();
|
||||||
|
vec![GuiAction::Build(Box::new(move |db| {
|
||||||
|
vec![GuiAction::EditSongs(selected.view(
|
||||||
|
|(artists, albums, songs)| {
|
||||||
|
songs
|
||||||
|
.iter()
|
||||||
|
.filter_map(|id| db.get_song(id).cloned())
|
||||||
|
.collect()
|
||||||
|
},
|
||||||
|
))]
|
||||||
|
}))]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[Label::new(
|
||||||
GuiElemCfg::default(),
|
GuiElemCfg::default(),
|
||||||
move |_| vec![GuiAction::EditSongs(vec![me.clone()])],
|
format!("Edit selected songs"),
|
||||||
[Label::new(
|
Color::WHITE,
|
||||||
GuiElemCfg::default(),
|
None,
|
||||||
format!("Edit"),
|
Vec2::new_y(0.5),
|
||||||
Color::WHITE,
|
)],
|
||||||
None,
|
)));
|
||||||
Vec2::new_y(0.5),
|
}
|
||||||
)],
|
vec![GuiAction::ContextMenu(Some(menu_actions))]
|
||||||
))]))]
|
|
||||||
} else {
|
|
||||||
vec![]
|
|
||||||
}
|
|
||||||
}))]
|
|
||||||
} else {
|
} else {
|
||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,7 @@ mod gui;
|
|||||||
mod gui_anim;
|
mod gui_anim;
|
||||||
#[cfg(feature = "speedy2d")]
|
#[cfg(feature = "speedy2d")]
|
||||||
mod gui_base;
|
mod gui_base;
|
||||||
|
pub mod gui_edit_any;
|
||||||
#[cfg(feature = "speedy2d")]
|
#[cfg(feature = "speedy2d")]
|
||||||
mod gui_edit_song;
|
mod gui_edit_song;
|
||||||
#[cfg(feature = "speedy2d")]
|
#[cfg(feature = "speedy2d")]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user