feat: add right click to songs in queue

This commit is contained in:
Mark
2026-08-01 13:09:43 +02:00
parent 8ceecf6bb6
commit e3293fffe6
17 changed files with 625 additions and 613 deletions

View File

@@ -658,11 +658,9 @@ pub(crate) trait GuiElemInternal: GuiElem {
// adjust info // adjust info
let npos = adjust_area(&info.pos, &self.config_mut().pos); let npos = adjust_area(&info.pos, &self.config_mut().pos);
let ppos = std::mem::replace(&mut info.pos, npos); let ppos = std::mem::replace(&mut info.pos, npos);
if info.child_has_keyboard_focus { if info.child_has_keyboard_focus && self.config().keyboard_focus_index == usize::MAX {
if self.config().keyboard_focus_index == usize::MAX { info.has_keyboard_focus = true;
info.has_keyboard_focus = true; info.child_has_keyboard_focus = false;
info.child_has_keyboard_focus = false;
}
} }
info.mouse_pos_in_bounds = info.pos.contains(info.mouse_pos); info.mouse_pos_in_bounds = info.pos.contains(info.mouse_pos);
if !info.mouse_pos_in_bounds { if !info.mouse_pos_in_bounds {
@@ -716,12 +714,11 @@ pub(crate) trait GuiElemInternal: GuiElem {
) -> Option<Vec<GuiAction>> { ) -> Option<Vec<GuiAction>> {
if self.config().enabled || allow_deactivated { if self.config().enabled || allow_deactivated {
for c in &mut self.children() { for c in &mut self.children() {
if c.config().enabled { if c.config().enabled
if c.config().pixel_pos.contains(pos) { && c.config().pixel_pos.contains(pos)
if let Some(v) = c._mouse_event(e, allow_deactivated, condition, pos) { && let Some(v) = c._mouse_event(e, allow_deactivated, condition, pos)
return Some(v); {
} return Some(v);
}
} }
} }
condition(self.elem_mut(), e) condition(self.elem_mut(), e)
@@ -739,10 +736,10 @@ pub(crate) trait GuiElemInternal: GuiElem {
e, e,
false, false,
&mut |v, e| { &mut |v, e| {
if v.config().drag_target { if v.config().drag_target
if let Some(d) = dragged.take() { && let Some(d) = dragged.take()
return Some(v.dragged(e, d)); {
} return Some(v.dragged(e, d));
} }
None None
}, },
@@ -887,7 +884,7 @@ pub(crate) trait GuiElemInternal: GuiElem {
} }
} }
fn _keyboard_move_focus(&mut self, decrement: bool, refocus: bool) -> bool { fn _keyboard_move_focus(&mut self, decrement: bool, refocus: bool) -> bool {
if self.config().enabled == false { if !self.config().enabled {
return false; return false;
} }
let mut focus_index = if refocus { let mut focus_index = if refocus {
@@ -1133,7 +1130,8 @@ pub struct GuiElemCfg {
pub enabled: bool, pub enabled: bool,
/// if true, indicates that something (text size, screen size, ...) has changed /// if true, indicates that something (text size, screen size, ...) has changed
/// and you should probably relayout and redraw from scratch. /// and you should probably relayout and redraw from scratch.
pub redraw: bool, redraw: bool,
redraw2: bool,
/// will be set to false after `draw`. /// will be set to false after `draw`.
/// can be used to, for example, add the keybinds for your element. /// can be used to, for example, add the keybinds for your element.
pub init: bool, pub init: bool,
@@ -1193,9 +1191,27 @@ impl GuiElemCfg {
self.drag_target = true; self.drag_target = true;
self self
} }
pub fn force_redraw(mut self) -> Self { pub fn redraw(&self) -> bool {
self.redraw
}
/// sets `redraw`, causing this element to redraw itself on the next frame.
pub fn redraw_once(&mut self) {
self.redraw = true; self.redraw = true;
self }
/// same as calling `redraw()` and then
/// calling `redraw()` again on the next frame.
/// in other words, the next 2 frames will see a redraw of this item.
pub fn redraw_twice(&mut self) {
self.redraw = true;
self.redraw2 = true;
}
/// sets `redraw` back to `false` (usually)
pub fn redrawn(&mut self) {
if self.redraw2 {
self.redraw2 = false;
} else {
self.redraw = false;
}
} }
pub fn disabled(mut self) -> Self { pub fn disabled(mut self) -> Self {
self.enabled = false; self.enabled = false;
@@ -1207,6 +1223,7 @@ impl Default for GuiElemCfg {
Self { Self {
enabled: true, enabled: true,
redraw: false, redraw: false,
redraw2: false,
init: true, init: true,
pos: Rectangle::new(Vec2::ZERO, Vec2::new(1.0, 1.0)), pos: Rectangle::new(Vec2::ZERO, Vec2::new(1.0, 1.0)),
pixel_pos: Rectangle::ZERO, pixel_pos: Rectangle::ZERO,
@@ -1270,6 +1287,7 @@ pub enum Dragging {
Queue(Result<Queue, Vec<usize>>), Queue(Result<Queue, Vec<usize>>),
Queues(Vec<Queue>), Queues(Vec<Queue>),
} }
#[allow(clippy::enum_variant_names)]
pub enum SpecificGuiElem { pub enum SpecificGuiElem {
SearchArtist, SearchArtist,
SearchAlbum, SearchAlbum,
@@ -1322,7 +1340,7 @@ impl Gui {
pub 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());
for action in actions { for action in actions {
self.exec_gui_action(action); self.exec_gui_action(action);
} }
@@ -1478,7 +1496,7 @@ impl WindowHandler<GuiEvent> for Gui {
time: draw_start_time, time: draw_start_time,
actions: Vec::with_capacity(0), actions: Vec::with_capacity(0),
pos: Rectangle::new(Vec2::ZERO, self.size.into_f32()), pos: Rectangle::new(Vec2::ZERO, self.size.into_f32()),
database: &mut *dblock, database: &mut dblock,
font: &self.font, font: &self.font,
mouse_pos: self.mouse_pos, mouse_pos: self.mouse_pos,
mouse_pos_in_bounds: false, mouse_pos_in_bounds: false,
@@ -1572,9 +1590,9 @@ impl WindowHandler<GuiEvent> for Gui {
} }
} }
fn on_mouse_button_down(&mut self, helper: &mut WindowHelper<GuiEvent>, button: MouseButton) { fn on_mouse_button_down(&mut self, helper: &mut WindowHelper<GuiEvent>, button: MouseButton) {
if let Some(a) = if let Some(a) = self
self.gui .gui
._mouse_button(&mut EventInfo::new(), button, true, self.mouse_pos.clone()) ._mouse_button(&mut EventInfo::new(), button, true, self.mouse_pos)
{ {
for a in a { for a in a {
self.exec_gui_action(a) self.exec_gui_action(a)
@@ -1586,9 +1604,9 @@ impl WindowHandler<GuiEvent> for Gui {
if self.dragging.is_some() { if self.dragging.is_some() {
let (dr, _) = self.dragging.take().unwrap(); let (dr, _) = self.dragging.take().unwrap();
let mut opt = Some(dr); let mut opt = Some(dr);
if let Some(a) = if let Some(a) = self
self.gui .gui
._release_drag(&mut EventInfo::new(), &mut opt, self.mouse_pos.clone()) ._release_drag(&mut EventInfo::new(), &mut opt, self.mouse_pos)
{ {
for a in a { for a in a {
self.exec_gui_action(a) self.exec_gui_action(a)
@@ -1609,7 +1627,7 @@ impl WindowHandler<GuiEvent> for Gui {
} }
if let Some(a) = if let Some(a) =
self.gui self.gui
._mouse_button(&mut EventInfo::new(), button, false, self.mouse_pos.clone()) ._mouse_button(&mut EventInfo::new(), button, false, self.mouse_pos)
{ {
for a in a { for a in a {
self.exec_gui_action(a) self.exec_gui_action(a)
@@ -1639,7 +1657,7 @@ impl WindowHandler<GuiEvent> for Gui {
}; };
if let Some(a) = self if let Some(a) = self
.gui .gui
._mouse_wheel(&mut EventInfo::new(), dist, self.mouse_pos.clone()) ._mouse_wheel(&mut EventInfo::new(), dist, self.mouse_pos)
{ {
for a in a { for a in a {
self.exec_gui_action(a) self.exec_gui_action(a)
@@ -1673,10 +1691,10 @@ impl WindowHandler<GuiEvent> for Gui {
scancode: KeyScancode, scancode: KeyScancode,
) { ) {
helper.request_redraw(); helper.request_redraw();
if let Some(VirtualKeyCode::Tab) = virtual_key_code { if let Some(VirtualKeyCode::Tab) = virtual_key_code
if !(self.modifiers.ctrl() || self.modifiers.alt() || self.modifiers.logo()) { && !(self.modifiers.ctrl() || self.modifiers.alt() || self.modifiers.logo())
self.gui._keyboard_move_focus(self.modifiers.shift(), false); {
} self.gui._keyboard_move_focus(self.modifiers.shift(), false);
} }
for a in self.gui._keyboard_event( for a in self.gui._keyboard_event(
&mut EventInfo::new(), &mut EventInfo::new(),
@@ -1717,18 +1735,19 @@ impl WindowHandler<GuiEvent> for Gui {
// handle keybinds unless settings are open, opening or closing // handle keybinds unless settings are open, opening or closing
let mut e = EventInfo::new(); let mut e = EventInfo::new();
let mut post_action = None; let mut post_action = None;
if self.gui.settings.0 == false && self.gui.settings.1.is_none() { if !self.gui.settings.0
if let Some(key) = virtual_key_code { && self.gui.settings.1.is_none()
let keybind = KeyBinding::new(&self.modifiers, key); && let Some(key) = virtual_key_code
if let Some(action) = self.keybinds.get(&keybind) { {
if action.has_priority() { let keybind = KeyBinding::new(&self.modifiers, key);
e.take(); if let Some(action) = self.keybinds.get(&keybind) {
for a in self.key_actions.get(&action.id()).execute() { if action.has_priority() {
self.exec_gui_action(a); e.take();
} for a in self.key_actions.get(&action.id()).execute() {
} else { self.exec_gui_action(a);
post_action = Some(action.id());
} }
} else {
post_action = Some(action.id());
} }
} }
} }
@@ -1760,11 +1779,11 @@ impl WindowHandler<GuiEvent> for Gui {
) { ) {
self.exec_gui_action(a); self.exec_gui_action(a);
} }
if let Some(post_action) = post_action.take() { if let Some(post_action) = post_action.take()
if e.take() { && e.take()
for a in self.key_actions.get(&post_action).execute() { {
self.exec_gui_action(a); for a in self.key_actions.get(&post_action).execute() {
} self.exec_gui_action(a);
} }
} }
} }

View File

@@ -73,7 +73,7 @@ pub struct Square<T: GuiElem> {
#[allow(unused)] #[allow(unused)]
impl<T: GuiElem> Square<T> { impl<T: GuiElem> Square<T> {
pub fn new(mut config: GuiElemCfg, inner: T) -> Self { pub fn new(mut config: GuiElemCfg, inner: T) -> Self {
config.redraw = true; config.redraw_once();
Self { config, inner } Self { config, inner }
} }
} }
@@ -101,10 +101,10 @@ impl<T: GuiElem + 'static> GuiElem for Square<T> {
} }
fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) { fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) {
if info.pos.size() != self.config.pixel_pos.size() { if info.pos.size() != self.config.pixel_pos.size() {
self.config.redraw = true; self.config.redraw_once();
} }
if self.config.redraw { if self.config.redraw() {
self.config.redraw = false; self.config.redrawn();
if info.pos.width() > info.pos.height() { if info.pos.width() > info.pos.height() {
let w = 0.5 * info.pos.height() / info.pos.width(); let w = 0.5 * info.pos.height() / info.pos.width();
self.inner.config_mut().pos = self.inner.config_mut().pos =
@@ -193,7 +193,7 @@ impl<C: GuiElemChildren + 'static> GuiElem for ScrollBox<C> {
} }
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.pixel_pos.size() != info.pos.size() { if self.config.pixel_pos.size() != info.pos.size() {
self.config.redraw = true; self.config.redraw_once();
} }
// smooth scrolling animation // smooth scrolling animation
if self.scroll_target > self.max_scroll { if self.scroll_target > self.max_scroll {
@@ -202,7 +202,7 @@ impl<C: GuiElemChildren + 'static> GuiElem for ScrollBox<C> {
self.scroll_target = 0.0; self.scroll_target = 0.0;
} }
if self.scroll_target != self.scroll_display { if self.scroll_target != self.scroll_display {
self.config.redraw = true; self.config.redraw_once();
if info.high_performance { if info.high_performance {
self.scroll_display = self.scroll_target; self.scroll_display = self.scroll_target;
} else { } else {
@@ -215,7 +215,7 @@ impl<C: GuiElemChildren + 'static> GuiElem for ScrollBox<C> {
} }
} }
// recalculate positions // recalculate positions
if self.config.redraw { if self.config.redraw() {
// adjust height vector length if necessary // adjust height vector length if necessary
if self.children_heights.len() != self.children.len() { if self.children_heights.len() != self.children.len() {
let target = self.children.len(); let target = self.children.len();
@@ -229,7 +229,7 @@ impl<C: GuiElemChildren + 'static> GuiElem for ScrollBox<C> {
// //
self.mouse_scroll_margin_right = info.line_height * 0.2; self.mouse_scroll_margin_right = info.line_height * 0.2;
let max_x = 1.0 - self.mouse_scroll_margin_right / info.pos.width(); let max_x = 1.0 - self.mouse_scroll_margin_right / info.pos.width();
self.config.redraw = false; self.config.redrawn();
let mut y_pos = -self.scroll_display; let mut y_pos = -self.scroll_display;
for (e, h) in self.children.iter().zip(self.children_heights.iter()) { for (e, h) in self.children.iter().zip(self.children_heights.iter()) {
let h_rel = self.size_unit.to_rel(*h, info.pos.height()); let h_rel = self.size_unit.to_rel(*h, info.pos.height());
@@ -292,8 +292,8 @@ impl<C: GuiElemChildren + 'static> GuiElem for ScrollBox<C> {
} }
} }
fn mouse_wheel(&mut self, e: &mut EventInfo, diff: f32) -> Vec<crate::gui::GuiAction> { fn mouse_wheel(&mut self, e: &mut EventInfo, diff: f32) -> Vec<crate::gui::GuiAction> {
let nst = (self.scroll_target - self.size_unit.from_abs(diff as f32, self.last_height_px)) let nst =
.max(0.0); (self.scroll_target - self.size_unit.from_abs(diff, self.last_height_px)).max(0.0);
// only take the event if this would actually scroll, and only scroll if we can actually take the event // only take the event if this would actually scroll, and only scroll if we can actually take the event
if nst != self.scroll_target && e.take() { if nst != self.scroll_target && e.take() {
self.scroll_target = nst; self.scroll_target = nst;
@@ -323,12 +323,14 @@ impl ScrollBoxSizeUnit {
Self::Pixels => val / draw_height, Self::Pixels => val / draw_height,
} }
} }
#[allow(clippy::wrong_self_convention)]
fn from_rel(&self, val: f32, draw_height: f32) -> f32 { fn from_rel(&self, val: f32, draw_height: f32) -> f32 {
match self { match self {
Self::Relative => val, Self::Relative => val,
Self::Pixels => val * draw_height, Self::Pixels => val * draw_height,
} }
} }
#[allow(clippy::wrong_self_convention)]
fn from_abs(&self, val: f32, draw_height: f32) -> f32 { fn from_abs(&self, val: f32, draw_height: f32) -> f32 {
match self { match self {
Self::Relative => val / draw_height, Self::Relative => val / draw_height,
@@ -545,21 +547,13 @@ impl Slider {
{ {
if since >= 1.0 { if since >= 1.0 {
s.display_since = None; s.display_since = None;
if s.display { if s.display { 1.0 } else { 0.0 }
1.0
} else {
0.0
}
} else { } else {
if let Some(h) = &i.helper { if let Some(h) = &i.helper {
h.request_redraw(); h.request_redraw();
} }
s.config.redraw = true; s.config.redraw_once();
if s.display { if s.display { since } else { 1.0 - since }
since
} else {
1.0 - since
}
} }
} else { } else {
1.0 1.0
@@ -613,7 +607,7 @@ impl GuiElem for Slider {
if self.display != (self.config.mouse_down.0 || info.pos.contains(info.mouse_pos)) { if self.display != (self.config.mouse_down.0 || info.pos.contains(info.mouse_pos)) {
self.display = !self.display; self.display = !self.display;
self.display_since = Some(info.time); self.display_since = Some(info.time);
self.config.redraw = true; self.config.redraw_once();
} }
let dot_size = (info.pos.height() * 0.9).min(info.pos.width() * 0.25); let dot_size = (info.pos.height() * 0.9).min(info.pos.width() * 0.25);
let y_mid_line = 0.5 * (info.pos.top_left().y + info.pos.bottom_right().y); let y_mid_line = 0.5 * (info.pos.top_left().y + info.pos.bottom_right().y);
@@ -634,10 +628,8 @@ impl GuiElem for Slider {
(info.mouse_pos.x - line_pos.top_left().x) as f64 / line_pos.width() as f64, (info.mouse_pos.x - line_pos.top_left().x) as f64 / line_pos.width() as f64,
)); ));
self.val_changed = true; self.val_changed = true;
for v in &mut self.val_changed_subs { self.val_changed_subs.fill(true);
*v = true; self.config.redraw_once();
}
self.config.redraw = true;
} }
let line_color = Color::from_int_rgb(50, 50, 100); let line_color = Color::from_int_rgb(50, 50, 100);
g.draw_circle( g.draw_circle(
@@ -660,8 +652,8 @@ impl GuiElem for Slider {
0.5 * dot_size, 0.5 * dot_size,
Color::CYAN, Color::CYAN,
); );
if self.config.redraw { if self.config.redraw() {
self.config.redraw = false; self.config.redrawn();
(Arc::clone(&self.on_update))(self, info); (Arc::clone(&self.on_update))(self, info);
} }
} }

View File

@@ -1,16 +1,16 @@
use std::{collections::BTreeSet, time::Instant}; use std::{collections::BTreeSet, time::Instant};
use speedy2d::{ use speedy2d::{
Graphics2D,
color::Color, color::Color,
dimen::{Vec2, Vector2}, dimen::{Vec2, Vector2},
shape::Rectangle, shape::Rectangle,
Graphics2D,
}; };
use crate::{ use crate::{
gui::{DrawInfo, GuiElem, GuiElemCfg}, gui::{DrawInfo, GuiElem, GuiElemCfg},
gui_anim::AnimationController, gui_anim::AnimationController,
gui_base::{Button, ScrollBox}, gui_base::{Button, Panel, ScrollBox},
gui_text::{Label, TextField}, gui_text::{Label, TextField},
}; };
@@ -24,36 +24,62 @@ pub enum Event {
pub struct EditorForAnyTagInList { pub struct EditorForAnyTagInList {
config: GuiElemCfg, config: GuiElemCfg,
pub tag: String, pub tag: String,
label: Label, panel: Panel<(Label, Button<[IconDelete; 1]>)>,
rm_button: Button<[IconDelete; 1]>,
} }
impl EditorForAnyTagInList { impl EditorForAnyTagInList {
pub fn new<T: From<Event> + 'static>( pub fn new<T: From<Event> + 'static>(
tag: String, tag: String,
index: usize,
sender: std::sync::mpsc::Sender<T>, sender: std::sync::mpsc::Sender<T>,
config: GuiElemCfg, config: GuiElemCfg,
) -> Self { ) -> Self {
Self { let label = Label::new(
config, GuiElemCfg::default(),
tag: tag.clone(), tag.clone(),
label: Label::new( Color::WHITE,
GuiElemCfg::default(), None,
tag.clone(), Vector2::new(0.0, 0.5),
Color::WHITE, );
None, let rm_button = Button::new(
Vector2::new(0.0, 0.5), GuiElemCfg::default(),
), {
rm_button: Button::new( let tag = tag.clone();
GuiElemCfg::default(),
move |btn| { move |btn| {
btn.disable(); btn.disable();
sender.send(Event::RemoveTag(tag.clone()).into()).unwrap(); sender.send(Event::RemoveTag(tag.clone()).into()).unwrap();
vec![] vec![]
}, }
[IconDelete::new(GuiElemCfg::default())], },
), [IconDelete::new(GuiElemCfg::default())],
} );
let panel = Panel::with_background(
GuiElemCfg::default(),
(label, rm_button),
match index % 2 {
1 => Color::from_rgba(0.0, 1.0, 0.0, 0.1),
_ => Color::from_rgba(0.0, 0.0, 1.0, 0.14),
},
);
Self { config, tag, panel }
}
fn row(&self) -> &(Label, Button<[IconDelete; 1]>) {
&self.panel.children
}
fn row_mut(&mut self) -> &mut (Label, Button<[IconDelete; 1]>) {
&mut self.panel.children
}
fn label(&self) -> &Label {
&self.panel.children.0
}
fn rm_button(&self) -> &Button<[IconDelete; 1]> {
&self.panel.children.1
}
fn label_mut(&mut self) -> &mut Label {
&mut self.panel.children.0
}
fn rm_button_mut(&mut self) -> &mut Button<[IconDelete; 1]> {
&mut self.panel.children.1
} }
} }
@@ -63,11 +89,11 @@ impl GuiElem for EditorForAnyTagInList {
let rm_button_padding = (info.pos.height() - rm_button_size) / 2.0; let rm_button_padding = (info.pos.height() - rm_button_size) / 2.0;
let label_padding = info.pos.height() * 0.05; let label_padding = info.pos.height() * 0.05;
let x_split = (info.pos.width() - rm_button_size) / info.pos.width(); let x_split = (info.pos.width() - rm_button_size) / info.pos.width();
self.rm_button.config_mut().pos = Rectangle::from_tuples( self.rm_button_mut().config_mut().pos = Rectangle::from_tuples(
(x_split, rm_button_padding / info.pos.height()), (x_split, rm_button_padding / info.pos.height()),
(1.0, 1.0 - rm_button_padding / info.pos.height()), (1.0, 1.0 - rm_button_padding / info.pos.height()),
); );
self.label.config_mut().pos = Rectangle::from_tuples( self.label_mut().config_mut().pos = Rectangle::from_tuples(
(0.0, label_padding / info.pos.height()), (0.0, label_padding / info.pos.height()),
(x_split, 1.0 - label_padding / info.pos.height()), (x_split, 1.0 - label_padding / info.pos.height()),
); );
@@ -79,7 +105,7 @@ impl GuiElem for EditorForAnyTagInList {
&mut self.config &mut self.config
} }
fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> { fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
Box::new([self.label.elem_mut(), self.rm_button.elem_mut()].into_iter()) Box::new([self.panel.elem_mut()].into_iter())
} }
fn any(&self) -> &dyn std::any::Any { fn any(&self) -> &dyn std::any::Any {
self self
@@ -162,7 +188,7 @@ impl<T: From<Event> + 'static> EditorForAnyTagAdder<T> {
expand_to, expand_to,
c_value: TextField::new( c_value: TextField::new(
GuiElemCfg::default(), GuiElemCfg::default(),
"artist".to_owned(), "tag".to_owned(),
Color::DARK_GRAY, Color::DARK_GRAY,
Color::WHITE, Color::WHITE,
), ),
@@ -180,7 +206,7 @@ impl<T: From<Event> + 'static> EditorForAnyTagAdder<T> {
self.last_search = "\n".to_owned(); self.last_search = "\n".to_owned();
self.c_value.c_input.content.text().clear(); self.c_value.c_input.content.text().clear();
self.open_prog.set_target(now, 1.0); self.open_prog.set_target(now, 1.0);
self.config_mut().redraw = true; self.config_mut().redraw_once();
} }
} }
impl<T: From<Event> + 'static> GuiElem for EditorForAnyTagAdder<T> { impl<T: From<Event> + 'static> GuiElem for EditorForAnyTagAdder<T> {
@@ -196,8 +222,8 @@ impl<T: From<Event> + 'static> GuiElem for EditorForAnyTagAdder<T> {
} }
let search = self.c_value.c_input.content.get_text().to_lowercase(); let search = self.c_value.c_input.content.get_text().to_lowercase();
let search_changed = &self.last_search != &search; let search_changed = self.last_search != search;
if self.config.redraw || search_changed { if self.config.redraw() || search_changed {
*self.c_value.c_input.content.color() = Color::WHITE; *self.c_value.c_input.content.color() = Color::WHITE;
if search_changed { if search_changed {
if search.is_empty() { if search.is_empty() {
@@ -224,7 +250,7 @@ impl<T: From<Event> + 'static> GuiElem for EditorForAnyTagAdder<T> {
.flat_map(|s| s.general.tags.iter()), .flat_map(|s| s.general.tags.iter()),
) )
.filter(|tag| tag.to_lowercase().contains(&search)) .filter(|tag| tag.to_lowercase().contains(&search))
.map(|tag| tag.clone()) .cloned()
.collect::<BTreeSet<_>>(); .collect::<BTreeSet<_>>();
if !tags.contains(self.c_value.c_input.content.get_text()) { if !tags.contains(self.c_value.c_input.content.get_text()) {
tags.insert(self.c_value.c_input.content.get_text().clone()); tags.insert(self.c_value.c_input.content.get_text().clone());
@@ -252,8 +278,9 @@ impl<T: From<Event> + 'static> GuiElem for EditorForAnyTagAdder<T> {
) )
}) })
.collect(); .collect();
self.c_picker.config_mut().redraw = true; self.c_picker.config_mut().redraw_once();
self.last_search = search; self.last_search = search;
self.config.redrawn();
} }
} }
fn config(&self) -> &GuiElemCfg { fn config(&self) -> &GuiElemCfg {

View File

@@ -1,7 +1,7 @@
use std::time::Instant; use std::time::Instant;
use musicdb_lib::{ use musicdb_lib::{
data::{song::Song, ArtistId}, data::{ArtistId, song::Song},
server::{Action, Req}, server::{Action, Req},
}; };
use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle}; use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle};
@@ -11,7 +11,7 @@ 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_edit_any::{ELEM_HEIGHT, EditorForAnyTagAdder, EditorForAnyTagInList, SpacerForScrollBox},
gui_text::{Label, TextField}, gui_text::{Label, TextField},
}; };
@@ -28,6 +28,7 @@ pub struct EditorForSongs {
event_sender: std::sync::mpsc::Sender<Event>, event_sender: std::sync::mpsc::Sender<Event>,
event_recv: std::sync::mpsc::Receiver<Event>, event_recv: std::sync::mpsc::Receiver<Event>,
} }
#[allow(clippy::enum_variant_names)]
pub enum Event { pub enum Event {
Close, Close,
Apply, Apply,
@@ -102,7 +103,7 @@ impl EditorForSongs {
c_artist: EditorForSongArtistChooser::new(sender.clone()), c_artist: EditorForSongArtistChooser::new(sender.clone()),
c_album: Label::new( c_album: Label::new(
GuiElemCfg::default(), GuiElemCfg::default(),
format!("(todo...)"), "(todo...)".to_owned(),
Color::GRAY, Color::GRAY,
None, None,
Vec2::new(0.0, 0.5), Vec2::new(0.0, 0.5),
@@ -117,9 +118,11 @@ impl EditorForSongs {
} }
} }
tags.into_iter() tags.into_iter()
.map(|tag| { .enumerate()
.map(|(i, tag)| {
EditorForAnyTagInList::new( EditorForAnyTagInList::new(
tag.to_owned(), tag.to_owned(),
i,
sender.clone(), sender.clone(),
GuiElemCfg::default(), GuiElemCfg::default(),
) )
@@ -198,110 +201,112 @@ impl GuiElem for EditorForSongs {
) )
} }
fn draw(&mut self, info: &mut crate::gui::DrawInfo, g: &mut speedy2d::Graphics2D) { fn draw(&mut self, info: &mut crate::gui::DrawInfo, g: &mut speedy2d::Graphics2D) {
loop { while let Ok(e) = self.event_recv.try_recv() {
match self.event_recv.try_recv() { match e {
Ok(e) => match e { Event::Close => info.actions.push(GuiAction::Do(Box::new(|gui| {
Event::Close => info.actions.push(GuiAction::Do(Box::new(|gui| { gui.gui.c_editing_songs = None;
gui.gui.c_editing_songs = None; gui.gui.set_normal_ui_enabled(true);
gui.gui.set_normal_ui_enabled(true); }))),
}))), Event::Apply => {
Event::Apply => { let mut actions = Vec::new();
let mut actions = Vec::new(); for song in self.songs.iter() {
for song in self.songs.iter() { let mut song = song.clone();
let mut song = song.clone();
let new_title = self let new_title = self
.c_scrollbox
.children
.c_title
.c_input
.content
.get_text()
.trim();
if !new_title.is_empty() {
song.title = new_title.to_owned();
}
if let Some(artist_id) = self.c_scrollbox.children.c_artist.chosen_id {
song.artist = artist_id;
song.album = None;
}
actions.push(Action::ModifySong(song, Req::none()));
}
if actions.len() == 1 {
info.actions
.push(GuiAction::SendToServer(actions.pop().unwrap()));
} else if actions.len() > 1 {
info.actions
.push(GuiAction::SendToServer(Action::Multiple(actions)));
}
}
Event::SetArtist(name, id) => {
self.c_scrollbox.children.c_artist.chosen_id = id;
self.c_scrollbox.children.c_artist.last_search = name.to_lowercase();
self.c_scrollbox
.children
.c_artist
.open_prog
.set_target(info.time, 1.0);
*self
.c_scrollbox .c_scrollbox
.children .children
.c_artist .c_title
.c_name
.c_input .c_input
.content .content
.text() = name; .get_text()
self.c_scrollbox.children.c_artist.config_mut().redraw = true; .trim();
if !new_title.is_empty() {
song.title = new_title.to_owned();
}
if let Some(artist_id) = self.c_scrollbox.children.c_artist.chosen_id {
song.artist = artist_id;
song.album = None;
}
actions.push(Action::ModifySong(song, Req::none()));
} }
Event::GeneralEvent(e) => { if actions.len() == 1 {
use super::gui_edit_any::Event as GeneralEvent; info.actions
match e { .push(GuiAction::SendToServer(actions.pop().unwrap()));
GeneralEvent::RemoveTag(tag) => { } else if actions.len() > 1 {
for song in self.songs.iter_mut() { info.actions
if let Some(i) = .push(GuiAction::SendToServer(Action::Multiple(actions)));
song.general.tags.iter().position(|t| *t == tag) }
{ }
song.general.tags.remove(i); Event::SetArtist(name, id) => {
} self.c_scrollbox.children.c_artist.chosen_id = id;
} self.c_scrollbox.children.c_artist.last_search = name.to_lowercase();
if let Some(i) = (&self.c_scrollbox.children.c_tags) self.c_scrollbox
.into_iter() .children
.position(|e| e.tag == tag) .c_artist
{ .open_prog
self.c_scrollbox.children.c_tags.remove(i); .set_target(info.time, 1.0);
self.c_scrollbox.config_mut().redraw = true; *self
.c_scrollbox
.children
.c_artist
.c_name
.c_input
.content
.text() = name;
self.c_scrollbox
.children
.c_artist
.config_mut()
.redraw_once();
}
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);
} }
} }
GeneralEvent::AddTag(tag) => { if let Some(i) = (&self.c_scrollbox.children.c_tags)
self.c_scrollbox.children.c_new_tag.clear(info.time); .into_iter()
for song in self.songs.iter_mut() { .position(|e| e.tag == tag)
if !song.general.tags.contains(&tag) { {
song.general.tags.push(tag.clone()); self.c_scrollbox.children.c_tags.remove(i);
} self.c_scrollbox.config_mut().redraw_once();
} }
if !(&self.c_scrollbox.children.c_tags) }
.into_iter() GeneralEvent::AddTag(tag) => {
.any(|e| e.tag == tag) self.c_scrollbox.children.c_new_tag.clear(info.time);
{ for song in self.songs.iter_mut() {
self.c_scrollbox.children_heights.insert( if !song.general.tags.contains(&tag) {
3 + self.c_scrollbox.children.c_tags.len(), song.general.tags.push(tag.clone());
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;
} }
} }
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,
);
let i = self.c_scrollbox.children.c_tags.len();
self.c_scrollbox
.children
.c_tags
.push(EditorForAnyTagInList::new(
tag,
i,
self.event_sender.clone(),
GuiElemCfg::default(),
));
self.c_scrollbox.config_mut().redraw_once();
}
} }
} }
}, }
Err(_) => break,
} }
} }
// animation // animation
@@ -332,7 +337,7 @@ impl GuiElem for EditorForSongs {
{ {
if let Some(v) = self.c_scrollbox.children_heights.get_mut(1) { if let Some(v) = self.c_scrollbox.children_heights.get_mut(1) {
*v = ELEM_HEIGHT * val as f32; *v = ELEM_HEIGHT * val as f32;
self.c_scrollbox.config_mut().redraw = true; self.c_scrollbox.config_mut().redraw_once();
} }
if let Some(h) = &info.helper { if let Some(h) = &info.helper {
h.request_redraw(); h.request_redraw();
@@ -351,7 +356,7 @@ impl GuiElem for EditorForSongs {
.get_mut(3 + self.c_scrollbox.children.c_tags.len()) .get_mut(3 + self.c_scrollbox.children.c_tags.len())
{ {
*v = ELEM_HEIGHT * val as f32; *v = ELEM_HEIGHT * val as f32;
self.c_scrollbox.config_mut().redraw = true; self.c_scrollbox.config_mut().redraw_once();
} }
if let Some(h) = &info.helper { if let Some(h) = &info.helper {
h.request_redraw(); h.request_redraw();
@@ -428,8 +433,8 @@ impl GuiElem for EditorForSongArtistChooser {
} }
let search = self.c_name.c_input.content.get_text().to_lowercase(); let search = self.c_name.c_input.content.get_text().to_lowercase();
let search_changed = &self.last_search != &search; let search_changed = self.last_search != search;
if self.config.redraw || search_changed { if self.config.redraw() || search_changed {
*self.c_name.c_input.content.color() = if self.chosen_id.is_some() { *self.c_name.c_input.content.color() = if self.chosen_id.is_some() {
Color::GREEN Color::GREEN
} else { } else {
@@ -480,8 +485,9 @@ impl GuiElem for EditorForSongArtistChooser {
) )
}) })
.collect(); .collect();
self.c_picker.config_mut().redraw = true; self.c_picker.config_mut().redraw_once();
self.last_search = search; self.last_search = search;
self.config.redrawn();
} }
} }
fn config(&self) -> &GuiElemCfg { fn config(&self) -> &GuiElemCfg {

View File

@@ -126,27 +126,27 @@ impl GuiElem for IdleDisplay {
self.c_top_label.content = if let Some(song) = self.current_info.current_song { self.c_top_label.content = if let Some(song) = self.current_info.current_song {
info.gui_config info.gui_config
.idle_top_text .idle_top_text
.gen_new(&info.database, info.database.get_song(&song)) .gen_new(info.database, info.database.get_song(&song))
} else { } else {
vec![] vec![]
}; };
self.c_top_label.config_mut().redraw = true; self.c_top_label.config_mut().redraw_once();
self.c_side1_label.content = if let Some(song) = self.current_info.current_song { self.c_side1_label.content = if let Some(song) = self.current_info.current_song {
info.gui_config info.gui_config
.idle_side1_text .idle_side1_text
.gen_new(&info.database, info.database.get_song(&song)) .gen_new(info.database, info.database.get_song(&song))
} else { } else {
vec![] vec![]
}; };
self.c_side1_label.config_mut().redraw = true; self.c_side1_label.config_mut().redraw_once();
self.c_side2_label.content = if let Some(song) = self.current_info.current_song { self.c_side2_label.content = if let Some(song) = self.current_info.current_song {
info.gui_config info.gui_config
.idle_side2_text .idle_side2_text
.gen_new(&info.database, info.database.get_song(&song)) .gen_new(info.database, info.database.get_song(&song))
} else { } else {
vec![] vec![]
}; };
self.c_side2_label.config_mut().redraw = true; self.c_side2_label.config_mut().redraw_once();
// check artist // check artist
if let Some(artist_id) = self if let Some(artist_id) = self
.current_info .current_info
@@ -165,8 +165,8 @@ impl GuiElem for IdleDisplay {
self.artist_image_aspect_ratio.set_target(info.time, 0.0); self.artist_image_aspect_ratio.set_target(info.time, 0.0);
if let Some(artist) = info.database.artists().get(&artist_id) { if let Some(artist) = info.database.artists().get(&artist_id) {
for tag in &artist.general.tags { for tag in &artist.general.tags {
if tag.starts_with("ImageExt=") { if let Some(tag) = tag.strip_prefix("ImageExt=") {
let filename = format!("{}.{}", artist.name, &tag[9..]); let filename = format!("{}.{}", artist.name, tag);
self.current_artist_image = self.current_artist_image =
Some((artist_id, Some((filename.clone(), None)))); Some((artist_id, Some((filename.clone(), None))));
if !info.custom_images.contains_key(&filename) { if !info.custom_images.contains_key(&filename) {
@@ -199,15 +199,14 @@ impl GuiElem for IdleDisplay {
Some((_, None)) | Some((_, Some(Some(_)))) => {} Some((_, None)) | Some((_, Some(Some(_)))) => {}
} }
} }
if let Some((_, Some((img, h)))) = &mut self.current_artist_image { if let Some((_, Some((img, h)))) = &mut self.current_artist_image
if h.is_none() { && h.is_none()
if let Some(img) = info.custom_images.get_mut(img) { && let Some(img) = info.custom_images.get_mut(img)
if let Some(img) = img.get_init(g) { {
*h = Some(Some(img)); if let Some(img) = img.get_init(g) {
} else if img.is_err() { *h = Some(Some(img));
*h = Some(None); } else if img.is_err() {
} *h = Some(None);
}
} }
} }
// draw cover // draw cover

View File

@@ -3,18 +3,19 @@ use std::{
collections::HashSet, collections::HashSet,
sync::Arc, sync::Arc,
sync::{ sync::{
Mutex,
atomic::{AtomicBool, AtomicUsize}, atomic::{AtomicBool, AtomicUsize},
mpsc, Mutex, mpsc,
}, },
}; };
use musicdb_lib::data::{ use musicdb_lib::data::{
AlbumId, ArtistId, GeneralData, SongId,
album::Album, album::Album,
artist::Artist, artist::Artist,
database::Database, database::Database,
queue::{Queue, QueueContent}, queue::{Queue, QueueContent},
song::Song, song::Song,
AlbumId, ArtistId, GeneralData, SongId,
}; };
use regex::{Regex, RegexBuilder}; use regex::{Regex, RegexBuilder};
use speedy2d::{ use speedy2d::{
@@ -296,12 +297,8 @@ impl GuiElem for LibraryBrowser {
false false
} }
fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) { fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) {
loop { while let Ok(action) = self.do_something_receiver.try_recv() {
if let Ok(action) = self.do_something_receiver.try_recv() { action(self);
action(self);
} else {
break;
}
} }
// search // search
let mut search_changed = false; let mut search_changed = false;
@@ -396,7 +393,7 @@ impl GuiElem for LibraryBrowser {
// - // -
if self.library_updated { if self.library_updated {
self.library_updated = false; self.library_updated = false;
self.update_local_library(&info.database, |(_, a), (_, b)| a.name.cmp(&b.name)); self.update_local_library(info.database, |(_, a), (_, b)| a.name.cmp(&b.name));
search_changed = true; search_changed = true;
} }
if search_changed { if search_changed {
@@ -404,7 +401,7 @@ impl GuiElem for LibraryBrowser {
s: &LibraryBrowser, s: &LibraryBrowser,
pat: &str, pat: &str,
regex: &Option<Regex>, regex: &Option<Regex>,
search_text: &String, search_text: &str,
filter: &Filter, filter: &Filter,
search_gd: &GeneralData, search_gd: &GeneralData,
) -> f32 { ) -> f32 {
@@ -414,7 +411,7 @@ impl GuiElem for LibraryBrowser {
if let Some(r) = regex { if let Some(r) = regex {
if s.search_prefers_start_matches { if s.search_prefers_start_matches {
r.find_iter(pat) r.find_iter(pat)
.map(|m| match pat[0..m.start()].chars().rev().next() { .map(|m| match pat[0..m.start()].chars().next_back() {
// found at the start of h, reaches to the end (whole pattern is part of the match) // found at the start of h, reaches to the end (whole pattern is part of the match)
None if m.end() == pat.len() => 6.0, None if m.end() == pat.len() => 6.0,
// found at start of h // found at start of h
@@ -433,11 +430,7 @@ impl GuiElem for LibraryBrowser {
}) })
.fold(0.0, f32::max) .fold(0.0, f32::max)
} else { } else {
if r.is_match(pat) { if r.is_match(pat) { 2.0 } else { 0.0 }
2.0
} else {
0.0
}
} }
} else if search_text.is_empty() { } else if search_text.is_empty() {
1.0 1.0
@@ -448,7 +441,7 @@ impl GuiElem for LibraryBrowser {
let allow_singles = self.search_album.is_empty() let allow_singles = self.search_album.is_empty()
&& self.filter_albums.lock().unwrap().filters.is_empty(); && self.filter_albums.lock().unwrap().filters.is_empty();
self.filter_local_library( self.filter_local_library(
&info.database, info.database,
|s, artist| { |s, artist| {
filter( filter(
s, s,
@@ -496,35 +489,35 @@ impl GuiElem for LibraryBrowser {
self.selected_popup_state.1 = artists; self.selected_popup_state.1 = artists;
self.selected_popup_state.2 = albums; self.selected_popup_state.2 = albums;
self.selected_popup_state.3 = songs; self.selected_popup_state.3 = songs;
if artists > 0 || albums > 0 || songs > 0 { if (artists > 0 || albums > 0 || songs > 0)
if let Some(text) = match (artists, albums, songs) { && let Some(text) = match (artists, albums, songs) {
(0, 0, 0) => None, (0, 0, 0) => None,
(0, 0, 1) => Some(format!("1 song selected")), (0, 0, 1) => Some("1 song selected".to_owned()),
(0, 0, s) => Some(format!("{s} songs selected")), (0, 0, s) => Some(format!("{s} songs selected")),
(0, 1, 0) => Some(format!("1 album selected")), (0, 1, 0) => Some("1 album selected".to_owned()),
(0, al, 0) => Some(format!("{al} albums selected")), (0, al, 0) => Some(format!("{al} albums selected")),
(1, 0, 0) => Some(format!("1 artist selected")), (1, 0, 0) => Some("1 artist selected".to_owned()),
(ar, 0, 0) => Some(format!("{ar} artists selected")), (ar, 0, 0) => Some(format!("{ar} artists selected")),
(0, 1, 1) => Some(format!("1 song and 1 album selected")), (0, 1, 1) => Some("1 song and 1 album selected".to_owned()),
(0, 1, s) => Some(format!("{s} songs and 1 album selected")), (0, 1, s) => Some(format!("{s} songs and 1 album selected")),
(0, al, 1) => Some(format!("1 song and {al} albums selected")), (0, al, 1) => Some(format!("1 song and {al} albums selected")),
(0, al, s) => Some(format!("{s} songs and {al} albums selected")), (0, al, s) => Some(format!("{s} songs and {al} albums selected")),
(1, 0, 1) => Some(format!("1 song and 1 artist selected")), (1, 0, 1) => Some("1 song and 1 artist selected".to_owned()),
(1, 0, s) => Some(format!("{s} songs and 1 artist selected")), (1, 0, s) => Some(format!("{s} songs and 1 artist selected")),
(ar, 0, 1) => Some(format!("1 song and {ar} artists selected")), (ar, 0, 1) => Some(format!("1 song and {ar} artists selected")),
(ar, 0, s) => Some(format!("{s} songs and {ar} artists selected")), (ar, 0, s) => Some(format!("{s} songs and {ar} artists selected")),
(1, 1, 0) => Some(format!("1 album and 1 artist selected")), (1, 1, 0) => Some("1 album and 1 artist selected".to_owned()),
(1, al, 0) => Some(format!("{al} albums and 1 artist selected")), (1, al, 0) => Some(format!("{al} albums and 1 artist selected")),
(ar, 1, 0) => Some(format!("1 album and {ar} artists selected")), (ar, 1, 0) => Some(format!("1 album and {ar} artists selected")),
(ar, al, 0) => Some(format!("{al} albums and {ar} artists selected")), (ar, al, 0) => Some(format!("{al} albums and {ar} artists selected")),
(1, 1, 1) => Some(format!("1 song, 1 album and 1 artist selected")), (1, 1, 1) => Some("1 song, 1 album and 1 artist selected".to_owned()),
(1, 1, s) => Some(format!("{s} songs, 1 album and 1 artist selected")), (1, 1, s) => Some(format!("{s} songs, 1 album and 1 artist selected")),
(1, al, 1) => { (1, al, 1) => {
Some(format!("1 song, {al} albums and 1 artist selected")) Some(format!("1 song, {al} albums and 1 artist selected"))
@@ -544,14 +537,13 @@ impl GuiElem for LibraryBrowser {
(ar, al, s) => { (ar, al, s) => {
Some(format!("{s} songs, {al} albums and {ar} artists selected")) Some(format!("{s} songs, {al} albums and {ar} artists selected"))
} }
} {
*self.c_selected_counter_panel.children[0].content.text() = text;
} }
} else { {
*self.c_selected_counter_panel.children[0].content.text() = text;
} }
} }
} }
self.config.redraw = true; self.config.redraw_once();
} }
// selected popup // selected popup
{ {
@@ -571,7 +563,7 @@ impl GuiElem for LibraryBrowser {
} else { } else {
if self.selected_popup_state.0 != 0.0 { if self.selected_popup_state.0 != 0.0 {
redraw = true; redraw = true;
self.selected_popup_state.0 = 0.7 * self.selected_popup_state.0; self.selected_popup_state.0 *= 0.7;
if self.selected_popup_state.0 < 0.01 { if self.selected_popup_state.0 < 0.01 {
self.selected_popup_state.0 = 0.0; self.selected_popup_state.0 = 0.0;
self.c_selected_counter_panel.config_mut().enabled = false; self.c_selected_counter_panel.config_mut().enabled = false;
@@ -588,9 +580,9 @@ impl GuiElem for LibraryBrowser {
} }
} }
} }
if self.config.redraw || info.pos.size() != self.config.pixel_pos.size() { if self.config.redraw() || info.pos.size() != self.config.pixel_pos.size() {
self.config.redraw = false; self.config.redrawn();
self.update_ui(&info.database, info.line_height); self.update_ui(info.database, info.line_height);
} }
} }
fn updated_library(&mut self) { fn updated_library(&mut self) {
@@ -631,13 +623,13 @@ impl LibraryBrowser {
self.library_sorted = artists self.library_sorted = artists
.into_iter() .into_iter()
.map(|(ar_id, artist)| { .map(|(ar_id, artist)| {
let singles = artist.singles.iter().map(|id| *id).collect(); let singles = artist.singles.clone();
let albums = artist let albums = artist
.albums .albums
.iter() .iter()
.map(|id| { .map(|id| {
let songs = if let Some(album) = db.albums().get(id) { let songs = if let Some(album) = db.albums().get(id) {
album.songs.iter().map(|id| *id).collect() album.songs.clone()
} else { } else {
eprintln!("[warn] No album with id {id} found in db!"); eprintln!("[warn] No album with id {id} found in db!");
vec![] vec![]
@@ -762,7 +754,7 @@ impl LibraryBrowser {
let library_scroll_box = &mut self.c_scroll_box; let library_scroll_box = &mut self.c_scroll_box;
library_scroll_box.children = elems; library_scroll_box.children = elems;
library_scroll_box.children_heights = elemh; library_scroll_box.children_heights = elemh;
library_scroll_box.config_mut().redraw = true; library_scroll_box.config_mut().redraw_once();
} }
fn build_ui_element_artist(&self, id: ArtistId, db: &Database, h: f32) -> (ListElement, f32) { fn build_ui_element_artist(&self, id: ArtistId, db: &Database, h: f32) -> (ListElement, f32) {
( (
@@ -878,7 +870,7 @@ impl ListArtist {
None, None,
Vec2::new(0.0, 0.5), Vec2::new(0.0, 0.5),
); );
config.redraw = true; config.redraw_once();
Self { Self {
config: config.w_mouse(), config: config.w_mouse(),
id, id,
@@ -913,8 +905,8 @@ impl GuiElem for ListArtist {
self self
} }
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 { if self.config.redraw() {
self.config.redraw = false; self.config.redrawn();
let sel = self.selected.contains_artist(&self.id); let sel = self.selected.contains_artist(&self.id);
if sel != self.sel { if sel != self.sel {
self.sel = sel; self.sel = sel;
@@ -972,7 +964,7 @@ impl GuiElem for ListArtist {
fn mouse_up(&mut self, e: &mut EventInfo, button: MouseButton) -> Vec<GuiAction> { fn mouse_up(&mut self, e: &mut EventInfo, button: MouseButton) -> Vec<GuiAction> {
if self.mouse && button == MouseButton::Left { if self.mouse && button == MouseButton::Left {
self.mouse = false; self.mouse = false;
self.config.redraw = true; self.config.redraw_once();
if e.take() { if e.take() {
if !self.sel { if !self.sel {
self.selected.insert_artist(self.id); self.selected.insert_artist(self.id);
@@ -1024,7 +1016,7 @@ impl ListAlbum {
), ),
]], ]],
); );
config.redraw = true; config.redraw_once();
Self { Self {
config: config.w_mouse(), config: config.w_mouse(),
id, id,
@@ -1059,8 +1051,8 @@ impl GuiElem for ListAlbum {
self self
} }
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 { if self.config.redraw() {
self.config.redraw = false; self.config.redrawn();
let sel = self.selected.contains_album(&self.id); let sel = self.selected.contains_album(&self.id);
if sel != self.sel { if sel != self.sel {
self.sel = sel; self.sel = sel;
@@ -1118,7 +1110,7 @@ impl GuiElem for ListAlbum {
fn mouse_up(&mut self, e: &mut EventInfo, button: MouseButton) -> Vec<GuiAction> { fn mouse_up(&mut self, e: &mut EventInfo, button: MouseButton) -> Vec<GuiAction> {
if self.mouse && button == MouseButton::Left { if self.mouse && button == MouseButton::Left {
self.mouse = false; self.mouse = false;
self.config.redraw = true; self.config.redraw_once();
if e.take() { if e.take() {
if !self.sel { if !self.sel {
self.selected.insert_album(self.id); self.selected.insert_album(self.id);
@@ -1167,7 +1159,7 @@ impl ListSong {
), ),
]], ]],
); );
config.redraw = true; config.redraw_once();
Self { Self {
config: config.w_mouse(), config: config.w_mouse(),
id, id,
@@ -1202,8 +1194,8 @@ impl GuiElem for ListSong {
self self
} }
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 { if self.config.redraw() {
self.config.redraw = false; self.config.redrawn();
let sel = self.selected.contains_song(&self.id); let sel = self.selected.contains_song(&self.id);
if sel != self.sel { if sel != self.sel {
self.sel = sel; self.sel = sel;
@@ -1261,7 +1253,7 @@ impl GuiElem for ListSong {
fn mouse_up(&mut self, e: &mut EventInfo, button: MouseButton) -> Vec<GuiAction> { fn mouse_up(&mut self, e: &mut EventInfo, button: MouseButton) -> Vec<GuiAction> {
if self.mouse && button == MouseButton::Left { if self.mouse && button == MouseButton::Left {
self.mouse = false; self.mouse = false;
self.config.redraw = true; self.config.redraw_once();
if e.take() { if e.take() {
if !self.sel { if !self.sel {
self.selected.insert_song(self.id); self.selected.insert_song(self.id);
@@ -1288,7 +1280,7 @@ impl GuiElem for ListSong {
}, },
[Label::new( [Label::new(
GuiElemCfg::default(), GuiElemCfg::default(),
format!("Edit this song"), "Edit this song".to_owned(),
Color::WHITE, Color::WHITE,
None, None,
Vec2::new_y(0.5), Vec2::new_y(0.5),
@@ -1315,7 +1307,7 @@ impl GuiElem for ListSong {
}, },
[Label::new( [Label::new(
GuiElemCfg::default(), GuiElemCfg::default(),
format!("Edit selected songs"), "Edit selected songs".to_owned(),
Color::WHITE, Color::WHITE,
None, None,
Vec2::new_y(0.5), Vec2::new_y(0.5),
@@ -1354,6 +1346,7 @@ struct FilterTab {
buttons: Vec<Button<[Label; 1]>>, buttons: Vec<Button<[Label; 1]>>,
filters: Vec<FilterLine>, filters: Vec<FilterLine>,
} }
#[allow(clippy::large_enum_variant)]
enum FilterLine { enum FilterLine {
Joiner(Button<[Label; 1]>), Joiner(Button<[Label; 1]>),
Not(Label), Not(Label),
@@ -1394,10 +1387,10 @@ impl GuiElemChildren for FilterTab {
self.buttons.len() + self.filters.len() self.buttons.len() + self.filters.len()
} }
} }
const FP_CASESENS_N: &'static str = "search is case-insensitive"; const FP_CASESENS_N: &str = "search is case-insensitive";
const FP_CASESENS_Y: &'static str = "search is case-sensitive!"; const FP_CASESENS_Y: &str = "search is case-sensitive!";
const FP_PREFSTART_N: &'static str = "simple search"; const FP_PREFSTART_N: &str = "simple search";
const FP_PREFSTART_Y: &'static str = "will prefer matches at the start of a word"; const FP_PREFSTART_Y: &str = "will prefer matches at the start of a word";
impl FilterPanel { impl FilterPanel {
pub fn new( pub fn new(
search_settings_changed: Arc<AtomicBool>, search_settings_changed: Arc<AtomicBool>,
@@ -1697,8 +1690,8 @@ impl FilterPanel {
.iter() .iter()
.cloned() .cloned()
.map(|(text, preset)| { .map(|(text, preset)| {
let f = Arc::clone(&filter); let f = Arc::clone(filter);
let oc = Arc::clone(&on_change); let oc = Arc::clone(on_change);
Button::new( Button::new(
GuiElemCfg::default(), GuiElemCfg::default(),
move |_| { move |_| {
@@ -1876,26 +1869,24 @@ impl FilterPanel {
let oc = Arc::clone(on_change); let oc = Arc::clone(on_change);
let p = path.clone(); let p = path.clone();
tf1.on_changed = Some(Box::new(move |text| { tf1.on_changed = Some(Box::new(move |text| {
if let Ok(n) = text.parse() { if let Ok(n) = text.parse()
if let Some(Ok(FilterType::TagWithValueInt(_, v, _))) = && let Some(Ok(FilterType::TagWithValueInt(_, v, _))) =
mx.lock().unwrap().get_mut(&p) mx.lock().unwrap().get_mut(&p)
{ {
*v = n; *v = n;
oc(false); oc(false);
}
} }
})); }));
let mx = Arc::clone(mutex); let mx = Arc::clone(mutex);
let oc = Arc::clone(on_change); let oc = Arc::clone(on_change);
let p = path.clone(); let p = path.clone();
tf2.on_changed = Some(Box::new(move |text| { tf2.on_changed = Some(Box::new(move |text| {
if let Ok(n) = text.parse() { if let Ok(n) = text.parse()
if let Some(Ok(FilterType::TagWithValueInt(_, _, v))) = && let Some(Ok(FilterType::TagWithValueInt(_, _, v))) =
mx.lock().unwrap().get_mut(&p) mx.lock().unwrap().get_mut(&p)
{ {
*v = n; *v = n;
oc(false); oc(false);
}
} }
})); }));
children.push(FilterLine::TagWithValueInt(Panel::new( children.push(FilterLine::TagWithValueInt(Panel::new(
@@ -1922,22 +1913,20 @@ impl GuiElem for FilterPanel {
fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) { fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) {
// set line height // set line height
if info.line_height != self.line_height { if info.line_height != self.line_height {
for h in &mut self.c_tab_main.children_heights { self.c_tab_main.children_heights.fill(info.line_height);
*h = info.line_height; self.c_tab_filters_songs
} .children_heights
for h in &mut self.c_tab_filters_songs.children_heights { .fill(info.line_height);
*h = info.line_height; self.c_tab_filters_albums
} .children_heights
for h in &mut self.c_tab_filters_albums.children_heights { .fill(info.line_height);
*h = info.line_height; self.c_tab_filters_artists
} .children_heights
for h in &mut self.c_tab_filters_artists.children_heights { .fill(info.line_height);
*h = info.line_height; self.c_tab_main.config_mut().redraw_once();
} self.c_tab_filters_songs.config_mut().redraw_once();
self.c_tab_main.config_mut().redraw = true; self.c_tab_filters_albums.config_mut().redraw_once();
self.c_tab_filters_songs.config_mut().redraw = true; self.c_tab_filters_artists.config_mut().redraw_once();
self.c_tab_filters_albums.config_mut().redraw = true;
self.c_tab_filters_artists.config_mut().redraw = true;
self.line_height = info.line_height; self.line_height = info.line_height;
} }
// maybe switch tabs // maybe switch tabs
@@ -2001,7 +1990,7 @@ impl GuiElem for FilterPanel {
); );
sb.children = ft; sb.children = ft;
sb.children_heights = heights; sb.children_heights = heights;
sb.config_mut().redraw = true; sb.config_mut().redraw_once();
} }
_ => {} _ => {}
} }

View File

@@ -177,10 +177,10 @@ impl GuiElem for NotifOverlay {
} }
} }
// redraw // redraw
if !self.notifs.is_empty() { if !self.notifs.is_empty()
if let Some(h) = &info.helper { && let Some(h) = &info.helper
h.request_redraw(); {
} h.request_redraw();
} }
} }
fn draw_rev(&self) -> bool { fn draw_rev(&self) -> bool {

View File

@@ -86,7 +86,7 @@ impl CurrentInfo {
GuiElemCfg::default(), GuiElemCfg::default(),
[Label::new( [Label::new(
GuiElemCfg::default(), GuiElemCfg::default(),
format!("Couldn't load cover"), "Couldn't load cover".to_owned(),
Color::WHITE, Color::WHITE,
None, None,
Vec2::new(0.5, 0.5), Vec2::new(0.5, 0.5),

View File

@@ -1,9 +1,9 @@
use musicdb_lib::{ use musicdb_lib::{
data::{ data::{
AlbumId, ArtistId,
database::Database, database::Database,
queue::{Queue, QueueContent, QueueDuration}, queue::{Queue, QueueContent, QueueDuration},
song::Song, song::Song,
AlbumId, ArtistId,
}, },
server::{Action, Req}, server::{Action, Req},
}; };
@@ -16,7 +16,7 @@ use speedy2d::{
use crate::{ use crate::{
gui::{Dragging, DrawInfo, EventInfo, GuiAction, GuiElem, GuiElemCfg}, gui::{Dragging, DrawInfo, EventInfo, GuiAction, GuiElem, GuiElemCfg},
gui_base::{Panel, ScrollBox}, gui_base::{Button, Panel, ScrollBox},
gui_text::{self, AdvancedLabel, Label, TextField}, gui_text::{self, AdvancedLabel, Label, TextField},
}; };
@@ -95,7 +95,7 @@ impl QueueViewer {
musicdb_lib::data::queue::QueueFolder { musicdb_lib::data::queue::QueueFolder {
index: 0, index: 0,
content: vec![], content: vec![],
name: format!("folder name"), name: "folder name".to_owned(),
order: None, order: None,
}, },
false, false,
@@ -104,7 +104,7 @@ impl QueueViewer {
{ {
let mut tf = TextField::new( let mut tf = TextField::new(
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))),
format!("folder name"), "folder name".to_owned(),
Color::from_rgb(0.0, 0.33, 0.0), Color::from_rgb(0.0, 0.33, 0.0),
Color::from_rgb(0.0, 0.67, 0.0), Color::from_rgb(0.0, 0.67, 0.0),
); );
@@ -219,8 +219,8 @@ impl GuiElem for QueueViewer {
} }
} }
} }
let dt = fmt_dur(info.database.queue.duration_total(&info.database)); let dt = fmt_dur(info.database.queue.duration_total(info.database));
let dr = fmt_dur(info.database.queue.duration_remaining(&info.database)); let dr = fmt_dur(info.database.queue.duration_remaining(info.database));
label.content = vec![ label.content = vec![
vec![( vec![(
gui_text::AdvancedContent::Text(gui_text::Content::new( gui_text::AdvancedContent::Text(gui_text::Content::new(
@@ -239,15 +239,15 @@ impl GuiElem for QueueViewer {
1.0, 1.0,
)], )],
]; ];
label.config_mut().redraw = true; label.config_mut().redraw_once();
} }
if self.config.redraw || info.pos.size() != self.config.pixel_pos.size() { if self.config.redraw() || info.pos.size() != self.config.pixel_pos.size() {
self.config.redraw = false; self.config.redrawn();
let mut c = vec![]; let mut c = vec![];
let mut h = vec![]; let mut h = vec![];
queue_gui( queue_gui(
&info.database.queue, &info.database.queue,
&info.database, info.database,
0.0, 0.0,
0.02, 0.02,
info.line_height, info.line_height,
@@ -260,12 +260,12 @@ impl GuiElem for QueueViewer {
let scroll_box = &mut self.c_scroll_box; let scroll_box = &mut self.c_scroll_box;
scroll_box.children = c; scroll_box.children = c;
scroll_box.children_heights = h; scroll_box.children_heights = h;
scroll_box.config_mut().redraw = true; scroll_box.config_mut().redraw_once();
} }
} }
fn updated_queue(&mut self) { fn updated_queue(&mut self) {
self.queue_updated = true; self.queue_updated = true;
self.config.redraw = true; self.config.redraw_once();
} }
} }
@@ -353,7 +353,7 @@ fn queue_gui(
Box::new(QueueLoop::new(cfg.clone(), path, queue.clone(), current)), Box::new(QueueLoop::new(cfg.clone(), path, queue.clone(), current)),
); );
if let Some(mut inner) = queue_gui( if let Some(mut inner) = queue_gui(
&inner, inner,
db, db,
depth, depth,
depth_inc_by, depth_inc_by,
@@ -423,7 +423,7 @@ impl GuiElem for QueueEmptySpaceDragHandler {
fn generic_queue_draw( fn generic_queue_draw(
info: &mut DrawInfo, info: &mut DrawInfo,
path: &Vec<usize>, path: &[usize],
queue: impl FnOnce() -> Queue, queue: impl FnOnce() -> Queue,
mouse: &mut bool, mouse: &mut bool,
copy_on_mouse_down: bool, copy_on_mouse_down: bool,
@@ -435,7 +435,7 @@ fn generic_queue_draw(
Dragging::Queue(if copy_on_mouse_down { Dragging::Queue(if copy_on_mouse_down {
Ok(queue()) Ok(queue())
} else { } else {
Err(path.clone()) Err(path.to_vec())
}), }),
None, None,
)))); ))));
@@ -567,8 +567,24 @@ impl GuiElem for QueueSong {
if button == MouseButton::Left && e.take() { if button == MouseButton::Left && e.take() {
self.mouse = true; self.mouse = true;
self.copy_on_mouse_down = self.copy; self.copy_on_mouse_down = self.copy;
vec![]
} else if button == MouseButton::Right && e.take() {
let me = self.song.clone();
let menu_actions: Vec<Box<dyn GuiElem + 'static>> = vec![Box::new(Button::new(
GuiElemCfg::default(),
move |_| vec![GuiAction::EditSongs(vec![me.clone()])],
[Label::new(
GuiElemCfg::default(),
"Edit this song".to_owned(),
Color::WHITE,
None,
Vec2::new_y(0.5),
)],
))];
vec![GuiAction::ContextMenu(Some(menu_actions))]
} else {
vec![]
} }
vec![]
} }
fn mouse_up(&mut self, e: &mut EventInfo, button: MouseButton) -> Vec<GuiAction> { fn mouse_up(&mut self, e: &mut EventInfo, button: MouseButton) -> Vec<GuiAction> {
if self.mouse && button == MouseButton::Left { if self.mouse && button == MouseButton::Left {
@@ -648,10 +664,8 @@ impl GuiElem for QueueSong {
} }
}, },
move |mut p, q| { move |mut p, q| {
if insert_below { if insert_below && let Some(l) = p.last_mut() {
if let Some(l) = p.last_mut() { *l += 1;
*l += 1;
}
} }
Action::QueueMove(q, p) Action::QueueMove(q, p)
}, },
@@ -971,9 +985,9 @@ impl QueueLoop {
match queue.content() { match queue.content() {
QueueContent::Loop(total, _current, _) => { QueueContent::Loop(total, _current, _) => {
if *total == 0 { if *total == 0 {
format!("repeat forever") "repeat forever".to_owned()
} else if *total == 1 { } else if *total == 1 {
format!("repeat 1 time") "repeat 1 time".to_owned()
} else { } else {
format!("repeat {total} times") format!("repeat {total} times")
} }
@@ -1141,46 +1155,38 @@ fn dragged_add_to_queue<T: 'static>(
} }
fn add_to_queue_album_by_id(id: AlbumId, db: &Database) -> Option<Queue> { fn add_to_queue_album_by_id(id: AlbumId, db: &Database) -> Option<Queue> {
if let Some(album) = db.albums().get(&id) { db.albums().get(&id).map(|album| {
Some( QueueContent::Folder(musicdb_lib::data::queue::QueueFolder {
QueueContent::Folder(musicdb_lib::data::queue::QueueFolder { index: 0,
index: 0, content: album
content: album .songs
.songs .iter()
.iter() .map(|id| QueueContent::Song(*id).into())
.map(|id| QueueContent::Song(*id).into()) .collect(),
.collect(), name: album.name.clone(),
name: album.name.clone(), order: None,
order: None, })
}) .into()
.into(), })
)
} else {
None
}
} }
fn add_to_queue_artist_by_id(id: ArtistId, db: &Database) -> Option<Queue> { fn add_to_queue_artist_by_id(id: ArtistId, db: &Database) -> Option<Queue> {
if let Some(artist) = db.artists().get(&id) { db.artists().get(&id).map(|artist| {
Some( QueueContent::Folder(musicdb_lib::data::queue::QueueFolder {
QueueContent::Folder(musicdb_lib::data::queue::QueueFolder { index: 0,
index: 0, content: artist
content: artist .singles
.singles .iter()
.iter() .map(|id| QueueContent::Song(*id).into())
.map(|id| QueueContent::Song(*id).into()) .chain(
.chain( artist
artist .albums
.albums .iter()
.iter() .filter_map(|id| add_to_queue_album_by_id(*id, db)),
.filter_map(|id| add_to_queue_album_by_id(*id, db)), )
) .collect(),
.collect(), name: artist.name.clone(),
name: artist.name.clone(), order: None,
order: None, })
}) .into()
.into(), })
)
} else {
None
}
} }

View File

@@ -4,8 +4,8 @@ use musicdb_lib::{
data::queue::{QueueContent, QueueFolder}, data::queue::{QueueContent, QueueFolder},
server::{Action, Req}, server::{Action, Req},
}; };
use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle, window::VirtualKeyCode, Graphics2D}; use speedy2d::{Graphics2D, color::Color, dimen::Vec2, shape::Rectangle, window::VirtualKeyCode};
use uianimator::{default_animator_f64_quadratic::DefaultAnimatorF64Quadratic, Animator}; use uianimator::{Animator, default_animator_f64_quadratic::DefaultAnimatorF64Quadratic};
use crate::{ use crate::{
gui::{ gui::{
@@ -54,7 +54,6 @@ pub struct GuiScreen {
pub c_main_view: Panel<MainView>, pub c_main_view: Panel<MainView>,
pub c_context_menu: Option<Box<dyn GuiElem>>, pub c_context_menu: Option<Box<dyn GuiElem>>,
pub idle: DefaultAnimatorF64Quadratic, pub idle: DefaultAnimatorF64Quadratic,
pub idle_prev_val: f32,
// pub settings: (bool, Option<Instant>), // pub settings: (bool, Option<Instant>),
pub settings: (bool, Option<Instant>), pub settings: (bool, Option<Instant>),
pub last_interaction: Instant, pub last_interaction: Instant,
@@ -172,7 +171,6 @@ impl GuiScreen {
c_context_menu: None, c_context_menu: None,
hotkey: Hotkey::new_noshift(VirtualKeyCode::Escape), hotkey: Hotkey::new_noshift(VirtualKeyCode::Escape),
idle: DefaultAnimatorF64Quadratic::new(0.0, 0.67), idle: DefaultAnimatorF64Quadratic::new(0.0, 0.67),
idle_prev_val: 0.0,
settings: (false, None), settings: (false, None),
last_interaction: Instant::now(), last_interaction: Instant::now(),
idle_timeout: Some(60.0), idle_timeout: Some(60.0),
@@ -184,17 +182,9 @@ impl GuiScreen {
let prog = since.elapsed().as_secs_f32() / seconds; let prog = since.elapsed().as_secs_f32() / seconds;
if prog >= 1.0 { if prog >= 1.0 {
v.1 = None; v.1 = None;
if v.0 { if v.0 { 1.0 } else { 0.0 }
1.0
} else {
0.0
}
} else { } else {
if v.0 { if v.0 { prog } else { 1.0 - prog }
prog
} else {
1.0 - prog
}
} }
} else if v.0 { } else if v.0 {
1.0 1.0
@@ -222,12 +212,11 @@ impl GuiScreen {
self.idle.set_target(0.0, Instant::now()); self.idle.set_target(0.0, Instant::now());
} }
fn idle_check(&mut self) { fn idle_check(&mut self) {
if self.idle.target() == 0.0 { if self.idle.target() == 0.0
if let Some(dur) = &self.idle_timeout { && let Some(dur) = &self.idle_timeout
if self.last_interaction.elapsed().as_secs_f64() > *dur { && self.last_interaction.elapsed().as_secs_f64() > *dur
self.idle.set_target(1.0, Instant::now()); {
} self.idle.set_target(1.0, Instant::now());
}
} }
} }
@@ -253,7 +242,7 @@ impl GuiElem for GuiScreen {
] ]
.into_iter() .into_iter()
.chain(self.c_editing_songs.as_mut().map(|v| v.elem_mut())) .chain(self.c_editing_songs.as_mut().map(|v| v.elem_mut()))
.chain(self.c_song_adder.as_mut().map(|v| v.elem_mut()).into_iter()) .chain(self.c_song_adder.as_mut().map(|v| v.elem_mut()))
.chain([ .chain([
self.c_status_bar.elem_mut(), self.c_status_bar.elem_mut(),
self.c_settings.elem_mut(), self.c_settings.elem_mut(),
@@ -404,15 +393,12 @@ impl GuiElem for GuiScreen {
}; };
// request_redraw for animations // request_redraw for animations
let idle_value = self.idle.get_value(info.time) as f32; let idle_value = self.idle.get_value(info.time) as f32;
let idle_changed = self.idle_prev_val != idle_value; let idle_changed = self.idle.target() as f32 != idle_value;
if idle_changed || idle_exit_anim || self.settings.1.is_some() { if idle_changed || idle_exit_anim || self.settings.1.is_some() {
self.idle_prev_val = idle_value;
if let Some(h) = &info.helper { if let Some(h) = &info.helper {
h.request_redraw() h.request_redraw()
} }
} // animations: idle
// animations: idle
if idle_changed {
let enable_normal_ui = idle_value < 1.0; let enable_normal_ui = idle_value < 1.0;
self.set_normal_ui_enabled(enable_normal_ui); self.set_normal_ui_enabled(enable_normal_ui);
if let Some(h) = &info.helper { if let Some(h) = &info.helper {

View File

@@ -1,12 +1,12 @@
use std::sync::{atomic::AtomicBool, Arc, Mutex}; use std::sync::{Arc, Mutex, atomic::AtomicBool};
use musicdb_lib::server::Action; use musicdb_lib::server::Action;
use speedy2d::{ use speedy2d::{
Graphics2D,
color::Color, color::Color,
dimen::Vec2, dimen::Vec2,
shape::Rectangle, shape::Rectangle,
window::{KeyScancode, ModifiersState, MouseButton, VirtualKeyCode}, window::{KeyScancode, ModifiersState, MouseButton, VirtualKeyCode},
Graphics2D,
}; };
use crate::{ use crate::{
@@ -32,7 +32,7 @@ impl Settings {
scroll_sensitivity_lines: f64, scroll_sensitivity_lines: f64,
scroll_sensitivity_pages: f64, scroll_sensitivity_pages: f64,
) -> Self { ) -> Self {
config.redraw = true; config.redraw_once();
Self { Self {
config, config,
c_scroll_box: ScrollBox::new( c_scroll_box: ScrollBox::new(
@@ -53,11 +53,7 @@ impl Settings {
} }
pub fn get_timeout_val(&self) -> Option<f64> { pub fn get_timeout_val(&self) -> Option<f64> {
let v = self.c_scroll_box.children.idle_time.children.1.val; let v = self.c_scroll_box.children.idle_time.children.1.val;
if v > 0.0 { if v > 0.0 { Some(v * v) } else { None }
Some(v * v)
} else {
None
}
} }
} }
pub struct SettingsContent { pub struct SettingsContent {
@@ -125,7 +121,7 @@ impl KeybindInput {
b.key, b.key,
) )
} else { } else {
format!("") String::new()
}, },
Color::WHITE, Color::WHITE,
None, None,
@@ -429,7 +425,7 @@ impl SettingsContent {
} }
if hours == 0 && minutes < 10 && (seconds > 0 || minutes == 0) { if hours == 0 && minutes < 10 && (seconds > 0 || minutes == 0) {
s.push_str(&seconds.to_string()); s.push_str(&seconds.to_string());
s.push_str("s"); s.push('s');
} else if s.ends_with(" ") { } else if s.ends_with(" ") {
s.pop(); s.pop();
} }
@@ -532,7 +528,7 @@ impl GuiElem for Settings {
} }
fn draw(&mut self, info: &mut DrawInfo, _g: &mut Graphics2D) { fn draw(&mut self, info: &mut DrawInfo, _g: &mut Graphics2D) {
if self.c_scroll_box.children.draw(info) { if self.c_scroll_box.children.draw(info) {
self.c_scroll_box.config_mut().redraw = true; self.c_scroll_box.config_mut().redraw_once();
} }
let scrollbox = &mut self.c_scroll_box; let scrollbox = &mut self.c_scroll_box;
let background = &mut self.c_background; let background = &mut self.c_background;
@@ -547,9 +543,9 @@ impl GuiElem for Settings {
settings_opacity_slider.val as _, settings_opacity_slider.val as _,
); );
} }
if self.config.redraw { if self.config.redraw() {
self.config.redraw = false; self.config.redrawn();
scrollbox.config_mut().redraw = true; scrollbox.config_mut().redraw_once();
if scrollbox.children_heights.len() == scrollbox.children.len() { if scrollbox.children_heights.len() == scrollbox.children.len() {
for (i, h) in scrollbox.children_heights.iter_mut().enumerate() { for (i, h) in scrollbox.children_heights.iter_mut().enumerate() {
*h = if i == 0 || i >= 8 { *h = if i == 0 || i >= 8 {
@@ -560,7 +556,7 @@ impl GuiElem for Settings {
} }
} else { } else {
// try again next frame (scrollbox will autofill the children_heights vec) // try again next frame (scrollbox will autofill the children_heights vec)
self.config.redraw = true; self.config.redraw_once();
} }
} }
} }
@@ -591,7 +587,7 @@ pub fn build_keybind_elems(
vec![ vec![
vec![( vec![(
AdvancedContent::Text(Content::new( AdvancedContent::Text(Content::new(
format!("{}", v.title), v.title.to_string(),
if v.enabled { if v.enabled {
Color::WHITE Color::WHITE
} else { } else {
@@ -603,7 +599,7 @@ pub fn build_keybind_elems(
)], )],
vec![( vec![(
AdvancedContent::Text(Content::new( AdvancedContent::Text(Content::new(
format!("{}", v.description), v.description.to_string(),
if v.enabled { if v.enabled {
Color::LIGHT_GRAY Color::LIGHT_GRAY
} else { } else {

View File

@@ -1,5 +1,5 @@
use musicdb_lib::data::{AlbumId, ArtistId}; use musicdb_lib::data::{AlbumId, ArtistId};
use speedy2d::{color::Color, dimen::Vec2, Graphics2D}; use speedy2d::{Graphics2D, color::Color, dimen::Vec2};
use crate::{ use crate::{
gui::{DrawInfo, GuiElem, GuiElemCfg}, gui::{DrawInfo, GuiElem, GuiElemCfg},
@@ -30,13 +30,13 @@ impl SongAdder {
scroll_sensitivity_lines: f64, scroll_sensitivity_lines: f64,
scroll_sensitivity_pages: f64, scroll_sensitivity_pages: f64,
) -> Self { ) -> Self {
config.redraw = true; config.redraw_once();
Self { Self {
config, config,
state: 0, state: 0,
c_loading: Some(Label::new( c_loading: Some(Label::new(
GuiElemCfg::default(), GuiElemCfg::default(),
format!("Loading..."), "Loading...".to_owned(),
Color::GRAY, Color::GRAY,
None, None,
Vec2::new(0.5, 0.5), Vec2::new(0.5, 0.5),
@@ -99,7 +99,7 @@ impl GuiElem for SongAdder {
.iter() .iter()
.map(|(path, is_bad)| AddableSong::new(path.to_owned(), *is_bad)) .map(|(path, is_bad)| AddableSong::new(path.to_owned(), *is_bad))
.collect(); .collect();
self.c_scroll_box.config_mut().redraw = true; self.c_scroll_box.config_mut().redraw_once();
self.data = Some( self.data = Some(
data.into_iter() data.into_iter()
.map(|(p, b)| AddSong { .map(|(p, b)| AddSong {
@@ -125,9 +125,9 @@ impl GuiElem for SongAdder {
} }
} }
if self.config.redraw { if self.config.redraw() {
self.config.redraw = false; self.config.redrawn();
self.c_scroll_box.config_mut().redraw = true; self.c_scroll_box.config_mut().redraw_once();
} }
} }
} }
@@ -147,7 +147,7 @@ impl AddableSong {
|_| vec![], |_| vec![],
[Label::new( [Label::new(
GuiElemCfg::default(), GuiElemCfg::default(),
format!("{path}"), path.to_string(),
if is_bad { if is_bad {
Color::LIGHT_GRAY Color::LIGHT_GRAY
} else { } else {

View File

@@ -61,11 +61,11 @@ impl GuiElem for StatusBar {
self.c_song_label.content = if let Some(song) = self.current_info.current_song { self.c_song_label.content = if let Some(song) = self.current_info.current_song {
info.gui_config info.gui_config
.status_bar_text .status_bar_text
.gen_new(&info.database, info.database.get_song(&song)) .gen_new(info.database, info.database.get_song(&song))
} else { } else {
vec![] vec![]
}; };
self.c_song_label.config_mut().redraw = true; self.c_song_label.config_mut().redraw_once();
} }
if self.current_info.new_cover { if self.current_info.new_cover {
self.current_info.new_cover = false; self.current_info.new_cover = false;

View File

@@ -387,14 +387,14 @@ impl GuiElem for AdvancedLabel {
self self
} }
fn draw(&mut self, info: &mut crate::gui::DrawInfo, g: &mut speedy2d::Graphics2D) { fn draw(&mut self, info: &mut crate::gui::DrawInfo, g: &mut speedy2d::Graphics2D) {
if self.config.redraw if self.config.redraw()
|| self.config.pixel_pos.size() != info.pos.size() || self.config.pixel_pos.size() != info.pos.size()
|| self || self
.content .content
.iter() .iter()
.any(|v| v.iter().any(|(c, _, _)| c.will_redraw())) .any(|v| v.iter().any(|(c, _, _)| c.will_redraw()))
{ {
self.config.redraw = false; self.config.redrawn();
let mut max_len = 0.0; let mut max_len = 0.0;
let mut total_height = 0.0; let mut total_height = 0.0;
for line in &self.content { for line in &self.content {
@@ -461,7 +461,7 @@ impl GuiElem for AdvancedLabel {
if handle.is_none() { if handle.is_none() {
match source { match source {
ImageSource::Cover(id) => { ImageSource::Cover(id) => {
if let Some(img) = info.covers.get_mut(&id) { if let Some(img) = info.covers.get_mut(id) {
if let Some(img) = img.get_init(g) { if let Some(img) = img.get_init(g) {
*handle = Some(Some(img)); *handle = Some(Some(img));
} else { } else {

View File

@@ -18,14 +18,13 @@ impl Hotkey {
if self.modifiers == u8::MAX { if self.modifiers == u8::MAX {
return false; return false;
} }
down == false !down
&& key.is_some_and(|v| v == self.key) && key.is_some_and(|v| v == self.key)
&& (self.modifiers & 0b10 == 1 || (self.modifiers & 0b01 == 1) == modifiers.ctrl()) && (self.modifiers & 0b10 > 0 || (self.modifiers & 0b01 > 0) == modifiers.ctrl())
&& (self.modifiers & 0b1000 == 1 || (self.modifiers & 0b0100 == 1) == modifiers.shift()) && (self.modifiers & 0b1000 > 0 || (self.modifiers & 0b0100 > 0) == modifiers.shift())
&& (self.modifiers & 0b100000 == 1 && (self.modifiers & 0b100000 > 0 || (self.modifiers & 0b010000 > 0) == modifiers.alt())
|| (self.modifiers & 0b010000 == 1) == modifiers.alt()) && (self.modifiers & 0b10000000 > 0
&& (self.modifiers & 0b10000000 == 1 || (self.modifiers & 0b01000000 > 0) == modifiers.logo())
|| (self.modifiers & 0b01000000 == 1) == modifiers.logo())
} }
/// unlike noshift, this ignores the shift modifier /// unlike noshift, this ignores the shift modifier
pub fn new_key(key: VirtualKeyCode) -> Self { pub fn new_key(key: VirtualKeyCode) -> Self {

View File

@@ -1,5 +1,7 @@
#![allow(dead_code)] #![allow(dead_code)]
#![allow(unused_variables)] #![allow(unused_variables)]
#![allow(clippy::type_complexity)]
#![allow(clippy::too_many_arguments)]
use std::{ use std::{
io::{BufReader, Write}, io::{BufReader, Write},
@@ -18,8 +20,8 @@ use musicdb_lib::data::cache_manager::CacheManager;
use musicdb_lib::player::{Player, PlayerBackendFeat}; use musicdb_lib::player::{Player, PlayerBackendFeat};
use musicdb_lib::{ use musicdb_lib::{
data::{ data::{
database::{ClientIo, Database},
CoverId, SongId, CoverId, SongId,
database::{ClientIo, Database},
}, },
load::ToFromBytes, load::ToFromBytes,
server::Command, server::Command,
@@ -107,7 +109,9 @@ fn main() {
#[cfg(not(feature = "speedy2d"))] #[cfg(not(feature = "speedy2d"))]
#[cfg(not(feature = "mers"))] #[cfg(not(feature = "mers"))]
#[cfg(not(feature = "playback"))] #[cfg(not(feature = "playback"))]
compile_error!("None of the optional features are enabled. Without at least one of these, the application is useless! See Cargo.toml for info."); compile_error!(
"None of the optional features are enabled. Without at least one of these, the application is useless! See Cargo.toml for info."
);
// parse args // parse args
let args = Args::parse(); let args = Args::parse();
// start // start
@@ -215,7 +219,7 @@ fn main() {
} }
#[cfg(feature = "playback")] #[cfg(feature = "playback")]
if let Some(player) = &mut player { if let Some(player) = &mut player {
player.update_dont_uncache(&mut *db); player.update_dont_uncache(&mut db);
} }
drop(db); drop(db);
#[cfg(feature = "speedy2d")] #[cfg(feature = "speedy2d")]
@@ -250,10 +254,12 @@ fn main() {
Some(Arc::clone(&get_con)); Some(Arc::clone(&get_con));
} }
let occasional_refresh_sender = Arc::clone(&sender); let occasional_refresh_sender = Arc::clone(&sender);
thread::spawn(move || loop { thread::spawn(move || {
std::thread::sleep(std::time::Duration::from_secs(1)); loop {
if let Some(v) = &*occasional_refresh_sender.lock().unwrap() { std::thread::sleep(std::time::Duration::from_secs(1));
v.send_event(GuiEvent::Refresh).unwrap(); if let Some(v) = &*occasional_refresh_sender.lock().unwrap() {
v.send_event(GuiEvent::Refresh).unwrap();
}
} }
}); });
gui::main( gui::main(
@@ -328,12 +334,8 @@ fn main() {
pub fn accumulate<F: FnMut() -> Option<T>, T>(mut f: F) -> Vec<T> { pub fn accumulate<F: FnMut() -> Option<T>, T>(mut f: F) -> Vec<T> {
let mut o = vec![]; let mut o = vec![];
loop { while let Some(v) = f() {
if let Some(v) = f() { o.push(v);
o.push(v);
} else {
break;
}
} }
o o
} }

View File

@@ -113,7 +113,7 @@ impl TextBuilder {
} }
for part in &self.0 { for part in &self.0 {
match part { match part {
TextPart::LineBreak => out.push(std::mem::replace(line, vec![])), TextPart::LineBreak => out.push(std::mem::take(line)),
TextPart::SetColor(c) => *color = *c, TextPart::SetColor(c) => *color = *c,
TextPart::SetScale(v) => *scale = *v, TextPart::SetScale(v) => *scale = *v,
TextPart::SetHeightAlign(v) => *align = *v, TextPart::SetHeightAlign(v) => *align = *v,
@@ -124,17 +124,17 @@ impl TextBuilder {
} }
} }
TextPart::AlbumName => { TextPart::AlbumName => {
if let Some(s) = current_song { if let Some(s) = current_song
if let Some(album) = s.album.and_then(|id| db.albums().get(&id)) { && let Some(album) = s.album.and_then(|id| db.albums().get(&id))
push!(album.name.to_owned()); {
} push!(album.name.to_owned());
} }
} }
TextPart::ArtistName => { TextPart::ArtistName => {
if let Some(s) = current_song { if let Some(s) = current_song
if let Some(artist) = db.artists().get(&s.artist) { && let Some(artist) = db.artists().get(&s.artist)
push!(artist.name.to_owned()); {
} push!(artist.name.to_owned());
} }
} }
TextPart::SongDuration(show_millis) => { TextPart::SongDuration(show_millis) => {
@@ -152,7 +152,7 @@ impl TextBuilder {
} }
TextPart::TagEq(p) => { TextPart::TagEq(p) => {
for (i, g) in all_general(db, &current_song).into_iter().enumerate() { for (i, g) in all_general(db, &current_song).into_iter().enumerate() {
if let Some(_) = g.and_then(|g| g.tags.iter().find(|t| *t == p)) { if g.and_then(|g| g.tags.iter().find(|t| *t == p)).is_some() {
push!( push!(
match i { match i {
0 => 's', 0 => 's',
@@ -223,174 +223,165 @@ impl TextBuilder {
if current.starts_with(' ') { if current.starts_with(' ') {
current = current.replacen(' ', "\u{00A0}", 1); current = current.replacen(' ', "\u{00A0}", 1);
} }
vec.push(TextPart::Literal(std::mem::replace( vec.push(TextPart::Literal(std::mem::take(&mut current)));
&mut current,
String::new(),
)));
} }
}; };
} }
loop { while let Some(ch) = chars.next() {
if let Some(ch) = chars.next() { match ch {
match ch { '\n' => {
'\n' => { done!();
vec.push(TextPart::LineBreak);
}
'\\' => match chars.next() {
None => current.push('\\'),
Some('t') => {
done!(); done!();
vec.push(TextPart::LineBreak); vec.push(TextPart::SongTitle);
} }
'\\' => match chars.next() { Some('a') => {
None => current.push('\\'), done!();
Some('t') => { vec.push(TextPart::AlbumName);
done!(); }
vec.push(TextPart::SongTitle); Some('A') => {
} done!();
Some('a') => { vec.push(TextPart::ArtistName);
done!(); }
vec.push(TextPart::AlbumName); Some('d') => {
} done!();
Some('A') => { vec.push(TextPart::SongDuration(false));
done!(); }
vec.push(TextPart::ArtistName); Some('D') => {
} done!();
Some('d') => { vec.push(TextPart::SongDuration(true));
done!(); }
vec.push(TextPart::SongDuration(false)); Some('s') => {
} done!();
Some('D') => { vec.push(TextPart::SetScale({
done!(); let mut str = String::new();
vec.push(TextPart::SongDuration(true));
}
Some('s') => {
done!();
vec.push(TextPart::SetScale({
let mut str = String::new();
loop {
match chars.next() {
None | Some(';') => break,
Some(c) => str.push(c),
}
}
if let Ok(v) = str.parse() {
v
} else {
return Err(TextBuilderParseError::CouldntParse(
str,
"number (float)".to_string(),
));
}
}))
}
Some('h') => {
done!();
vec.push(TextPart::SetHeightAlign({
let mut str = String::new();
loop {
match chars.next() {
None | Some(';') => break,
Some(c) => str.push(c),
}
}
if let Ok(v) = str.parse() {
v
} else {
return Err(TextBuilderParseError::CouldntParse(
str,
"number (float)".to_string(),
));
}
}))
}
Some('c') => {
done!();
vec.push(TextPart::SetColor({
let mut str = String::new();
for _ in 0..6 {
if let Some(ch) = chars.next() {
str.push(ch);
} else {
return Err(TextBuilderParseError::TooFewCharsForColor);
}
}
if let Ok(i) = u32::from_str_radix(&str, 16) {
Color::from_hex_rgb(i)
} else {
return Err(TextBuilderParseError::ColorNotHex);
}
}));
}
Some('i') => {
done!();
let mut src = String::new();
loop { loop {
match chars.next() { match chars.next() {
None => { None | Some(';') => break,
return Err(TextBuilderParseError::InvalidImageSourceName( Some(c) => str.push(c),
src,
));
}
Some(':') => break,
Some(c) => src.push(c),
} }
} }
vec.push(match src.as_str() { if let Ok(v) = str.parse() {
"Cover" => { v
let mut id = String::new(); } else {
loop { return Err(TextBuilderParseError::CouldntParse(
match chars.next() { str,
None | Some(';') => break, "number (float)".to_string(),
Some(c) => id.push(c), ));
} }
} }))
if let Ok(id) = id.parse() { }
TextPart::ImgCover(id) Some('h') => {
} else {
return Err(TextBuilderParseError::InvalidImageCoverId(id));
}
}
"CustomFile" => TextPart::ImgCustom(Self::from_chars(chars)?),
_ => {
return Err(TextBuilderParseError::InvalidImageSourceName(src));
}
});
}
Some(ch) => current.push(ch),
},
'%' => {
done!(); done!();
let mode = if let Some(ch) = chars.next() { vec.push(TextPart::SetHeightAlign({
ch let mut str = String::new();
} else { loop {
return Err(TextBuilderParseError::UnclosedPercent); match chars.next() {
}; None | Some(';') => break,
Some(c) => str.push(c),
}
}
if let Ok(v) = str.parse() {
v
} else {
return Err(TextBuilderParseError::CouldntParse(
str,
"number (float)".to_string(),
));
}
}))
}
Some('c') => {
done!();
vec.push(TextPart::SetColor({
let mut str = String::new();
for _ in 0..6 {
if let Some(ch) = chars.next() {
str.push(ch);
} else {
return Err(TextBuilderParseError::TooFewCharsForColor);
}
}
if let Ok(i) = u32::from_str_radix(&str, 16) {
Color::from_hex_rgb(i)
} else {
return Err(TextBuilderParseError::ColorNotHex);
}
}));
}
Some('i') => {
done!();
let mut src = String::new();
loop { loop {
match chars.next() { match chars.next() {
Some('%') => { None => {
let s = std::mem::replace(&mut current, String::new()); return Err(TextBuilderParseError::InvalidImageSourceName(src));
vec.push(match mode {
'=' => TextPart::TagEq(s),
'>' => TextPart::TagEnd(s),
'_' => TextPart::TagContains(s),
c => return Err(TextBuilderParseError::TagModeUnknown(c)),
});
break;
} }
Some(ch) => current.push(ch), Some(':') => break,
None => return Err(TextBuilderParseError::UnclosedPercent), Some(c) => src.push(c),
} }
} }
vec.push(match src.as_str() {
"Cover" => {
let mut id = String::new();
loop {
match chars.next() {
None | Some(';') => break,
Some(c) => id.push(c),
}
}
if let Ok(id) = id.parse() {
TextPart::ImgCover(id)
} else {
return Err(TextBuilderParseError::InvalidImageCoverId(id));
}
}
"CustomFile" => TextPart::ImgCustom(Self::from_chars(chars)?),
_ => {
return Err(TextBuilderParseError::InvalidImageSourceName(src));
}
});
} }
'?' => { Some(ch) => current.push(ch),
done!(); },
vec.push(TextPart::If( '%' => {
Self::from_chars(chars)?, done!();
Self::from_chars(chars)?, let mode = if let Some(ch) = chars.next() {
Self::from_chars(chars)?, ch
)); } else {
return Err(TextBuilderParseError::UnclosedPercent);
};
loop {
match chars.next() {
Some('%') => {
let s = std::mem::take(&mut current);
vec.push(match mode {
'=' => TextPart::TagEq(s),
'>' => TextPart::TagEnd(s),
'_' => TextPart::TagContains(s),
c => return Err(TextBuilderParseError::TagModeUnknown(c)),
});
break;
}
Some(ch) => current.push(ch),
None => return Err(TextBuilderParseError::UnclosedPercent),
}
} }
'#' => break,
ch => current.push(ch),
} }
} else { '?' => {
break; done!();
vec.push(TextPart::If(
Self::from_chars(chars)?,
Self::from_chars(chars)?,
Self::from_chars(chars)?,
));
}
'#' => break,
ch => current.push(ch),
} }
} }
done!(); done!();