added multiselect to the client's library

This commit is contained in:
Mark 2023-10-04 16:16:05 +02:00
parent 9c340aa913
commit 6bab2bc075
5 changed files with 614 additions and 160 deletions

View File

@ -436,6 +436,7 @@ pub enum Dragging {
Album(AlbumId), Album(AlbumId),
Song(SongId), Song(SongId),
Queue(Queue), Queue(Queue),
Queues(Vec<Queue>),
} }
/// GuiElems have access to this within draw. /// GuiElems have access to this within draw.
@ -733,7 +734,7 @@ pub fn adjust_pos(outer: &Rectangle, rel_pos: &Vec2) -> Vec2 {
} }
impl Gui { impl Gui {
fn exec_gui_action(&mut self, action: GuiAction) { pub fn exec_gui_action(&mut self, action: GuiAction) {
match action { match action {
GuiAction::Build(f) => { GuiAction::Build(f) => {
let actions = f(&mut *self.database.lock().unwrap()); let actions = f(&mut *self.database.lock().unwrap());
@ -891,6 +892,11 @@ impl WindowHandler<GuiEvent> for Gui {
25.0, 25.0,
Color::from_int_rgba(100, 0, 255, 100), Color::from_int_rgba(100, 0, 255, 100),
), ),
Dragging::Queues(_) => graphics.draw_circle(
self.mouse_pos,
25.0,
Color::from_int_rgba(100, 0, 255, 100),
),
} }
} }
} }

View File

@ -373,7 +373,9 @@ impl GuiElemTrait for GuiEdit {
} }
fn dragged(&mut self, dragged: Dragging) -> Vec<GuiAction> { fn dragged(&mut self, dragged: Dragging) -> Vec<GuiAction> {
let dragged = match dragged { let dragged = match dragged {
Dragging::Artist(_) | Dragging::Album(_) | Dragging::Song(_) => dragged, Dragging::Artist(_) | Dragging::Album(_) | Dragging::Song(_) | Dragging::Queues(_) => {
dragged
}
Dragging::Queue(q) => match q.content() { Dragging::Queue(q) => match q.content() {
QueueContent::Song(id) => Dragging::Song(*id), QueueContent::Song(id) => Dragging::Song(*id),
_ => Dragging::Queue(q), _ => Dragging::Queue(q),
@ -396,6 +398,7 @@ impl GuiElemTrait for GuiEdit {
} }
} }
Dragging::Queue(_) => return vec![], Dragging::Queue(_) => return vec![],
Dragging::Queues(_) => return vec![],
} }
self.reload = true; self.reload = true;
vec![] vec![]

View File

@ -1,15 +1,21 @@
use std::{ use std::{
cmp::Ordering, cmp::Ordering,
collections::HashSet,
rc::Rc, rc::Rc,
sync::{ sync::{
atomic::{AtomicBool, AtomicUsize}, atomic::{AtomicBool, AtomicUsize},
Arc, Mutex, mpsc, Mutex,
}, },
}; };
use clap::builder::StringValueParser;
use musicdb_lib::data::{ use musicdb_lib::data::{
album::Album, artist::Artist, database::Database, song::Song, AlbumId, ArtistId, GeneralData, album::Album,
SongId, artist::Artist,
database::Database,
queue::{Queue, QueueContent},
song::Song,
AlbumId, ArtistId, GeneralData, SongId,
}; };
use regex::{Regex, RegexBuilder}; use regex::{Regex, RegexBuilder};
use speedy2d::{ use speedy2d::{
@ -21,8 +27,7 @@ use speedy2d::{
use crate::{ use crate::{
gui::{Dragging, DrawInfo, GuiAction, GuiElem, GuiElemCfg, GuiElemTrait}, gui::{Dragging, DrawInfo, GuiAction, GuiElem, GuiElemCfg, GuiElemTrait},
gui_base::{Button, Panel, ScrollBox, Slider}, gui_base::{Button, Panel, ScrollBox},
gui_edit::GuiEdit,
gui_text::{Label, TextField}, gui_text::{Label, TextField},
gui_wrappers::WithFocusHotkey, gui_wrappers::WithFocusHotkey,
}; };
@ -34,7 +39,6 @@ with Regex search and drag-n-drop.
*/ */
#[derive(Clone)]
pub struct LibraryBrowser { pub struct LibraryBrowser {
config: GuiElemCfg, config: GuiElemCfg,
pub children: Vec<GuiElem>, pub children: Vec<GuiElem>,
@ -46,6 +50,7 @@ pub struct LibraryBrowser {
Vec<(AlbumId, Vec<(SongId, f32)>, f32)>, Vec<(AlbumId, Vec<(SongId, f32)>, f32)>,
f32, f32,
)>, )>,
selected: Selected,
// - - - // - - -
search_artist: String, search_artist: String,
search_artist_regex: Option<Regex>, search_artist_regex: Option<Regex>,
@ -64,6 +69,79 @@ pub struct LibraryBrowser {
filter_songs: Rc<Mutex<Filter>>, filter_songs: Rc<Mutex<Filter>>,
filter_albums: Rc<Mutex<Filter>>, filter_albums: Rc<Mutex<Filter>>,
filter_artists: Rc<Mutex<Filter>>, filter_artists: Rc<Mutex<Filter>>,
do_something_sender: mpsc::Sender<Box<dyn FnOnce(&mut Self)>>,
do_something_receiver: mpsc::Receiver<Box<dyn FnOnce(&mut Self)>>,
}
impl Clone for LibraryBrowser {
fn clone(&self) -> Self {
Self::new(self.config.clone())
}
}
#[derive(Clone)]
struct Selected(Rc<Mutex<(HashSet<ArtistId>, HashSet<AlbumId>, HashSet<SongId>)>>);
impl Selected {
pub fn as_queue(&self, lb: &LibraryBrowser, db: &Database) -> Vec<Queue> {
let lock = self.0.lock().unwrap();
let (sel_artists, sel_albums, sel_songs) = &*lock;
let mut out = vec![];
for (artist, singles, albums, _) in &lb.library_filtered {
let artist_selected = sel_artists.contains(artist);
let mut local_artist_owned = vec![];
let mut local_artist = if artist_selected {
&mut local_artist_owned
} else {
&mut out
};
for (song, _) in singles {
let song_selected = sel_songs.contains(song);
if song_selected {
local_artist.push(QueueContent::Song(*song).into());
}
}
for (album, songs, _) in albums {
let album_selected = sel_albums.contains(album);
let mut local_album_owned = vec![];
let local_album = if album_selected {
&mut local_album_owned
} else {
&mut local_artist
};
for (song, _) in songs {
let song_selected = sel_songs.contains(song);
if song_selected {
local_album.push(QueueContent::Song(*song).into());
}
}
if album_selected {
local_artist.push(
QueueContent::Folder(
0,
local_album_owned,
match db.albums().get(album) {
Some(v) => v.name.clone(),
None => "< unknown album >".to_owned(),
},
)
.into(),
);
}
}
if artist_selected {
out.push(
QueueContent::Folder(
0,
local_artist_owned,
match db.artists().get(artist) {
Some(v) => v.name.to_owned(),
None => "< unknown artist >".to_owned(),
},
)
.into(),
);
}
}
out
}
} }
fn search_regex_new(pat: &str, case_insensitive: bool) -> Result<Option<Regex>, regex::Error> { fn search_regex_new(pat: &str, case_insensitive: bool) -> Result<Option<Regex>, regex::Error> {
if pat.is_empty() { if pat.is_empty() {
@ -108,6 +186,7 @@ impl LibraryBrowser {
crate::gui_base::ScrollBoxSizeUnit::Pixels, crate::gui_base::ScrollBoxSizeUnit::Pixels,
vec![], vec![],
); );
let (do_something_sender, do_something_receiver) = mpsc::channel();
let search_settings_changed = Rc::new(AtomicBool::new(false)); let search_settings_changed = Rc::new(AtomicBool::new(false));
let search_was_case_sensitive = false; let search_was_case_sensitive = false;
let search_is_case_sensitive = Rc::new(AtomicBool::new(search_was_case_sensitive)); let search_is_case_sensitive = Rc::new(AtomicBool::new(search_was_case_sensitive));
@ -144,6 +223,11 @@ impl LibraryBrowser {
and: true, and: true,
filters: vec![], filters: vec![],
})); }));
let selected = Selected(Rc::new(Mutex::new((
HashSet::new(),
HashSet::new(),
HashSet::new(),
))));
Self { Self {
config, config,
children: vec![ children: vec![
@ -159,11 +243,14 @@ impl LibraryBrowser {
Rc::clone(&filter_songs), Rc::clone(&filter_songs),
Rc::clone(&filter_albums), Rc::clone(&filter_albums),
Rc::clone(&filter_artists), Rc::clone(&filter_artists),
selected.clone(),
do_something_sender.clone(),
)), )),
], ],
// - - - // - - -
library_sorted: vec![], library_sorted: vec![],
library_filtered: vec![], library_filtered: vec![],
selected,
// - - - // - - -
search_artist: String::new(), search_artist: String::new(),
search_artist_regex: None, search_artist_regex: None,
@ -182,6 +269,8 @@ impl LibraryBrowser {
filter_songs, filter_songs,
filter_albums, filter_albums,
filter_artists, filter_artists,
do_something_sender,
do_something_receiver,
} }
} }
} }
@ -205,6 +294,13 @@ impl GuiElemTrait for LibraryBrowser {
Box::new(self.clone()) Box::new(self.clone())
} }
fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) { fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) {
loop {
if let Ok(action) = self.do_something_receiver.try_recv() {
action(self);
} else {
break;
}
}
// search // search
let mut search_changed = false; let mut search_changed = false;
let mut rebuild_regex = false; let mut rebuild_regex = false;
@ -574,6 +670,7 @@ impl LibraryBrowser {
} else { } else {
format!("[ Artist #{id} ]") format!("[ Artist #{id} ]")
}, },
self.selected.clone(),
)), )),
h * 2.5, h * 2.5,
) )
@ -588,6 +685,7 @@ impl LibraryBrowser {
} else { } else {
format!("[ Album #{id} ]") format!("[ Album #{id} ]")
}, },
self.selected.clone(),
)), )),
h * 1.5, h * 1.5,
) )
@ -602,6 +700,7 @@ impl LibraryBrowser {
} else { } else {
format!("[ Song #{id} ]") format!("[ Song #{id} ]")
}, },
self.selected.clone(),
)), )),
h, h,
) )
@ -615,9 +714,11 @@ struct ListArtist {
children: Vec<GuiElem>, children: Vec<GuiElem>,
mouse: bool, mouse: bool,
mouse_pos: Vec2, mouse_pos: Vec2,
selected: Selected,
sel: bool,
} }
impl ListArtist { impl ListArtist {
pub fn new(config: GuiElemCfg, id: ArtistId, name: String) -> Self { pub fn new(mut config: GuiElemCfg, id: ArtistId, name: String, selected: Selected) -> Self {
let label = Label::new( let label = Label::new(
GuiElemCfg::default(), GuiElemCfg::default(),
name, name,
@ -625,12 +726,15 @@ impl ListArtist {
None, None,
Vec2::new(0.0, 0.5), Vec2::new(0.0, 0.5),
); );
config.redraw = true;
Self { Self {
config: config.w_mouse(), config: config.w_mouse(),
id, id,
children: vec![GuiElem::new(label)], children: vec![GuiElem::new(label)],
mouse: false, mouse: false,
mouse_pos: Vec2::ZERO, mouse_pos: Vec2::ZERO,
selected,
sel: false,
} }
} }
} }
@ -654,11 +758,50 @@ impl GuiElemTrait for ListArtist {
Box::new(self.clone()) Box::new(self.clone())
} }
fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) { fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) {
if self.config.redraw {
self.config.redraw = false;
let sel = self.selected.0.lock().unwrap().0.contains(&self.id);
if sel != self.sel {
self.sel = sel;
if sel {
self.children.push(GuiElem::new(Panel::with_background(
GuiElemCfg::default(),
vec![],
Color::from_rgba(1.0, 1.0, 1.0, 0.2),
)));
} else {
self.children.pop();
}
}
}
if self.mouse { if self.mouse {
if info.pos.contains(info.mouse_pos) { if info.pos.contains(info.mouse_pos) {
return; return;
} else { } else {
self.mouse = false; self.mouse = false;
if self.sel {
let selected = self.selected.clone();
info.actions.push(GuiAction::Do(Box::new(move |gui| {
let q = selected.as_queue(
gui.gui
.inner
.children()
.nth(2)
.unwrap()
.inner
.children()
.nth(2)
.unwrap()
.try_as()
.unwrap(),
&gui.database.lock().unwrap(),
);
gui.exec_gui_action(GuiAction::SetDragging(Some((
Dragging::Queues(q),
None,
))));
})));
}
} }
} }
self.mouse_pos = Vec2::new( self.mouse_pos = Vec2::new(
@ -673,6 +816,9 @@ impl GuiElemTrait for ListArtist {
let w = self.config.pixel_pos.width(); let w = self.config.pixel_pos.width();
let h = self.config.pixel_pos.height(); let h = self.config.pixel_pos.height();
let mut el = GuiElem::new(self.clone()); let mut el = GuiElem::new(self.clone());
if self.sel {
vec![]
} else {
vec![GuiAction::SetDragging(Some(( vec![GuiAction::SetDragging(Some((
Dragging::Artist(self.id), Dragging::Artist(self.id),
Some(Box::new(move |i, g| { Some(Box::new(move |i, g| {
@ -685,6 +831,7 @@ impl GuiElemTrait for ListArtist {
el.draw(i, g) el.draw(i, g)
})), })),
)))] )))]
}
} else { } else {
vec![] vec![]
} }
@ -692,14 +839,15 @@ impl GuiElemTrait for ListArtist {
fn mouse_up(&mut self, button: MouseButton) -> Vec<GuiAction> { fn mouse_up(&mut self, button: MouseButton) -> Vec<GuiAction> {
if self.mouse && button == MouseButton::Left { if self.mouse && button == MouseButton::Left {
self.mouse = false; self.mouse = false;
vec![GuiAction::OpenEditPanel(GuiElem::new(GuiEdit::new( self.config.redraw = true;
GuiElemCfg::default(), if !self.sel {
crate::gui_edit::Editable::Artist(vec![self.id]), self.selected.0.lock().unwrap().0.insert(self.id);
)))]
} else { } else {
vec![] self.selected.0.lock().unwrap().0.remove(&self.id);
} }
} }
vec![]
}
} }
#[derive(Clone)] #[derive(Clone)]
@ -709,9 +857,11 @@ struct ListAlbum {
children: Vec<GuiElem>, children: Vec<GuiElem>,
mouse: bool, mouse: bool,
mouse_pos: Vec2, mouse_pos: Vec2,
selected: Selected,
sel: bool,
} }
impl ListAlbum { impl ListAlbum {
pub fn new(config: GuiElemCfg, id: AlbumId, name: String) -> Self { pub fn new(mut config: GuiElemCfg, id: AlbumId, name: String, selected: Selected) -> Self {
let label = Label::new( let label = Label::new(
GuiElemCfg::default(), GuiElemCfg::default(),
name, name,
@ -719,12 +869,15 @@ impl ListAlbum {
None, None,
Vec2::new(0.0, 0.5), Vec2::new(0.0, 0.5),
); );
config.redraw = true;
Self { Self {
config: config.w_mouse(), config: config.w_mouse(),
id, id,
children: vec![GuiElem::new(label)], children: vec![GuiElem::new(label)],
mouse: false, mouse: false,
mouse_pos: Vec2::ZERO, mouse_pos: Vec2::ZERO,
selected,
sel: false,
} }
} }
} }
@ -748,11 +901,50 @@ impl GuiElemTrait for ListAlbum {
Box::new(self.clone()) Box::new(self.clone())
} }
fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) { fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) {
if self.config.redraw {
self.config.redraw = false;
let sel = self.selected.0.lock().unwrap().1.contains(&self.id);
if sel != self.sel {
self.sel = sel;
if sel {
self.children.push(GuiElem::new(Panel::with_background(
GuiElemCfg::default(),
vec![],
Color::from_rgba(1.0, 1.0, 1.0, 0.2),
)));
} else {
self.children.pop();
}
}
}
if self.mouse { if self.mouse {
if info.pos.contains(info.mouse_pos) { if info.pos.contains(info.mouse_pos) {
return; return;
} else { } else {
self.mouse = false; self.mouse = false;
if self.sel {
let selected = self.selected.clone();
info.actions.push(GuiAction::Do(Box::new(move |gui| {
let q = selected.as_queue(
gui.gui
.inner
.children()
.nth(2)
.unwrap()
.inner
.children()
.nth(2)
.unwrap()
.try_as()
.unwrap(),
&gui.database.lock().unwrap(),
);
gui.exec_gui_action(GuiAction::SetDragging(Some((
Dragging::Queues(q),
None,
))));
})));
}
} }
} }
self.mouse_pos = Vec2::new( self.mouse_pos = Vec2::new(
@ -767,6 +959,9 @@ impl GuiElemTrait for ListAlbum {
let w = self.config.pixel_pos.width(); let w = self.config.pixel_pos.width();
let h = self.config.pixel_pos.height(); let h = self.config.pixel_pos.height();
let mut el = GuiElem::new(self.clone()); let mut el = GuiElem::new(self.clone());
if self.sel {
vec![]
} else {
vec![GuiAction::SetDragging(Some(( vec![GuiAction::SetDragging(Some((
Dragging::Album(self.id), Dragging::Album(self.id),
Some(Box::new(move |i, g| { Some(Box::new(move |i, g| {
@ -779,6 +974,7 @@ impl GuiElemTrait for ListAlbum {
el.draw(i, g) el.draw(i, g)
})), })),
)))] )))]
}
} else { } else {
vec![] vec![]
} }
@ -786,14 +982,15 @@ impl GuiElemTrait for ListAlbum {
fn mouse_up(&mut self, button: MouseButton) -> Vec<GuiAction> { fn mouse_up(&mut self, button: MouseButton) -> Vec<GuiAction> {
if self.mouse && button == MouseButton::Left { if self.mouse && button == MouseButton::Left {
self.mouse = false; self.mouse = false;
vec![GuiAction::OpenEditPanel(GuiElem::new(GuiEdit::new( self.config.redraw = true;
GuiElemCfg::default(), if !self.sel {
crate::gui_edit::Editable::Album(vec![self.id]), self.selected.0.lock().unwrap().1.insert(self.id);
)))]
} else { } else {
vec![] self.selected.0.lock().unwrap().1.remove(&self.id);
} }
} }
vec![]
}
} }
#[derive(Clone)] #[derive(Clone)]
@ -803,9 +1000,11 @@ struct ListSong {
children: Vec<GuiElem>, children: Vec<GuiElem>,
mouse: bool, mouse: bool,
mouse_pos: Vec2, mouse_pos: Vec2,
selected: Selected,
sel: bool,
} }
impl ListSong { impl ListSong {
pub fn new(config: GuiElemCfg, id: SongId, name: String) -> Self { pub fn new(mut config: GuiElemCfg, id: SongId, name: String, selected: Selected) -> Self {
let label = Label::new( let label = Label::new(
GuiElemCfg::default(), GuiElemCfg::default(),
name, name,
@ -813,12 +1012,15 @@ impl ListSong {
None, None,
Vec2::new(0.0, 0.5), Vec2::new(0.0, 0.5),
); );
config.redraw = true;
Self { Self {
config: config.w_mouse(), config: config.w_mouse(),
id, id,
children: vec![GuiElem::new(label)], children: vec![GuiElem::new(label)],
mouse: false, mouse: false,
mouse_pos: Vec2::ZERO, mouse_pos: Vec2::ZERO,
selected,
sel: false,
} }
} }
} }
@ -842,11 +1044,50 @@ impl GuiElemTrait for ListSong {
Box::new(self.clone()) Box::new(self.clone())
} }
fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) { fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) {
if self.config.redraw {
self.config.redraw = false;
let sel = self.selected.0.lock().unwrap().2.contains(&self.id);
if sel != self.sel {
self.sel = sel;
if sel {
self.children.push(GuiElem::new(Panel::with_background(
GuiElemCfg::default(),
vec![],
Color::from_rgba(1.0, 1.0, 1.0, 0.2),
)));
} else {
self.children.pop();
}
}
}
if self.mouse { if self.mouse {
if info.pos.contains(info.mouse_pos) { if info.pos.contains(info.mouse_pos) {
return; return;
} else { } else {
self.mouse = false; self.mouse = false;
if self.sel {
let selected = self.selected.clone();
info.actions.push(GuiAction::Do(Box::new(move |gui| {
let q = selected.as_queue(
gui.gui
.inner
.children()
.nth(2)
.unwrap()
.inner
.children()
.nth(2)
.unwrap()
.try_as()
.unwrap(),
&gui.database.lock().unwrap(),
);
gui.exec_gui_action(GuiAction::SetDragging(Some((
Dragging::Queues(q),
None,
))));
})));
}
} }
} }
self.mouse_pos = Vec2::new( self.mouse_pos = Vec2::new(
@ -861,6 +1102,9 @@ impl GuiElemTrait for ListSong {
let w = self.config.pixel_pos.width(); let w = self.config.pixel_pos.width();
let h = self.config.pixel_pos.height(); let h = self.config.pixel_pos.height();
let mut el = GuiElem::new(self.clone()); let mut el = GuiElem::new(self.clone());
if self.sel {
vec![]
} else {
vec![GuiAction::SetDragging(Some(( vec![GuiAction::SetDragging(Some((
Dragging::Song(self.id), Dragging::Song(self.id),
Some(Box::new(move |i, g| { Some(Box::new(move |i, g| {
@ -873,6 +1117,7 @@ impl GuiElemTrait for ListSong {
el.draw(i, g) el.draw(i, g)
})), })),
)))] )))]
}
} else { } else {
vec![] vec![]
} }
@ -880,14 +1125,15 @@ impl GuiElemTrait for ListSong {
fn mouse_up(&mut self, button: MouseButton) -> Vec<GuiAction> { fn mouse_up(&mut self, button: MouseButton) -> Vec<GuiAction> {
if self.mouse && button == MouseButton::Left { if self.mouse && button == MouseButton::Left {
self.mouse = false; self.mouse = false;
vec![GuiAction::OpenEditPanel(GuiElem::new(GuiEdit::new( self.config.redraw = true;
GuiElemCfg::default(), if !self.sel {
crate::gui_edit::Editable::Song(vec![self.id]), self.selected.0.lock().unwrap().2.insert(self.id);
)))]
} else { } else {
vec![] self.selected.0.lock().unwrap().2.remove(&self.id);
} }
} }
vec![]
}
} }
#[derive(Clone)] #[derive(Clone)]
@ -914,13 +1160,25 @@ impl FilterPanel {
filter_songs: Rc<Mutex<Filter>>, filter_songs: Rc<Mutex<Filter>>,
filter_albums: Rc<Mutex<Filter>>, filter_albums: Rc<Mutex<Filter>>,
filter_artists: Rc<Mutex<Filter>>, filter_artists: Rc<Mutex<Filter>>,
selected: Selected,
do_something_sender: mpsc::Sender<Box<dyn FnOnce(&mut LibraryBrowser)>>,
) -> Self { ) -> Self {
let is_case_sensitive = search_is_case_sensitive.load(std::sync::atomic::Ordering::Relaxed); let is_case_sensitive = search_is_case_sensitive.load(std::sync::atomic::Ordering::Relaxed);
let prefer_start_matches = let prefer_start_matches =
search_prefer_start_matches.load(std::sync::atomic::Ordering::Relaxed); search_prefer_start_matches.load(std::sync::atomic::Ordering::Relaxed);
let ssc1 = Rc::clone(&search_settings_changed); let ssc1 = Rc::clone(&search_settings_changed);
let ssc2 = Rc::clone(&search_settings_changed); let ssc2 = Rc::clone(&search_settings_changed);
let tab_settings = GuiElem::new(ScrollBox::new( let ssc3 = Rc::clone(&search_settings_changed);
let ssc4 = Rc::clone(&search_settings_changed);
let ssc5 = Rc::clone(&search_settings_changed);
let ssc6 = Rc::clone(&search_settings_changed);
let ssc7 = Rc::clone(&search_settings_changed);
let sel3 = selected.clone();
let sel4 = selected.clone();
let sel5 = selected.clone();
let sel6 = selected.clone();
let sel7 = selected.clone();
let tab_main = GuiElem::new(ScrollBox::new(
GuiElemCfg::default(), GuiElemCfg::default(),
crate::gui_base::ScrollBoxSizeUnit::Pixels, crate::gui_base::ScrollBoxSizeUnit::Pixels,
vec![ vec![
@ -997,6 +1255,153 @@ impl FilterPanel {
)), )),
1.0, 1.0,
), ),
(
GuiElem::new(Button::new(
GuiElemCfg::default(),
move |_| {
ssc3.store(true, std::sync::atomic::Ordering::Relaxed);
let mut sel = sel3.0.lock().unwrap();
sel.2 = HashSet::new();
sel.1 = HashSet::new();
sel.0 = HashSet::new();
vec![]
},
vec![GuiElem::new(Label::new(
GuiElemCfg::default(),
"deselect all".to_owned(),
Color::GRAY,
None,
Vec2::new(0.5, 0.5),
))],
)),
1.0,
),
(
GuiElem::new(Panel::new(
GuiElemCfg::default(),
vec![
GuiElem::new(Button::new(
GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (0.5, 1.0))),
{
let dss = do_something_sender.clone();
move |_| {
dss.send(Box::new(|s| {
s.search_settings_changed
.store(true, std::sync::atomic::Ordering::Relaxed);
let mut sel = s.selected.0.lock().unwrap();
for (id, singles, albums, _) in &s.library_filtered {
sel.0.insert(*id);
for (s, _) in singles {
sel.2.insert(*s);
}
for (id, album, _) in albums {
sel.1.insert(*id);
for (s, _) in album {
sel.2.insert(*s);
}
}
}
}))
.unwrap();
vec![]
}
},
vec![GuiElem::new(Label::new(
GuiElemCfg::default(),
"select all".to_owned(),
Color::GRAY,
None,
Vec2::new(0.5, 0.5),
))],
)),
GuiElem::new(Button::new(
GuiElemCfg::at(Rectangle::from_tuples((0.55, 0.0), (0.65, 1.0))),
{
let dss = do_something_sender.clone();
move |_| {
dss.send(Box::new(|s| {
s.search_settings_changed
.store(true, std::sync::atomic::Ordering::Relaxed);
let mut sel = s.selected.0.lock().unwrap();
for (_, singles, albums, _) in &s.library_filtered {
for (s, _) in singles {
sel.2.insert(*s);
}
for (_, album, _) in albums {
for (s, _) in album {
sel.2.insert(*s);
}
}
}
}))
.unwrap();
vec![]
}
},
vec![GuiElem::new(Label::new(
GuiElemCfg::default(),
"songs".to_owned(),
Color::GRAY,
None,
Vec2::new(0.5, 0.5),
))],
)),
GuiElem::new(Button::new(
GuiElemCfg::at(Rectangle::from_tuples((0.7, 0.0), (0.8, 1.0))),
{
let dss = do_something_sender.clone();
move |_| {
dss.send(Box::new(|s| {
s.search_settings_changed
.store(true, std::sync::atomic::Ordering::Relaxed);
let mut sel = s.selected.0.lock().unwrap();
for (_, _, albums, _) in &s.library_filtered {
for (id, _, _) in albums {
sel.1.insert(*id);
}
}
}))
.unwrap();
vec![]
}
},
vec![GuiElem::new(Label::new(
GuiElemCfg::default(),
"albums".to_owned(),
Color::GRAY,
None,
Vec2::new(0.5, 0.5),
))],
)),
GuiElem::new(Button::new(
GuiElemCfg::at(Rectangle::from_tuples((0.85, 0.0), (0.95, 1.0))),
{
let dss = do_something_sender.clone();
move |_| {
dss.send(Box::new(|s| {
s.search_settings_changed
.store(true, std::sync::atomic::Ordering::Relaxed);
let mut sel = s.selected.0.lock().unwrap();
for (id, _, _, _) in &s.library_filtered {
sel.0.insert(*id);
}
}))
.unwrap();
vec![]
}
},
vec![GuiElem::new(Label::new(
GuiElemCfg::default(),
"artists".to_owned(),
Color::GRAY,
None,
Vec2::new(0.5, 0.5),
))],
)),
],
)),
1.0,
),
], ],
)); ));
let tab_filters_songs = GuiElem::new(ScrollBox::new( let tab_filters_songs = GuiElem::new(ScrollBox::new(
@ -1015,7 +1420,6 @@ impl FilterPanel {
vec![], vec![],
)); ));
let new_tab = Rc::new(AtomicUsize::new(0)); let new_tab = Rc::new(AtomicUsize::new(0));
let set_tab_0 = Rc::clone(&new_tab);
let set_tab_1 = Rc::clone(&new_tab); let set_tab_1 = Rc::clone(&new_tab);
let set_tab_2 = Rc::clone(&new_tab); let set_tab_2 = Rc::clone(&new_tab);
let set_tab_3 = Rc::clone(&new_tab); let set_tab_3 = Rc::clone(&new_tab);
@ -1027,56 +1431,60 @@ impl FilterPanel {
GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (1.0, HEIGHT))), GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (1.0, HEIGHT))),
vec![ vec![
GuiElem::new(Button::new( GuiElem::new(Button::new(
GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (0.4, 1.0))), GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (0.33, 1.0))),
move |_| { move |_| {
set_tab_0.store(0, std::sync::atomic::Ordering::Relaxed); let v = if set_tab_1.load(std::sync::atomic::Ordering::Relaxed) != 1
{
1
} else {
0
};
set_tab_1.store(v, std::sync::atomic::Ordering::Relaxed);
vec![] vec![]
}, },
vec![GuiElem::new(Label::new( vec![GuiElem::new(Label::new(
GuiElemCfg::default(), GuiElemCfg::default(),
"Settings".to_owned(), "Filter Songs".to_owned(),
Color::WHITE,
None,
Vec2::new(0.5, 0.5),
))],
)),
GuiElem::new(Button::new(
GuiElemCfg::at(Rectangle::from_tuples((0.4, 0.0), (0.6, 1.0))),
move |_| {
set_tab_1.store(1, std::sync::atomic::Ordering::Relaxed);
vec![]
},
vec![GuiElem::new(Label::new(
GuiElemCfg::default(),
"Filters\n(Songs)".to_owned(),
Color::GRAY, Color::GRAY,
None, None,
Vec2::new(0.5, 0.5), Vec2::new(0.5, 0.5),
))], ))],
)), )),
GuiElem::new(Button::new( GuiElem::new(Button::new(
GuiElemCfg::at(Rectangle::from_tuples((0.6, 0.0), (0.8, 1.0))), GuiElemCfg::at(Rectangle::from_tuples((0.33, 0.0), (0.67, 1.0))),
move |_| { move |_| {
set_tab_2.store(2, std::sync::atomic::Ordering::Relaxed); let v = if set_tab_2.load(std::sync::atomic::Ordering::Relaxed) != 2
{
2
} else {
0
};
set_tab_2.store(v, std::sync::atomic::Ordering::Relaxed);
vec![] vec![]
}, },
vec![GuiElem::new(Label::new( vec![GuiElem::new(Label::new(
GuiElemCfg::default(), GuiElemCfg::default(),
"Filters\n(Albums)".to_owned(), "Filter Albums".to_owned(),
Color::GRAY, Color::GRAY,
None, None,
Vec2::new(0.5, 0.5), Vec2::new(0.5, 0.5),
))], ))],
)), )),
GuiElem::new(Button::new( GuiElem::new(Button::new(
GuiElemCfg::at(Rectangle::from_tuples((0.8, 0.0), (1.0, 1.0))), GuiElemCfg::at(Rectangle::from_tuples((0.67, 0.0), (1.0, 1.0))),
move |_| { move |_| {
set_tab_3.store(3, std::sync::atomic::Ordering::Relaxed); let v = if set_tab_3.load(std::sync::atomic::Ordering::Relaxed) != 3
{
3
} else {
0
};
set_tab_3.store(v, std::sync::atomic::Ordering::Relaxed);
vec![] vec![]
}, },
vec![GuiElem::new(Label::new( vec![GuiElem::new(Label::new(
GuiElemCfg::default(), GuiElemCfg::default(),
"Filters\n(Artists)".to_owned(), "Filter Artists".to_owned(),
Color::GRAY, Color::GRAY,
None, None,
Vec2::new(0.5, 0.5), Vec2::new(0.5, 0.5),
@ -1087,7 +1495,7 @@ impl FilterPanel {
GuiElem::new(Panel::new( GuiElem::new(Panel::new(
GuiElemCfg::at(Rectangle::from_tuples((0.0, HEIGHT), (1.0, 1.0))), GuiElemCfg::at(Rectangle::from_tuples((0.0, HEIGHT), (1.0, 1.0))),
vec![ vec![
tab_settings, tab_main,
tab_filters_songs, tab_filters_songs,
tab_filters_albums, tab_filters_albums,
tab_filters_artists, tab_filters_artists,
@ -1390,12 +1798,15 @@ impl GuiElemTrait for FilterPanel {
self.line_height = info.line_height; self.line_height = info.line_height;
} }
// maybe switch tabs // maybe switch tabs
let new_tab = self.new_tab.load(std::sync::atomic::Ordering::Relaxed); let mut new_tab = self.new_tab.load(std::sync::atomic::Ordering::Relaxed);
let mut load_tab = false; let mut load_tab = false;
if new_tab < usize::MAX { if new_tab != self.tab {
load_tab = true; load_tab = true;
if new_tab == usize::MAX {
self.new_tab self.new_tab
.store(usize::MAX, std::sync::atomic::Ordering::Relaxed); .store(self.tab, std::sync::atomic::Ordering::Relaxed);
new_tab = self.tab;
} else {
self.children[1] self.children[1]
.inner .inner
.children() .children()
@ -1412,10 +1823,11 @@ impl GuiElemTrait for FilterPanel {
.inner .inner
.config_mut() .config_mut()
.enabled = true; .enabled = true;
if self.tab > 0 {
*self.children[0] *self.children[0]
.inner .inner
.children() .children()
.nth(self.tab) .nth(self.tab - 1)
.unwrap() .unwrap()
.try_as_mut::<Button>() .try_as_mut::<Button>()
.unwrap() .unwrap()
@ -1424,10 +1836,12 @@ impl GuiElemTrait for FilterPanel {
.unwrap() .unwrap()
.content .content
.color() = Color::GRAY; .color() = Color::GRAY;
}
if new_tab > 0 {
*self.children[0] *self.children[0]
.inner .inner
.children() .children()
.nth(new_tab) .nth(new_tab - 1)
.unwrap() .unwrap()
.try_as_mut::<Button>() .try_as_mut::<Button>()
.unwrap() .unwrap()
@ -1436,8 +1850,10 @@ impl GuiElemTrait for FilterPanel {
.unwrap() .unwrap()
.content .content
.color() = Color::WHITE; .color() = Color::WHITE;
}
self.tab = new_tab; self.tab = new_tab;
} }
}
// load tab // load tab
if load_tab { if load_tab {
match new_tab { match new_tab {
@ -1450,7 +1866,6 @@ impl GuiElemTrait for FilterPanel {
.try_as_mut::<ScrollBox>() .try_as_mut::<ScrollBox>()
.unwrap(); .unwrap();
let ssc = Rc::clone(&self.search_settings_changed); let ssc = Rc::clone(&self.search_settings_changed);
let my_tab = self.tab;
let ntab = Rc::clone(&self.new_tab); let ntab = Rc::clone(&self.new_tab);
sb.children = Self::build_filter( sb.children = Self::build_filter(
match new_tab { match new_tab {
@ -1462,7 +1877,7 @@ impl GuiElemTrait for FilterPanel {
info.line_height, info.line_height,
&Rc::new(move |update_ui| { &Rc::new(move |update_ui| {
if update_ui { if update_ui {
ntab.store(my_tab, std::sync::atomic::Ordering::Relaxed); ntab.store(usize::MAX, std::sync::atomic::Ordering::Relaxed);
} }
ssc.store(true, std::sync::atomic::Ordering::Relaxed); ssc.store(true, std::sync::atomic::Ordering::Relaxed);
}), }),

View File

@ -116,7 +116,13 @@ impl QueueViewer {
GuiElemCfg::at(Rectangle::from_tuples((0.5, 0.5), (1.0, 1.0))) GuiElemCfg::at(Rectangle::from_tuples((0.5, 0.5), (1.0, 1.0)))
.w_mouse(), .w_mouse(),
vec![], vec![],
QueueContent::Shuffle { inner: Box::new(QueueContent::Folder(0, vec![], String::new()).into()), state: ShuffleState::NotShuffled }.into(), QueueContent::Shuffle {
inner: Box::new(
QueueContent::Folder(0, vec![], String::new()).into(),
),
state: ShuffleState::NotShuffled,
}
.into(),
false, false,
) )
.alwayscopy(), .alwayscopy(),
@ -262,7 +268,12 @@ fn queue_gui(
} }
QueueContent::Random(q) => { QueueContent::Random(q) => {
target.push(( target.push((
GuiElem::new(QueueRandom::new(cfg.clone(), path.clone(), queue.clone(), current)), GuiElem::new(QueueRandom::new(
cfg.clone(),
path.clone(),
queue.clone(),
current,
)),
line_height, line_height,
)); ));
for (i, inner) in q.iter().enumerate() { for (i, inner) in q.iter().enumerate() {
@ -289,7 +300,12 @@ fn queue_gui(
} }
QueueContent::Shuffle { inner, state: _ } => { QueueContent::Shuffle { inner, state: _ } => {
target.push(( target.push((
GuiElem::new(QueueShuffle::new(cfg.clone(), path.clone(), queue.clone(), current)), GuiElem::new(QueueShuffle::new(
cfg.clone(),
path.clone(),
queue.clone(),
current,
)),
line_height * 0.8, line_height * 0.8,
)); ));
let mut p = path.clone(); let mut p = path.clone();
@ -348,7 +364,7 @@ impl GuiElemTrait for QueueEmptySpaceDragHandler {
Box::new(self.clone()) Box::new(self.clone())
} }
fn dragged(&mut self, dragged: Dragging) -> Vec<GuiAction> { fn dragged(&mut self, dragged: Dragging) -> Vec<GuiAction> {
dragged_add_to_queue(dragged, |q| Command::QueueAdd(vec![], q)) dragged_add_to_queue(dragged, |q, _| Command::QueueAdd(vec![], q))
} }
} }
@ -552,11 +568,11 @@ impl GuiElemTrait for QueueSong {
if !self.always_copy { if !self.always_copy {
let mut p = self.path.clone(); let mut p = self.path.clone();
let insert_below = self.insert_below; let insert_below = self.insert_below;
dragged_add_to_queue(dragged, move |q| { dragged_add_to_queue(dragged, move |q, i| {
if let Some(i) = p.pop() { if let Some(j) = p.pop() {
Command::QueueInsert(p, if insert_below { i + 1 } else { i }, q) Command::QueueInsert(p.clone(), if insert_below { j + 1 } else { j } + i, q)
} else { } else {
Command::QueueAdd(p, q) Command::QueueAdd(p.clone(), q)
} }
}) })
} else { } else {
@ -722,11 +738,13 @@ impl GuiElemTrait for QueueFolder {
if !self.always_copy { if !self.always_copy {
if self.insert_into { if self.insert_into {
let p = self.path.clone(); let p = self.path.clone();
dragged_add_to_queue(dragged, move |q| Command::QueueAdd(p, q)) dragged_add_to_queue(dragged, move |q, _| Command::QueueAdd(p.clone(), q))
} else { } else {
let mut p = self.path.clone(); let mut p = self.path.clone();
let i = p.pop(); let j = p.pop().unwrap_or(0);
dragged_add_to_queue(dragged, move |q| Command::QueueInsert(p, i.unwrap_or(0), q)) dragged_add_to_queue(dragged, move |q, i| {
Command::QueueInsert(p.clone(), j + i, q)
})
} }
} else { } else {
vec![] vec![]
@ -785,8 +803,10 @@ impl GuiElemTrait for QueueIndentEnd {
} }
} }
fn dragged(&mut self, dragged: Dragging) -> Vec<GuiAction> { fn dragged(&mut self, dragged: Dragging) -> Vec<GuiAction> {
let (p, i) = self.path_insert.clone(); let (p, j) = self.path_insert.clone();
dragged_add_to_queue(dragged, move |q| Command::QueueInsert(p, i, q)) dragged_add_to_queue(dragged, move |q, i| {
Command::QueueInsert(p.clone(), j + i, q)
})
} }
} }
@ -945,7 +965,7 @@ impl GuiElemTrait for QueueLoop {
if !self.always_copy { if !self.always_copy {
let mut p = self.path.clone(); let mut p = self.path.clone();
p.push(0); p.push(0);
dragged_add_to_queue(dragged, move |q| Command::QueueAdd(p, q)) dragged_add_to_queue(dragged, move |q, _| Command::QueueAdd(p.clone(), q))
} else { } else {
vec![] vec![]
} }
@ -1077,7 +1097,7 @@ impl GuiElemTrait for QueueRandom {
fn dragged(&mut self, dragged: Dragging) -> Vec<GuiAction> { fn dragged(&mut self, dragged: Dragging) -> Vec<GuiAction> {
if !self.always_copy { if !self.always_copy {
let p = self.path.clone(); let p = self.path.clone();
dragged_add_to_queue(dragged, move |q| Command::QueueAdd(p, q)) dragged_add_to_queue(dragged, move |q, _| Command::QueueAdd(p.clone(), q))
} else { } else {
vec![] vec![]
} }
@ -1210,22 +1230,22 @@ impl GuiElemTrait for QueueShuffle {
if !self.always_copy { if !self.always_copy {
let mut p = self.path.clone(); let mut p = self.path.clone();
p.push(0); p.push(0);
dragged_add_to_queue(dragged, move |q| Command::QueueAdd(p, q)) dragged_add_to_queue(dragged, move |q, _| Command::QueueAdd(p.clone(), q))
} else { } else {
vec![] vec![]
} }
} }
} }
fn dragged_add_to_queue<F: FnOnce(Queue) -> Command + 'static>( fn dragged_add_to_queue<F: FnMut(Queue, usize) -> Command + 'static>(
dragged: Dragging, dragged: Dragging,
f: F, mut f: F,
) -> Vec<GuiAction> { ) -> Vec<GuiAction> {
match dragged { match dragged {
Dragging::Artist(id) => { Dragging::Artist(id) => {
vec![GuiAction::Build(Box::new(move |db| { vec![GuiAction::Build(Box::new(move |db| {
if let Some(q) = add_to_queue_artist_by_id(id, db) { if let Some(q) = add_to_queue_artist_by_id(id, db) {
vec![GuiAction::SendToServer(f(q))] vec![GuiAction::SendToServer(f(q, 0))]
} else { } else {
vec![] vec![]
} }
@ -1234,7 +1254,7 @@ fn dragged_add_to_queue<F: FnOnce(Queue) -> Command + 'static>(
Dragging::Album(id) => { Dragging::Album(id) => {
vec![GuiAction::Build(Box::new(move |db| { vec![GuiAction::Build(Box::new(move |db| {
if let Some(q) = add_to_queue_album_by_id(id, db) { if let Some(q) = add_to_queue_album_by_id(id, db) {
vec![GuiAction::SendToServer(f(q))] vec![GuiAction::SendToServer(f(q, 0))]
} else { } else {
vec![] vec![]
} }
@ -1242,11 +1262,16 @@ fn dragged_add_to_queue<F: FnOnce(Queue) -> Command + 'static>(
} }
Dragging::Song(id) => { Dragging::Song(id) => {
let q = QueueContent::Song(id).into(); let q = QueueContent::Song(id).into();
vec![GuiAction::SendToServer(f(q))] vec![GuiAction::SendToServer(f(q, 0))]
} }
Dragging::Queue(q) => { Dragging::Queue(q) => {
vec![GuiAction::SendToServer(f(q))] vec![GuiAction::SendToServer(f(q, 0))]
} }
Dragging::Queues(q) => q
.into_iter()
.enumerate()
.map(|(i, q)| GuiAction::SendToServer(f(q, i)))
.collect(),
} }
} }

View File

@ -37,6 +37,11 @@ pub struct GuiScreen {
/// 0: StatusBar / Idle display /// 0: StatusBar / Idle display
/// 1: Settings /// 1: Settings
/// 2: Panel for Main view /// 2: Panel for Main view
/// 0: settings button
/// 1: exit button
/// 2: library browser
/// 3: queue
/// 4: queue clear button
/// 3: Edit Panel /// 3: Edit Panel
children: Vec<GuiElem>, children: Vec<GuiElem>,
pub idle: (bool, Option<Instant>), pub idle: (bool, Option<Instant>),