mirror of
https://github.com/Dummi26/musicdb.git
synced 2026-08-13 09:42:40 +02:00
feat: add right click to songs in queue
This commit is contained in:
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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, ¤t_song).into_iter().enumerate() {
|
for (i, g) in all_general(db, ¤t_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!();
|
||||||
|
|||||||
Reference in New Issue
Block a user