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
|
||||
let npos = adjust_area(&info.pos, &self.config_mut().pos);
|
||||
let ppos = std::mem::replace(&mut info.pos, npos);
|
||||
if info.child_has_keyboard_focus {
|
||||
if self.config().keyboard_focus_index == usize::MAX {
|
||||
info.has_keyboard_focus = true;
|
||||
info.child_has_keyboard_focus = false;
|
||||
}
|
||||
if info.child_has_keyboard_focus && self.config().keyboard_focus_index == usize::MAX {
|
||||
info.has_keyboard_focus = true;
|
||||
info.child_has_keyboard_focus = false;
|
||||
}
|
||||
info.mouse_pos_in_bounds = info.pos.contains(info.mouse_pos);
|
||||
if !info.mouse_pos_in_bounds {
|
||||
@@ -716,12 +714,11 @@ pub(crate) trait GuiElemInternal: GuiElem {
|
||||
) -> Option<Vec<GuiAction>> {
|
||||
if self.config().enabled || allow_deactivated {
|
||||
for c in &mut self.children() {
|
||||
if c.config().enabled {
|
||||
if c.config().pixel_pos.contains(pos) {
|
||||
if let Some(v) = c._mouse_event(e, allow_deactivated, condition, pos) {
|
||||
return Some(v);
|
||||
}
|
||||
}
|
||||
if c.config().enabled
|
||||
&& c.config().pixel_pos.contains(pos)
|
||||
&& let Some(v) = c._mouse_event(e, allow_deactivated, condition, pos)
|
||||
{
|
||||
return Some(v);
|
||||
}
|
||||
}
|
||||
condition(self.elem_mut(), e)
|
||||
@@ -739,10 +736,10 @@ pub(crate) trait GuiElemInternal: GuiElem {
|
||||
e,
|
||||
false,
|
||||
&mut |v, e| {
|
||||
if v.config().drag_target {
|
||||
if let Some(d) = dragged.take() {
|
||||
return Some(v.dragged(e, d));
|
||||
}
|
||||
if v.config().drag_target
|
||||
&& let Some(d) = dragged.take()
|
||||
{
|
||||
return Some(v.dragged(e, d));
|
||||
}
|
||||
None
|
||||
},
|
||||
@@ -887,7 +884,7 @@ pub(crate) trait GuiElemInternal: GuiElem {
|
||||
}
|
||||
}
|
||||
fn _keyboard_move_focus(&mut self, decrement: bool, refocus: bool) -> bool {
|
||||
if self.config().enabled == false {
|
||||
if !self.config().enabled {
|
||||
return false;
|
||||
}
|
||||
let mut focus_index = if refocus {
|
||||
@@ -1133,7 +1130,8 @@ pub struct GuiElemCfg {
|
||||
pub enabled: bool,
|
||||
/// if true, indicates that something (text size, screen size, ...) has changed
|
||||
/// and you should probably relayout and redraw from scratch.
|
||||
pub redraw: bool,
|
||||
redraw: bool,
|
||||
redraw2: bool,
|
||||
/// will be set to false after `draw`.
|
||||
/// can be used to, for example, add the keybinds for your element.
|
||||
pub init: bool,
|
||||
@@ -1193,9 +1191,27 @@ impl GuiElemCfg {
|
||||
self.drag_target = true;
|
||||
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
|
||||
}
|
||||
/// 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 {
|
||||
self.enabled = false;
|
||||
@@ -1207,6 +1223,7 @@ impl Default for GuiElemCfg {
|
||||
Self {
|
||||
enabled: true,
|
||||
redraw: false,
|
||||
redraw2: false,
|
||||
init: true,
|
||||
pos: Rectangle::new(Vec2::ZERO, Vec2::new(1.0, 1.0)),
|
||||
pixel_pos: Rectangle::ZERO,
|
||||
@@ -1270,6 +1287,7 @@ pub enum Dragging {
|
||||
Queue(Result<Queue, Vec<usize>>),
|
||||
Queues(Vec<Queue>),
|
||||
}
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
pub enum SpecificGuiElem {
|
||||
SearchArtist,
|
||||
SearchAlbum,
|
||||
@@ -1322,7 +1340,7 @@ impl Gui {
|
||||
pub fn exec_gui_action(&mut self, action: GuiAction) {
|
||||
match action {
|
||||
GuiAction::Build(f) => {
|
||||
let actions = f(&mut *self.database.lock().unwrap());
|
||||
let actions = f(&mut self.database.lock().unwrap());
|
||||
for action in actions {
|
||||
self.exec_gui_action(action);
|
||||
}
|
||||
@@ -1478,7 +1496,7 @@ impl WindowHandler<GuiEvent> for Gui {
|
||||
time: draw_start_time,
|
||||
actions: Vec::with_capacity(0),
|
||||
pos: Rectangle::new(Vec2::ZERO, self.size.into_f32()),
|
||||
database: &mut *dblock,
|
||||
database: &mut dblock,
|
||||
font: &self.font,
|
||||
mouse_pos: self.mouse_pos,
|
||||
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) {
|
||||
if let Some(a) =
|
||||
self.gui
|
||||
._mouse_button(&mut EventInfo::new(), button, true, self.mouse_pos.clone())
|
||||
if let Some(a) = self
|
||||
.gui
|
||||
._mouse_button(&mut EventInfo::new(), button, true, self.mouse_pos)
|
||||
{
|
||||
for a in a {
|
||||
self.exec_gui_action(a)
|
||||
@@ -1586,9 +1604,9 @@ impl WindowHandler<GuiEvent> for Gui {
|
||||
if self.dragging.is_some() {
|
||||
let (dr, _) = self.dragging.take().unwrap();
|
||||
let mut opt = Some(dr);
|
||||
if let Some(a) =
|
||||
self.gui
|
||||
._release_drag(&mut EventInfo::new(), &mut opt, self.mouse_pos.clone())
|
||||
if let Some(a) = self
|
||||
.gui
|
||||
._release_drag(&mut EventInfo::new(), &mut opt, self.mouse_pos)
|
||||
{
|
||||
for a in a {
|
||||
self.exec_gui_action(a)
|
||||
@@ -1609,7 +1627,7 @@ impl WindowHandler<GuiEvent> for Gui {
|
||||
}
|
||||
if let Some(a) =
|
||||
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 {
|
||||
self.exec_gui_action(a)
|
||||
@@ -1639,7 +1657,7 @@ impl WindowHandler<GuiEvent> for Gui {
|
||||
};
|
||||
if let Some(a) = self
|
||||
.gui
|
||||
._mouse_wheel(&mut EventInfo::new(), dist, self.mouse_pos.clone())
|
||||
._mouse_wheel(&mut EventInfo::new(), dist, self.mouse_pos)
|
||||
{
|
||||
for a in a {
|
||||
self.exec_gui_action(a)
|
||||
@@ -1673,10 +1691,10 @@ impl WindowHandler<GuiEvent> for Gui {
|
||||
scancode: KeyScancode,
|
||||
) {
|
||||
helper.request_redraw();
|
||||
if let Some(VirtualKeyCode::Tab) = virtual_key_code {
|
||||
if !(self.modifiers.ctrl() || self.modifiers.alt() || self.modifiers.logo()) {
|
||||
self.gui._keyboard_move_focus(self.modifiers.shift(), false);
|
||||
}
|
||||
if let Some(VirtualKeyCode::Tab) = virtual_key_code
|
||||
&& !(self.modifiers.ctrl() || self.modifiers.alt() || self.modifiers.logo())
|
||||
{
|
||||
self.gui._keyboard_move_focus(self.modifiers.shift(), false);
|
||||
}
|
||||
for a in self.gui._keyboard_event(
|
||||
&mut EventInfo::new(),
|
||||
@@ -1717,18 +1735,19 @@ impl WindowHandler<GuiEvent> for Gui {
|
||||
// handle keybinds unless settings are open, opening or closing
|
||||
let mut e = EventInfo::new();
|
||||
let mut post_action = None;
|
||||
if self.gui.settings.0 == false && self.gui.settings.1.is_none() {
|
||||
if let Some(key) = virtual_key_code {
|
||||
let keybind = KeyBinding::new(&self.modifiers, key);
|
||||
if let Some(action) = self.keybinds.get(&keybind) {
|
||||
if action.has_priority() {
|
||||
e.take();
|
||||
for a in self.key_actions.get(&action.id()).execute() {
|
||||
self.exec_gui_action(a);
|
||||
}
|
||||
} else {
|
||||
post_action = Some(action.id());
|
||||
if !self.gui.settings.0
|
||||
&& self.gui.settings.1.is_none()
|
||||
&& let Some(key) = virtual_key_code
|
||||
{
|
||||
let keybind = KeyBinding::new(&self.modifiers, key);
|
||||
if let Some(action) = self.keybinds.get(&keybind) {
|
||||
if action.has_priority() {
|
||||
e.take();
|
||||
for a in self.key_actions.get(&action.id()).execute() {
|
||||
self.exec_gui_action(a);
|
||||
}
|
||||
} else {
|
||||
post_action = Some(action.id());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1760,11 +1779,11 @@ impl WindowHandler<GuiEvent> for Gui {
|
||||
) {
|
||||
self.exec_gui_action(a);
|
||||
}
|
||||
if let Some(post_action) = post_action.take() {
|
||||
if e.take() {
|
||||
for a in self.key_actions.get(&post_action).execute() {
|
||||
self.exec_gui_action(a);
|
||||
}
|
||||
if let Some(post_action) = post_action.take()
|
||||
&& e.take()
|
||||
{
|
||||
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)]
|
||||
impl<T: GuiElem> Square<T> {
|
||||
pub fn new(mut config: GuiElemCfg, inner: T) -> Self {
|
||||
config.redraw = true;
|
||||
config.redraw_once();
|
||||
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) {
|
||||
if info.pos.size() != self.config.pixel_pos.size() {
|
||||
self.config.redraw = true;
|
||||
self.config.redraw_once();
|
||||
}
|
||||
if self.config.redraw {
|
||||
self.config.redraw = false;
|
||||
if self.config.redraw() {
|
||||
self.config.redrawn();
|
||||
if info.pos.width() > info.pos.height() {
|
||||
let w = 0.5 * info.pos.height() / info.pos.width();
|
||||
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) {
|
||||
if self.config.pixel_pos.size() != info.pos.size() {
|
||||
self.config.redraw = true;
|
||||
self.config.redraw_once();
|
||||
}
|
||||
// smooth scrolling animation
|
||||
if self.scroll_target > self.max_scroll {
|
||||
@@ -202,7 +202,7 @@ impl<C: GuiElemChildren + 'static> GuiElem for ScrollBox<C> {
|
||||
self.scroll_target = 0.0;
|
||||
}
|
||||
if self.scroll_target != self.scroll_display {
|
||||
self.config.redraw = true;
|
||||
self.config.redraw_once();
|
||||
if info.high_performance {
|
||||
self.scroll_display = self.scroll_target;
|
||||
} else {
|
||||
@@ -215,7 +215,7 @@ impl<C: GuiElemChildren + 'static> GuiElem for ScrollBox<C> {
|
||||
}
|
||||
}
|
||||
// recalculate positions
|
||||
if self.config.redraw {
|
||||
if self.config.redraw() {
|
||||
// adjust height vector length if necessary
|
||||
if self.children_heights.len() != 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;
|
||||
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;
|
||||
for (e, h) in self.children.iter().zip(self.children_heights.iter()) {
|
||||
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> {
|
||||
let nst = (self.scroll_target - self.size_unit.from_abs(diff as f32, self.last_height_px))
|
||||
.max(0.0);
|
||||
let nst =
|
||||
(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
|
||||
if nst != self.scroll_target && e.take() {
|
||||
self.scroll_target = nst;
|
||||
@@ -323,12 +323,14 @@ impl ScrollBoxSizeUnit {
|
||||
Self::Pixels => val / draw_height,
|
||||
}
|
||||
}
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
fn from_rel(&self, val: f32, draw_height: f32) -> f32 {
|
||||
match self {
|
||||
Self::Relative => val,
|
||||
Self::Pixels => val * draw_height,
|
||||
}
|
||||
}
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
fn from_abs(&self, val: f32, draw_height: f32) -> f32 {
|
||||
match self {
|
||||
Self::Relative => val / draw_height,
|
||||
@@ -545,21 +547,13 @@ impl Slider {
|
||||
{
|
||||
if since >= 1.0 {
|
||||
s.display_since = None;
|
||||
if s.display {
|
||||
1.0
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
if s.display { 1.0 } else { 0.0 }
|
||||
} else {
|
||||
if let Some(h) = &i.helper {
|
||||
h.request_redraw();
|
||||
}
|
||||
s.config.redraw = true;
|
||||
if s.display {
|
||||
since
|
||||
} else {
|
||||
1.0 - since
|
||||
}
|
||||
s.config.redraw_once();
|
||||
if s.display { since } else { 1.0 - since }
|
||||
}
|
||||
} else {
|
||||
1.0
|
||||
@@ -613,7 +607,7 @@ impl GuiElem for Slider {
|
||||
if self.display != (self.config.mouse_down.0 || info.pos.contains(info.mouse_pos)) {
|
||||
self.display = !self.display;
|
||||
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 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,
|
||||
));
|
||||
self.val_changed = true;
|
||||
for v in &mut self.val_changed_subs {
|
||||
*v = true;
|
||||
}
|
||||
self.config.redraw = true;
|
||||
self.val_changed_subs.fill(true);
|
||||
self.config.redraw_once();
|
||||
}
|
||||
let line_color = Color::from_int_rgb(50, 50, 100);
|
||||
g.draw_circle(
|
||||
@@ -660,8 +652,8 @@ impl GuiElem for Slider {
|
||||
0.5 * dot_size,
|
||||
Color::CYAN,
|
||||
);
|
||||
if self.config.redraw {
|
||||
self.config.redraw = false;
|
||||
if self.config.redraw() {
|
||||
self.config.redrawn();
|
||||
(Arc::clone(&self.on_update))(self, info);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
use std::{collections::BTreeSet, time::Instant};
|
||||
|
||||
use speedy2d::{
|
||||
Graphics2D,
|
||||
color::Color,
|
||||
dimen::{Vec2, Vector2},
|
||||
shape::Rectangle,
|
||||
Graphics2D,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
gui::{DrawInfo, GuiElem, GuiElemCfg},
|
||||
gui_anim::AnimationController,
|
||||
gui_base::{Button, ScrollBox},
|
||||
gui_base::{Button, Panel, ScrollBox},
|
||||
gui_text::{Label, TextField},
|
||||
};
|
||||
|
||||
@@ -24,36 +24,62 @@ pub enum Event {
|
||||
pub struct EditorForAnyTagInList {
|
||||
config: GuiElemCfg,
|
||||
pub tag: String,
|
||||
label: Label,
|
||||
rm_button: Button<[IconDelete; 1]>,
|
||||
panel: Panel<(Label, Button<[IconDelete; 1]>)>,
|
||||
}
|
||||
|
||||
impl EditorForAnyTagInList {
|
||||
pub fn new<T: From<Event> + 'static>(
|
||||
tag: String,
|
||||
index: usize,
|
||||
sender: std::sync::mpsc::Sender<T>,
|
||||
config: GuiElemCfg,
|
||||
) -> Self {
|
||||
Self {
|
||||
config,
|
||||
tag: tag.clone(),
|
||||
label: Label::new(
|
||||
GuiElemCfg::default(),
|
||||
tag.clone(),
|
||||
Color::WHITE,
|
||||
None,
|
||||
Vector2::new(0.0, 0.5),
|
||||
),
|
||||
rm_button: Button::new(
|
||||
GuiElemCfg::default(),
|
||||
let label = Label::new(
|
||||
GuiElemCfg::default(),
|
||||
tag.clone(),
|
||||
Color::WHITE,
|
||||
None,
|
||||
Vector2::new(0.0, 0.5),
|
||||
);
|
||||
let rm_button = Button::new(
|
||||
GuiElemCfg::default(),
|
||||
{
|
||||
let tag = tag.clone();
|
||||
move |btn| {
|
||||
btn.disable();
|
||||
sender.send(Event::RemoveTag(tag.clone()).into()).unwrap();
|
||||
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 label_padding = info.pos.height() * 0.05;
|
||||
let x_split = (info.pos.width() - rm_button_size) / info.pos.width();
|
||||
self.rm_button.config_mut().pos = Rectangle::from_tuples(
|
||||
self.rm_button_mut().config_mut().pos = Rectangle::from_tuples(
|
||||
(x_split, rm_button_padding / info.pos.height()),
|
||||
(1.0, 1.0 - rm_button_padding / info.pos.height()),
|
||||
);
|
||||
self.label.config_mut().pos = Rectangle::from_tuples(
|
||||
self.label_mut().config_mut().pos = Rectangle::from_tuples(
|
||||
(0.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
|
||||
}
|
||||
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 {
|
||||
self
|
||||
@@ -162,7 +188,7 @@ impl<T: From<Event> + 'static> EditorForAnyTagAdder<T> {
|
||||
expand_to,
|
||||
c_value: TextField::new(
|
||||
GuiElemCfg::default(),
|
||||
"artist".to_owned(),
|
||||
"tag".to_owned(),
|
||||
Color::DARK_GRAY,
|
||||
Color::WHITE,
|
||||
),
|
||||
@@ -180,7 +206,7 @@ impl<T: From<Event> + 'static> EditorForAnyTagAdder<T> {
|
||||
self.last_search = "\n".to_owned();
|
||||
self.c_value.c_input.content.text().clear();
|
||||
self.open_prog.set_target(now, 1.0);
|
||||
self.config_mut().redraw = true;
|
||||
self.config_mut().redraw_once();
|
||||
}
|
||||
}
|
||||
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_changed = &self.last_search != &search;
|
||||
if self.config.redraw || search_changed {
|
||||
let search_changed = self.last_search != search;
|
||||
if self.config.redraw() || search_changed {
|
||||
*self.c_value.c_input.content.color() = Color::WHITE;
|
||||
if search_changed {
|
||||
if search.is_empty() {
|
||||
@@ -224,7 +250,7 @@ impl<T: From<Event> + 'static> GuiElem for EditorForAnyTagAdder<T> {
|
||||
.flat_map(|s| s.general.tags.iter()),
|
||||
)
|
||||
.filter(|tag| tag.to_lowercase().contains(&search))
|
||||
.map(|tag| tag.clone())
|
||||
.cloned()
|
||||
.collect::<BTreeSet<_>>();
|
||||
if !tags.contains(self.c_value.c_input.content.get_text()) {
|
||||
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();
|
||||
self.c_picker.config_mut().redraw = true;
|
||||
self.c_picker.config_mut().redraw_once();
|
||||
self.last_search = search;
|
||||
self.config.redrawn();
|
||||
}
|
||||
}
|
||||
fn config(&self) -> &GuiElemCfg {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use musicdb_lib::{
|
||||
data::{song::Song, ArtistId},
|
||||
data::{ArtistId, song::Song},
|
||||
server::{Action, Req},
|
||||
};
|
||||
use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle};
|
||||
@@ -11,7 +11,7 @@ use crate::{
|
||||
gui::{GuiAction, GuiElem, GuiElemCfg, GuiElemChildren},
|
||||
gui_anim::AnimationController,
|
||||
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},
|
||||
};
|
||||
|
||||
@@ -28,6 +28,7 @@ pub struct EditorForSongs {
|
||||
event_sender: std::sync::mpsc::Sender<Event>,
|
||||
event_recv: std::sync::mpsc::Receiver<Event>,
|
||||
}
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
pub enum Event {
|
||||
Close,
|
||||
Apply,
|
||||
@@ -102,7 +103,7 @@ impl EditorForSongs {
|
||||
c_artist: EditorForSongArtistChooser::new(sender.clone()),
|
||||
c_album: Label::new(
|
||||
GuiElemCfg::default(),
|
||||
format!("(todo...)"),
|
||||
"(todo...)".to_owned(),
|
||||
Color::GRAY,
|
||||
None,
|
||||
Vec2::new(0.0, 0.5),
|
||||
@@ -117,9 +118,11 @@ impl EditorForSongs {
|
||||
}
|
||||
}
|
||||
tags.into_iter()
|
||||
.map(|tag| {
|
||||
.enumerate()
|
||||
.map(|(i, tag)| {
|
||||
EditorForAnyTagInList::new(
|
||||
tag.to_owned(),
|
||||
i,
|
||||
sender.clone(),
|
||||
GuiElemCfg::default(),
|
||||
)
|
||||
@@ -198,110 +201,112 @@ impl GuiElem for EditorForSongs {
|
||||
)
|
||||
}
|
||||
fn draw(&mut self, info: &mut crate::gui::DrawInfo, g: &mut speedy2d::Graphics2D) {
|
||||
loop {
|
||||
match self.event_recv.try_recv() {
|
||||
Ok(e) => match e {
|
||||
Event::Close => info.actions.push(GuiAction::Do(Box::new(|gui| {
|
||||
gui.gui.c_editing_songs = None;
|
||||
gui.gui.set_normal_ui_enabled(true);
|
||||
}))),
|
||||
Event::Apply => {
|
||||
let mut actions = Vec::new();
|
||||
for song in self.songs.iter() {
|
||||
let mut song = song.clone();
|
||||
while let Ok(e) = self.event_recv.try_recv() {
|
||||
match e {
|
||||
Event::Close => info.actions.push(GuiAction::Do(Box::new(|gui| {
|
||||
gui.gui.c_editing_songs = None;
|
||||
gui.gui.set_normal_ui_enabled(true);
|
||||
}))),
|
||||
Event::Apply => {
|
||||
let mut actions = Vec::new();
|
||||
for song in self.songs.iter() {
|
||||
let mut song = song.clone();
|
||||
|
||||
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
|
||||
let new_title = self
|
||||
.c_scrollbox
|
||||
.children
|
||||
.c_artist
|
||||
.c_name
|
||||
.c_title
|
||||
.c_input
|
||||
.content
|
||||
.text() = name;
|
||||
self.c_scrollbox.children.c_artist.config_mut().redraw = true;
|
||||
.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()));
|
||||
}
|
||||
Event::GeneralEvent(e) => {
|
||||
use super::gui_edit_any::Event as GeneralEvent;
|
||||
match e {
|
||||
GeneralEvent::RemoveTag(tag) => {
|
||||
for song in self.songs.iter_mut() {
|
||||
if let Some(i) =
|
||||
song.general.tags.iter().position(|t| *t == tag)
|
||||
{
|
||||
song.general.tags.remove(i);
|
||||
}
|
||||
}
|
||||
if let Some(i) = (&self.c_scrollbox.children.c_tags)
|
||||
.into_iter()
|
||||
.position(|e| e.tag == tag)
|
||||
{
|
||||
self.c_scrollbox.children.c_tags.remove(i);
|
||||
self.c_scrollbox.config_mut().redraw = true;
|
||||
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
|
||||
.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) => {
|
||||
self.c_scrollbox.children.c_new_tag.clear(info.time);
|
||||
for song in self.songs.iter_mut() {
|
||||
if !song.general.tags.contains(&tag) {
|
||||
song.general.tags.push(tag.clone());
|
||||
}
|
||||
}
|
||||
if !(&self.c_scrollbox.children.c_tags)
|
||||
.into_iter()
|
||||
.any(|e| e.tag == tag)
|
||||
{
|
||||
self.c_scrollbox.children_heights.insert(
|
||||
3 + self.c_scrollbox.children.c_tags.len(),
|
||||
ELEM_HEIGHT,
|
||||
);
|
||||
self.c_scrollbox.children.c_tags.push(
|
||||
EditorForAnyTagInList::new(
|
||||
tag,
|
||||
self.event_sender.clone(),
|
||||
GuiElemCfg::default(),
|
||||
),
|
||||
);
|
||||
self.c_scrollbox.config_mut().redraw = true;
|
||||
if let Some(i) = (&self.c_scrollbox.children.c_tags)
|
||||
.into_iter()
|
||||
.position(|e| e.tag == tag)
|
||||
{
|
||||
self.c_scrollbox.children.c_tags.remove(i);
|
||||
self.c_scrollbox.config_mut().redraw_once();
|
||||
}
|
||||
}
|
||||
GeneralEvent::AddTag(tag) => {
|
||||
self.c_scrollbox.children.c_new_tag.clear(info.time);
|
||||
for song in self.songs.iter_mut() {
|
||||
if !song.general.tags.contains(&tag) {
|
||||
song.general.tags.push(tag.clone());
|
||||
}
|
||||
}
|
||||
if !(&self.c_scrollbox.children.c_tags)
|
||||
.into_iter()
|
||||
.any(|e| e.tag == tag)
|
||||
{
|
||||
self.c_scrollbox.children_heights.insert(
|
||||
3 + self.c_scrollbox.children.c_tags.len(),
|
||||
ELEM_HEIGHT,
|
||||
);
|
||||
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
|
||||
@@ -332,7 +337,7 @@ impl GuiElem for EditorForSongs {
|
||||
{
|
||||
if let Some(v) = self.c_scrollbox.children_heights.get_mut(1) {
|
||||
*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 {
|
||||
h.request_redraw();
|
||||
@@ -351,7 +356,7 @@ impl GuiElem for EditorForSongs {
|
||||
.get_mut(3 + self.c_scrollbox.children.c_tags.len())
|
||||
{
|
||||
*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 {
|
||||
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_changed = &self.last_search != &search;
|
||||
if self.config.redraw || search_changed {
|
||||
let search_changed = self.last_search != search;
|
||||
if self.config.redraw() || search_changed {
|
||||
*self.c_name.c_input.content.color() = if self.chosen_id.is_some() {
|
||||
Color::GREEN
|
||||
} else {
|
||||
@@ -480,8 +485,9 @@ impl GuiElem for EditorForSongArtistChooser {
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
self.c_picker.config_mut().redraw = true;
|
||||
self.c_picker.config_mut().redraw_once();
|
||||
self.last_search = search;
|
||||
self.config.redrawn();
|
||||
}
|
||||
}
|
||||
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 {
|
||||
info.gui_config
|
||||
.idle_top_text
|
||||
.gen_new(&info.database, info.database.get_song(&song))
|
||||
.gen_new(info.database, info.database.get_song(&song))
|
||||
} else {
|
||||
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 {
|
||||
info.gui_config
|
||||
.idle_side1_text
|
||||
.gen_new(&info.database, info.database.get_song(&song))
|
||||
.gen_new(info.database, info.database.get_song(&song))
|
||||
} else {
|
||||
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 {
|
||||
info.gui_config
|
||||
.idle_side2_text
|
||||
.gen_new(&info.database, info.database.get_song(&song))
|
||||
.gen_new(info.database, info.database.get_song(&song))
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
self.c_side2_label.config_mut().redraw = true;
|
||||
self.c_side2_label.config_mut().redraw_once();
|
||||
// check artist
|
||||
if let Some(artist_id) = self
|
||||
.current_info
|
||||
@@ -165,8 +165,8 @@ impl GuiElem for IdleDisplay {
|
||||
self.artist_image_aspect_ratio.set_target(info.time, 0.0);
|
||||
if let Some(artist) = info.database.artists().get(&artist_id) {
|
||||
for tag in &artist.general.tags {
|
||||
if tag.starts_with("ImageExt=") {
|
||||
let filename = format!("{}.{}", artist.name, &tag[9..]);
|
||||
if let Some(tag) = tag.strip_prefix("ImageExt=") {
|
||||
let filename = format!("{}.{}", artist.name, tag);
|
||||
self.current_artist_image =
|
||||
Some((artist_id, Some((filename.clone(), None))));
|
||||
if !info.custom_images.contains_key(&filename) {
|
||||
@@ -199,15 +199,14 @@ impl GuiElem for IdleDisplay {
|
||||
Some((_, None)) | Some((_, Some(Some(_)))) => {}
|
||||
}
|
||||
}
|
||||
if let Some((_, Some((img, h)))) = &mut self.current_artist_image {
|
||||
if h.is_none() {
|
||||
if let Some(img) = info.custom_images.get_mut(img) {
|
||||
if let Some(img) = img.get_init(g) {
|
||||
*h = Some(Some(img));
|
||||
} else if img.is_err() {
|
||||
*h = Some(None);
|
||||
}
|
||||
}
|
||||
if let Some((_, Some((img, h)))) = &mut self.current_artist_image
|
||||
&& h.is_none()
|
||||
&& let Some(img) = info.custom_images.get_mut(img)
|
||||
{
|
||||
if let Some(img) = img.get_init(g) {
|
||||
*h = Some(Some(img));
|
||||
} else if img.is_err() {
|
||||
*h = Some(None);
|
||||
}
|
||||
}
|
||||
// draw cover
|
||||
|
||||
@@ -3,18 +3,19 @@ use std::{
|
||||
collections::HashSet,
|
||||
sync::Arc,
|
||||
sync::{
|
||||
Mutex,
|
||||
atomic::{AtomicBool, AtomicUsize},
|
||||
mpsc, Mutex,
|
||||
mpsc,
|
||||
},
|
||||
};
|
||||
|
||||
use musicdb_lib::data::{
|
||||
AlbumId, ArtistId, GeneralData, SongId,
|
||||
album::Album,
|
||||
artist::Artist,
|
||||
database::Database,
|
||||
queue::{Queue, QueueContent},
|
||||
song::Song,
|
||||
AlbumId, ArtistId, GeneralData, SongId,
|
||||
};
|
||||
use regex::{Regex, RegexBuilder};
|
||||
use speedy2d::{
|
||||
@@ -296,12 +297,8 @@ impl GuiElem for LibraryBrowser {
|
||||
false
|
||||
}
|
||||
fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) {
|
||||
loop {
|
||||
if let Ok(action) = self.do_something_receiver.try_recv() {
|
||||
action(self);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
while let Ok(action) = self.do_something_receiver.try_recv() {
|
||||
action(self);
|
||||
}
|
||||
// search
|
||||
let mut search_changed = false;
|
||||
@@ -396,7 +393,7 @@ impl GuiElem for LibraryBrowser {
|
||||
// -
|
||||
if self.library_updated {
|
||||
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;
|
||||
}
|
||||
if search_changed {
|
||||
@@ -404,7 +401,7 @@ impl GuiElem for LibraryBrowser {
|
||||
s: &LibraryBrowser,
|
||||
pat: &str,
|
||||
regex: &Option<Regex>,
|
||||
search_text: &String,
|
||||
search_text: &str,
|
||||
filter: &Filter,
|
||||
search_gd: &GeneralData,
|
||||
) -> f32 {
|
||||
@@ -414,7 +411,7 @@ impl GuiElem for LibraryBrowser {
|
||||
if let Some(r) = regex {
|
||||
if s.search_prefers_start_matches {
|
||||
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)
|
||||
None if m.end() == pat.len() => 6.0,
|
||||
// found at start of h
|
||||
@@ -433,11 +430,7 @@ impl GuiElem for LibraryBrowser {
|
||||
})
|
||||
.fold(0.0, f32::max)
|
||||
} else {
|
||||
if r.is_match(pat) {
|
||||
2.0
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
if r.is_match(pat) { 2.0 } else { 0.0 }
|
||||
}
|
||||
} else if search_text.is_empty() {
|
||||
1.0
|
||||
@@ -448,7 +441,7 @@ impl GuiElem for LibraryBrowser {
|
||||
let allow_singles = self.search_album.is_empty()
|
||||
&& self.filter_albums.lock().unwrap().filters.is_empty();
|
||||
self.filter_local_library(
|
||||
&info.database,
|
||||
info.database,
|
||||
|s, artist| {
|
||||
filter(
|
||||
s,
|
||||
@@ -496,35 +489,35 @@ impl GuiElem for LibraryBrowser {
|
||||
self.selected_popup_state.1 = artists;
|
||||
self.selected_popup_state.2 = albums;
|
||||
self.selected_popup_state.3 = songs;
|
||||
if artists > 0 || albums > 0 || songs > 0 {
|
||||
if let Some(text) = match (artists, albums, songs) {
|
||||
if (artists > 0 || albums > 0 || songs > 0)
|
||||
&& let Some(text) = match (artists, albums, songs) {
|
||||
(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, 1, 0) => Some(format!("1 album selected")),
|
||||
(0, 1, 0) => Some("1 album selected".to_owned()),
|
||||
(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")),
|
||||
|
||||
(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, al, 1) => Some(format!("1 song 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")),
|
||||
(ar, 0, 1) => Some(format!("1 song 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")),
|
||||
(ar, 1, 0) => Some(format!("1 album 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, al, 1) => {
|
||||
Some(format!("1 song, {al} albums and 1 artist selected"))
|
||||
@@ -544,14 +537,13 @@ impl GuiElem for LibraryBrowser {
|
||||
(ar, al, s) => {
|
||||
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
|
||||
{
|
||||
@@ -571,7 +563,7 @@ impl GuiElem for LibraryBrowser {
|
||||
} else {
|
||||
if self.selected_popup_state.0 != 0.0 {
|
||||
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 {
|
||||
self.selected_popup_state.0 = 0.0;
|
||||
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() {
|
||||
self.config.redraw = false;
|
||||
self.update_ui(&info.database, info.line_height);
|
||||
if self.config.redraw() || info.pos.size() != self.config.pixel_pos.size() {
|
||||
self.config.redrawn();
|
||||
self.update_ui(info.database, info.line_height);
|
||||
}
|
||||
}
|
||||
fn updated_library(&mut self) {
|
||||
@@ -631,13 +623,13 @@ impl LibraryBrowser {
|
||||
self.library_sorted = artists
|
||||
.into_iter()
|
||||
.map(|(ar_id, artist)| {
|
||||
let singles = artist.singles.iter().map(|id| *id).collect();
|
||||
let singles = artist.singles.clone();
|
||||
let albums = artist
|
||||
.albums
|
||||
.iter()
|
||||
.map(|id| {
|
||||
let songs = if let Some(album) = db.albums().get(id) {
|
||||
album.songs.iter().map(|id| *id).collect()
|
||||
album.songs.clone()
|
||||
} else {
|
||||
eprintln!("[warn] No album with id {id} found in db!");
|
||||
vec![]
|
||||
@@ -762,7 +754,7 @@ impl LibraryBrowser {
|
||||
let library_scroll_box = &mut self.c_scroll_box;
|
||||
library_scroll_box.children = elems;
|
||||
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) {
|
||||
(
|
||||
@@ -878,7 +870,7 @@ impl ListArtist {
|
||||
None,
|
||||
Vec2::new(0.0, 0.5),
|
||||
);
|
||||
config.redraw = true;
|
||||
config.redraw_once();
|
||||
Self {
|
||||
config: config.w_mouse(),
|
||||
id,
|
||||
@@ -913,8 +905,8 @@ impl GuiElem for ListArtist {
|
||||
self
|
||||
}
|
||||
fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) {
|
||||
if self.config.redraw {
|
||||
self.config.redraw = false;
|
||||
if self.config.redraw() {
|
||||
self.config.redrawn();
|
||||
let sel = self.selected.contains_artist(&self.id);
|
||||
if sel != self.sel {
|
||||
self.sel = sel;
|
||||
@@ -972,7 +964,7 @@ impl GuiElem for ListArtist {
|
||||
fn mouse_up(&mut self, e: &mut EventInfo, button: MouseButton) -> Vec<GuiAction> {
|
||||
if self.mouse && button == MouseButton::Left {
|
||||
self.mouse = false;
|
||||
self.config.redraw = true;
|
||||
self.config.redraw_once();
|
||||
if e.take() {
|
||||
if !self.sel {
|
||||
self.selected.insert_artist(self.id);
|
||||
@@ -1024,7 +1016,7 @@ impl ListAlbum {
|
||||
),
|
||||
]],
|
||||
);
|
||||
config.redraw = true;
|
||||
config.redraw_once();
|
||||
Self {
|
||||
config: config.w_mouse(),
|
||||
id,
|
||||
@@ -1059,8 +1051,8 @@ impl GuiElem for ListAlbum {
|
||||
self
|
||||
}
|
||||
fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) {
|
||||
if self.config.redraw {
|
||||
self.config.redraw = false;
|
||||
if self.config.redraw() {
|
||||
self.config.redrawn();
|
||||
let sel = self.selected.contains_album(&self.id);
|
||||
if sel != self.sel {
|
||||
self.sel = sel;
|
||||
@@ -1118,7 +1110,7 @@ impl GuiElem for ListAlbum {
|
||||
fn mouse_up(&mut self, e: &mut EventInfo, button: MouseButton) -> Vec<GuiAction> {
|
||||
if self.mouse && button == MouseButton::Left {
|
||||
self.mouse = false;
|
||||
self.config.redraw = true;
|
||||
self.config.redraw_once();
|
||||
if e.take() {
|
||||
if !self.sel {
|
||||
self.selected.insert_album(self.id);
|
||||
@@ -1167,7 +1159,7 @@ impl ListSong {
|
||||
),
|
||||
]],
|
||||
);
|
||||
config.redraw = true;
|
||||
config.redraw_once();
|
||||
Self {
|
||||
config: config.w_mouse(),
|
||||
id,
|
||||
@@ -1202,8 +1194,8 @@ impl GuiElem for ListSong {
|
||||
self
|
||||
}
|
||||
fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) {
|
||||
if self.config.redraw {
|
||||
self.config.redraw = false;
|
||||
if self.config.redraw() {
|
||||
self.config.redrawn();
|
||||
let sel = self.selected.contains_song(&self.id);
|
||||
if sel != self.sel {
|
||||
self.sel = sel;
|
||||
@@ -1261,7 +1253,7 @@ impl GuiElem for ListSong {
|
||||
fn mouse_up(&mut self, e: &mut EventInfo, button: MouseButton) -> Vec<GuiAction> {
|
||||
if self.mouse && button == MouseButton::Left {
|
||||
self.mouse = false;
|
||||
self.config.redraw = true;
|
||||
self.config.redraw_once();
|
||||
if e.take() {
|
||||
if !self.sel {
|
||||
self.selected.insert_song(self.id);
|
||||
@@ -1288,7 +1280,7 @@ impl GuiElem for ListSong {
|
||||
},
|
||||
[Label::new(
|
||||
GuiElemCfg::default(),
|
||||
format!("Edit this song"),
|
||||
"Edit this song".to_owned(),
|
||||
Color::WHITE,
|
||||
None,
|
||||
Vec2::new_y(0.5),
|
||||
@@ -1315,7 +1307,7 @@ impl GuiElem for ListSong {
|
||||
},
|
||||
[Label::new(
|
||||
GuiElemCfg::default(),
|
||||
format!("Edit selected songs"),
|
||||
"Edit selected songs".to_owned(),
|
||||
Color::WHITE,
|
||||
None,
|
||||
Vec2::new_y(0.5),
|
||||
@@ -1354,6 +1346,7 @@ struct FilterTab {
|
||||
buttons: Vec<Button<[Label; 1]>>,
|
||||
filters: Vec<FilterLine>,
|
||||
}
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
enum FilterLine {
|
||||
Joiner(Button<[Label; 1]>),
|
||||
Not(Label),
|
||||
@@ -1394,10 +1387,10 @@ impl GuiElemChildren for FilterTab {
|
||||
self.buttons.len() + self.filters.len()
|
||||
}
|
||||
}
|
||||
const FP_CASESENS_N: &'static str = "search is case-insensitive";
|
||||
const FP_CASESENS_Y: &'static str = "search is case-sensitive!";
|
||||
const FP_PREFSTART_N: &'static str = "simple search";
|
||||
const FP_PREFSTART_Y: &'static str = "will prefer matches at the start of a word";
|
||||
const FP_CASESENS_N: &str = "search is case-insensitive";
|
||||
const FP_CASESENS_Y: &str = "search is case-sensitive!";
|
||||
const FP_PREFSTART_N: &str = "simple search";
|
||||
const FP_PREFSTART_Y: &str = "will prefer matches at the start of a word";
|
||||
impl FilterPanel {
|
||||
pub fn new(
|
||||
search_settings_changed: Arc<AtomicBool>,
|
||||
@@ -1697,8 +1690,8 @@ impl FilterPanel {
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|(text, preset)| {
|
||||
let f = Arc::clone(&filter);
|
||||
let oc = Arc::clone(&on_change);
|
||||
let f = Arc::clone(filter);
|
||||
let oc = Arc::clone(on_change);
|
||||
Button::new(
|
||||
GuiElemCfg::default(),
|
||||
move |_| {
|
||||
@@ -1876,26 +1869,24 @@ impl FilterPanel {
|
||||
let oc = Arc::clone(on_change);
|
||||
let p = path.clone();
|
||||
tf1.on_changed = Some(Box::new(move |text| {
|
||||
if let Ok(n) = text.parse() {
|
||||
if let Some(Ok(FilterType::TagWithValueInt(_, v, _))) =
|
||||
if let Ok(n) = text.parse()
|
||||
&& let Some(Ok(FilterType::TagWithValueInt(_, v, _))) =
|
||||
mx.lock().unwrap().get_mut(&p)
|
||||
{
|
||||
*v = n;
|
||||
oc(false);
|
||||
}
|
||||
{
|
||||
*v = n;
|
||||
oc(false);
|
||||
}
|
||||
}));
|
||||
let mx = Arc::clone(mutex);
|
||||
let oc = Arc::clone(on_change);
|
||||
let p = path.clone();
|
||||
tf2.on_changed = Some(Box::new(move |text| {
|
||||
if let Ok(n) = text.parse() {
|
||||
if let Some(Ok(FilterType::TagWithValueInt(_, _, v))) =
|
||||
if let Ok(n) = text.parse()
|
||||
&& let Some(Ok(FilterType::TagWithValueInt(_, _, v))) =
|
||||
mx.lock().unwrap().get_mut(&p)
|
||||
{
|
||||
*v = n;
|
||||
oc(false);
|
||||
}
|
||||
{
|
||||
*v = n;
|
||||
oc(false);
|
||||
}
|
||||
}));
|
||||
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) {
|
||||
// set line height
|
||||
if info.line_height != self.line_height {
|
||||
for h in &mut self.c_tab_main.children_heights {
|
||||
*h = info.line_height;
|
||||
}
|
||||
for h in &mut self.c_tab_filters_songs.children_heights {
|
||||
*h = info.line_height;
|
||||
}
|
||||
for h in &mut self.c_tab_filters_albums.children_heights {
|
||||
*h = info.line_height;
|
||||
}
|
||||
for h in &mut self.c_tab_filters_artists.children_heights {
|
||||
*h = info.line_height;
|
||||
}
|
||||
self.c_tab_main.config_mut().redraw = true;
|
||||
self.c_tab_filters_songs.config_mut().redraw = true;
|
||||
self.c_tab_filters_albums.config_mut().redraw = true;
|
||||
self.c_tab_filters_artists.config_mut().redraw = true;
|
||||
self.c_tab_main.children_heights.fill(info.line_height);
|
||||
self.c_tab_filters_songs
|
||||
.children_heights
|
||||
.fill(info.line_height);
|
||||
self.c_tab_filters_albums
|
||||
.children_heights
|
||||
.fill(info.line_height);
|
||||
self.c_tab_filters_artists
|
||||
.children_heights
|
||||
.fill(info.line_height);
|
||||
self.c_tab_main.config_mut().redraw_once();
|
||||
self.c_tab_filters_songs.config_mut().redraw_once();
|
||||
self.c_tab_filters_albums.config_mut().redraw_once();
|
||||
self.c_tab_filters_artists.config_mut().redraw_once();
|
||||
self.line_height = info.line_height;
|
||||
}
|
||||
// maybe switch tabs
|
||||
@@ -2001,7 +1990,7 @@ impl GuiElem for FilterPanel {
|
||||
);
|
||||
sb.children = ft;
|
||||
sb.children_heights = heights;
|
||||
sb.config_mut().redraw = true;
|
||||
sb.config_mut().redraw_once();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -177,10 +177,10 @@ impl GuiElem for NotifOverlay {
|
||||
}
|
||||
}
|
||||
// redraw
|
||||
if !self.notifs.is_empty() {
|
||||
if let Some(h) = &info.helper {
|
||||
h.request_redraw();
|
||||
}
|
||||
if !self.notifs.is_empty()
|
||||
&& let Some(h) = &info.helper
|
||||
{
|
||||
h.request_redraw();
|
||||
}
|
||||
}
|
||||
fn draw_rev(&self) -> bool {
|
||||
|
||||
@@ -86,7 +86,7 @@ impl CurrentInfo {
|
||||
GuiElemCfg::default(),
|
||||
[Label::new(
|
||||
GuiElemCfg::default(),
|
||||
format!("Couldn't load cover"),
|
||||
"Couldn't load cover".to_owned(),
|
||||
Color::WHITE,
|
||||
None,
|
||||
Vec2::new(0.5, 0.5),
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use musicdb_lib::{
|
||||
data::{
|
||||
AlbumId, ArtistId,
|
||||
database::Database,
|
||||
queue::{Queue, QueueContent, QueueDuration},
|
||||
song::Song,
|
||||
AlbumId, ArtistId,
|
||||
},
|
||||
server::{Action, Req},
|
||||
};
|
||||
@@ -16,7 +16,7 @@ use speedy2d::{
|
||||
|
||||
use crate::{
|
||||
gui::{Dragging, DrawInfo, EventInfo, GuiAction, GuiElem, GuiElemCfg},
|
||||
gui_base::{Panel, ScrollBox},
|
||||
gui_base::{Button, Panel, ScrollBox},
|
||||
gui_text::{self, AdvancedLabel, Label, TextField},
|
||||
};
|
||||
|
||||
@@ -95,7 +95,7 @@ impl QueueViewer {
|
||||
musicdb_lib::data::queue::QueueFolder {
|
||||
index: 0,
|
||||
content: vec![],
|
||||
name: format!("folder name"),
|
||||
name: "folder name".to_owned(),
|
||||
order: None,
|
||||
},
|
||||
false,
|
||||
@@ -104,7 +104,7 @@ impl QueueViewer {
|
||||
{
|
||||
let mut tf = TextField::new(
|
||||
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.67, 0.0),
|
||||
);
|
||||
@@ -219,8 +219,8 @@ impl GuiElem for QueueViewer {
|
||||
}
|
||||
}
|
||||
}
|
||||
let dt = fmt_dur(info.database.queue.duration_total(&info.database));
|
||||
let dr = fmt_dur(info.database.queue.duration_remaining(&info.database));
|
||||
let dt = fmt_dur(info.database.queue.duration_total(info.database));
|
||||
let dr = fmt_dur(info.database.queue.duration_remaining(info.database));
|
||||
label.content = vec![
|
||||
vec![(
|
||||
gui_text::AdvancedContent::Text(gui_text::Content::new(
|
||||
@@ -239,15 +239,15 @@ impl GuiElem for QueueViewer {
|
||||
1.0,
|
||||
)],
|
||||
];
|
||||
label.config_mut().redraw = true;
|
||||
label.config_mut().redraw_once();
|
||||
}
|
||||
if self.config.redraw || info.pos.size() != self.config.pixel_pos.size() {
|
||||
self.config.redraw = false;
|
||||
if self.config.redraw() || info.pos.size() != self.config.pixel_pos.size() {
|
||||
self.config.redrawn();
|
||||
let mut c = vec![];
|
||||
let mut h = vec![];
|
||||
queue_gui(
|
||||
&info.database.queue,
|
||||
&info.database,
|
||||
info.database,
|
||||
0.0,
|
||||
0.02,
|
||||
info.line_height,
|
||||
@@ -260,12 +260,12 @@ impl GuiElem for QueueViewer {
|
||||
let scroll_box = &mut self.c_scroll_box;
|
||||
scroll_box.children = c;
|
||||
scroll_box.children_heights = h;
|
||||
scroll_box.config_mut().redraw = true;
|
||||
scroll_box.config_mut().redraw_once();
|
||||
}
|
||||
}
|
||||
fn updated_queue(&mut self) {
|
||||
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)),
|
||||
);
|
||||
if let Some(mut inner) = queue_gui(
|
||||
&inner,
|
||||
inner,
|
||||
db,
|
||||
depth,
|
||||
depth_inc_by,
|
||||
@@ -423,7 +423,7 @@ impl GuiElem for QueueEmptySpaceDragHandler {
|
||||
|
||||
fn generic_queue_draw(
|
||||
info: &mut DrawInfo,
|
||||
path: &Vec<usize>,
|
||||
path: &[usize],
|
||||
queue: impl FnOnce() -> Queue,
|
||||
mouse: &mut bool,
|
||||
copy_on_mouse_down: bool,
|
||||
@@ -435,7 +435,7 @@ fn generic_queue_draw(
|
||||
Dragging::Queue(if copy_on_mouse_down {
|
||||
Ok(queue())
|
||||
} else {
|
||||
Err(path.clone())
|
||||
Err(path.to_vec())
|
||||
}),
|
||||
None,
|
||||
))));
|
||||
@@ -567,8 +567,24 @@ impl GuiElem for QueueSong {
|
||||
if button == MouseButton::Left && e.take() {
|
||||
self.mouse = true;
|
||||
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> {
|
||||
if self.mouse && button == MouseButton::Left {
|
||||
@@ -648,10 +664,8 @@ impl GuiElem for QueueSong {
|
||||
}
|
||||
},
|
||||
move |mut p, q| {
|
||||
if insert_below {
|
||||
if let Some(l) = p.last_mut() {
|
||||
*l += 1;
|
||||
}
|
||||
if insert_below && let Some(l) = p.last_mut() {
|
||||
*l += 1;
|
||||
}
|
||||
Action::QueueMove(q, p)
|
||||
},
|
||||
@@ -971,9 +985,9 @@ impl QueueLoop {
|
||||
match queue.content() {
|
||||
QueueContent::Loop(total, _current, _) => {
|
||||
if *total == 0 {
|
||||
format!("repeat forever")
|
||||
"repeat forever".to_owned()
|
||||
} else if *total == 1 {
|
||||
format!("repeat 1 time")
|
||||
"repeat 1 time".to_owned()
|
||||
} else {
|
||||
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> {
|
||||
if let Some(album) = db.albums().get(&id) {
|
||||
Some(
|
||||
QueueContent::Folder(musicdb_lib::data::queue::QueueFolder {
|
||||
index: 0,
|
||||
content: album
|
||||
.songs
|
||||
.iter()
|
||||
.map(|id| QueueContent::Song(*id).into())
|
||||
.collect(),
|
||||
name: album.name.clone(),
|
||||
order: None,
|
||||
})
|
||||
.into(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
db.albums().get(&id).map(|album| {
|
||||
QueueContent::Folder(musicdb_lib::data::queue::QueueFolder {
|
||||
index: 0,
|
||||
content: album
|
||||
.songs
|
||||
.iter()
|
||||
.map(|id| QueueContent::Song(*id).into())
|
||||
.collect(),
|
||||
name: album.name.clone(),
|
||||
order: None,
|
||||
})
|
||||
.into()
|
||||
})
|
||||
}
|
||||
fn add_to_queue_artist_by_id(id: ArtistId, db: &Database) -> Option<Queue> {
|
||||
if let Some(artist) = db.artists().get(&id) {
|
||||
Some(
|
||||
QueueContent::Folder(musicdb_lib::data::queue::QueueFolder {
|
||||
index: 0,
|
||||
content: artist
|
||||
.singles
|
||||
.iter()
|
||||
.map(|id| QueueContent::Song(*id).into())
|
||||
.chain(
|
||||
artist
|
||||
.albums
|
||||
.iter()
|
||||
.filter_map(|id| add_to_queue_album_by_id(*id, db)),
|
||||
)
|
||||
.collect(),
|
||||
name: artist.name.clone(),
|
||||
order: None,
|
||||
})
|
||||
.into(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
db.artists().get(&id).map(|artist| {
|
||||
QueueContent::Folder(musicdb_lib::data::queue::QueueFolder {
|
||||
index: 0,
|
||||
content: artist
|
||||
.singles
|
||||
.iter()
|
||||
.map(|id| QueueContent::Song(*id).into())
|
||||
.chain(
|
||||
artist
|
||||
.albums
|
||||
.iter()
|
||||
.filter_map(|id| add_to_queue_album_by_id(*id, db)),
|
||||
)
|
||||
.collect(),
|
||||
name: artist.name.clone(),
|
||||
order: None,
|
||||
})
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ use musicdb_lib::{
|
||||
data::queue::{QueueContent, QueueFolder},
|
||||
server::{Action, Req},
|
||||
};
|
||||
use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle, window::VirtualKeyCode, Graphics2D};
|
||||
use uianimator::{default_animator_f64_quadratic::DefaultAnimatorF64Quadratic, Animator};
|
||||
use speedy2d::{Graphics2D, color::Color, dimen::Vec2, shape::Rectangle, window::VirtualKeyCode};
|
||||
use uianimator::{Animator, default_animator_f64_quadratic::DefaultAnimatorF64Quadratic};
|
||||
|
||||
use crate::{
|
||||
gui::{
|
||||
@@ -54,7 +54,6 @@ pub struct GuiScreen {
|
||||
pub c_main_view: Panel<MainView>,
|
||||
pub c_context_menu: Option<Box<dyn GuiElem>>,
|
||||
pub idle: DefaultAnimatorF64Quadratic,
|
||||
pub idle_prev_val: f32,
|
||||
// pub settings: (bool, Option<Instant>),
|
||||
pub settings: (bool, Option<Instant>),
|
||||
pub last_interaction: Instant,
|
||||
@@ -172,7 +171,6 @@ impl GuiScreen {
|
||||
c_context_menu: None,
|
||||
hotkey: Hotkey::new_noshift(VirtualKeyCode::Escape),
|
||||
idle: DefaultAnimatorF64Quadratic::new(0.0, 0.67),
|
||||
idle_prev_val: 0.0,
|
||||
settings: (false, None),
|
||||
last_interaction: Instant::now(),
|
||||
idle_timeout: Some(60.0),
|
||||
@@ -184,17 +182,9 @@ impl GuiScreen {
|
||||
let prog = since.elapsed().as_secs_f32() / seconds;
|
||||
if prog >= 1.0 {
|
||||
v.1 = None;
|
||||
if v.0 {
|
||||
1.0
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
if v.0 { 1.0 } else { 0.0 }
|
||||
} else {
|
||||
if v.0 {
|
||||
prog
|
||||
} else {
|
||||
1.0 - prog
|
||||
}
|
||||
if v.0 { prog } else { 1.0 - prog }
|
||||
}
|
||||
} else if v.0 {
|
||||
1.0
|
||||
@@ -222,12 +212,11 @@ impl GuiScreen {
|
||||
self.idle.set_target(0.0, Instant::now());
|
||||
}
|
||||
fn idle_check(&mut self) {
|
||||
if self.idle.target() == 0.0 {
|
||||
if let Some(dur) = &self.idle_timeout {
|
||||
if self.last_interaction.elapsed().as_secs_f64() > *dur {
|
||||
self.idle.set_target(1.0, Instant::now());
|
||||
}
|
||||
}
|
||||
if self.idle.target() == 0.0
|
||||
&& let Some(dur) = &self.idle_timeout
|
||||
&& self.last_interaction.elapsed().as_secs_f64() > *dur
|
||||
{
|
||||
self.idle.set_target(1.0, Instant::now());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,7 +242,7 @@ impl GuiElem for GuiScreen {
|
||||
]
|
||||
.into_iter()
|
||||
.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([
|
||||
self.c_status_bar.elem_mut(),
|
||||
self.c_settings.elem_mut(),
|
||||
@@ -404,15 +393,12 @@ impl GuiElem for GuiScreen {
|
||||
};
|
||||
// request_redraw for animations
|
||||
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() {
|
||||
self.idle_prev_val = idle_value;
|
||||
if let Some(h) = &info.helper {
|
||||
h.request_redraw()
|
||||
}
|
||||
}
|
||||
// animations: idle
|
||||
if idle_changed {
|
||||
// animations: idle
|
||||
let enable_normal_ui = idle_value < 1.0;
|
||||
self.set_normal_ui_enabled(enable_normal_ui);
|
||||
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 speedy2d::{
|
||||
Graphics2D,
|
||||
color::Color,
|
||||
dimen::Vec2,
|
||||
shape::Rectangle,
|
||||
window::{KeyScancode, ModifiersState, MouseButton, VirtualKeyCode},
|
||||
Graphics2D,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -32,7 +32,7 @@ impl Settings {
|
||||
scroll_sensitivity_lines: f64,
|
||||
scroll_sensitivity_pages: f64,
|
||||
) -> Self {
|
||||
config.redraw = true;
|
||||
config.redraw_once();
|
||||
Self {
|
||||
config,
|
||||
c_scroll_box: ScrollBox::new(
|
||||
@@ -53,11 +53,7 @@ impl Settings {
|
||||
}
|
||||
pub fn get_timeout_val(&self) -> Option<f64> {
|
||||
let v = self.c_scroll_box.children.idle_time.children.1.val;
|
||||
if v > 0.0 {
|
||||
Some(v * v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
if v > 0.0 { Some(v * v) } else { None }
|
||||
}
|
||||
}
|
||||
pub struct SettingsContent {
|
||||
@@ -125,7 +121,7 @@ impl KeybindInput {
|
||||
b.key,
|
||||
)
|
||||
} else {
|
||||
format!("")
|
||||
String::new()
|
||||
},
|
||||
Color::WHITE,
|
||||
None,
|
||||
@@ -429,7 +425,7 @@ impl SettingsContent {
|
||||
}
|
||||
if hours == 0 && minutes < 10 && (seconds > 0 || minutes == 0) {
|
||||
s.push_str(&seconds.to_string());
|
||||
s.push_str("s");
|
||||
s.push('s');
|
||||
} else if s.ends_with(" ") {
|
||||
s.pop();
|
||||
}
|
||||
@@ -532,7 +528,7 @@ impl GuiElem for Settings {
|
||||
}
|
||||
fn draw(&mut self, info: &mut DrawInfo, _g: &mut Graphics2D) {
|
||||
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 background = &mut self.c_background;
|
||||
@@ -547,9 +543,9 @@ impl GuiElem for Settings {
|
||||
settings_opacity_slider.val as _,
|
||||
);
|
||||
}
|
||||
if self.config.redraw {
|
||||
self.config.redraw = false;
|
||||
scrollbox.config_mut().redraw = true;
|
||||
if self.config.redraw() {
|
||||
self.config.redrawn();
|
||||
scrollbox.config_mut().redraw_once();
|
||||
if scrollbox.children_heights.len() == scrollbox.children.len() {
|
||||
for (i, h) in scrollbox.children_heights.iter_mut().enumerate() {
|
||||
*h = if i == 0 || i >= 8 {
|
||||
@@ -560,7 +556,7 @@ impl GuiElem for Settings {
|
||||
}
|
||||
} else {
|
||||
// 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![(
|
||||
AdvancedContent::Text(Content::new(
|
||||
format!("{}", v.title),
|
||||
v.title.to_string(),
|
||||
if v.enabled {
|
||||
Color::WHITE
|
||||
} else {
|
||||
@@ -603,7 +599,7 @@ pub fn build_keybind_elems(
|
||||
)],
|
||||
vec![(
|
||||
AdvancedContent::Text(Content::new(
|
||||
format!("{}", v.description),
|
||||
v.description.to_string(),
|
||||
if v.enabled {
|
||||
Color::LIGHT_GRAY
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use musicdb_lib::data::{AlbumId, ArtistId};
|
||||
use speedy2d::{color::Color, dimen::Vec2, Graphics2D};
|
||||
use speedy2d::{Graphics2D, color::Color, dimen::Vec2};
|
||||
|
||||
use crate::{
|
||||
gui::{DrawInfo, GuiElem, GuiElemCfg},
|
||||
@@ -30,13 +30,13 @@ impl SongAdder {
|
||||
scroll_sensitivity_lines: f64,
|
||||
scroll_sensitivity_pages: f64,
|
||||
) -> Self {
|
||||
config.redraw = true;
|
||||
config.redraw_once();
|
||||
Self {
|
||||
config,
|
||||
state: 0,
|
||||
c_loading: Some(Label::new(
|
||||
GuiElemCfg::default(),
|
||||
format!("Loading..."),
|
||||
"Loading...".to_owned(),
|
||||
Color::GRAY,
|
||||
None,
|
||||
Vec2::new(0.5, 0.5),
|
||||
@@ -99,7 +99,7 @@ impl GuiElem for SongAdder {
|
||||
.iter()
|
||||
.map(|(path, is_bad)| AddableSong::new(path.to_owned(), *is_bad))
|
||||
.collect();
|
||||
self.c_scroll_box.config_mut().redraw = true;
|
||||
self.c_scroll_box.config_mut().redraw_once();
|
||||
self.data = Some(
|
||||
data.into_iter()
|
||||
.map(|(p, b)| AddSong {
|
||||
@@ -125,9 +125,9 @@ impl GuiElem for SongAdder {
|
||||
}
|
||||
}
|
||||
|
||||
if self.config.redraw {
|
||||
self.config.redraw = false;
|
||||
self.c_scroll_box.config_mut().redraw = true;
|
||||
if self.config.redraw() {
|
||||
self.config.redrawn();
|
||||
self.c_scroll_box.config_mut().redraw_once();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -147,7 +147,7 @@ impl AddableSong {
|
||||
|_| vec![],
|
||||
[Label::new(
|
||||
GuiElemCfg::default(),
|
||||
format!("{path}"),
|
||||
path.to_string(),
|
||||
if is_bad {
|
||||
Color::LIGHT_GRAY
|
||||
} else {
|
||||
|
||||
@@ -61,11 +61,11 @@ impl GuiElem for StatusBar {
|
||||
self.c_song_label.content = if let Some(song) = self.current_info.current_song {
|
||||
info.gui_config
|
||||
.status_bar_text
|
||||
.gen_new(&info.database, info.database.get_song(&song))
|
||||
.gen_new(info.database, info.database.get_song(&song))
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
self.c_song_label.config_mut().redraw = true;
|
||||
self.c_song_label.config_mut().redraw_once();
|
||||
}
|
||||
if self.current_info.new_cover {
|
||||
self.current_info.new_cover = false;
|
||||
|
||||
@@ -387,14 +387,14 @@ impl GuiElem for AdvancedLabel {
|
||||
self
|
||||
}
|
||||
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
|
||||
.content
|
||||
.iter()
|
||||
.any(|v| v.iter().any(|(c, _, _)| c.will_redraw()))
|
||||
{
|
||||
self.config.redraw = false;
|
||||
self.config.redrawn();
|
||||
let mut max_len = 0.0;
|
||||
let mut total_height = 0.0;
|
||||
for line in &self.content {
|
||||
@@ -461,7 +461,7 @@ impl GuiElem for AdvancedLabel {
|
||||
if handle.is_none() {
|
||||
match source {
|
||||
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) {
|
||||
*handle = Some(Some(img));
|
||||
} else {
|
||||
|
||||
@@ -18,14 +18,13 @@ impl Hotkey {
|
||||
if self.modifiers == u8::MAX {
|
||||
return false;
|
||||
}
|
||||
down == false
|
||||
!down
|
||||
&& key.is_some_and(|v| v == self.key)
|
||||
&& (self.modifiers & 0b10 == 1 || (self.modifiers & 0b01 == 1) == modifiers.ctrl())
|
||||
&& (self.modifiers & 0b1000 == 1 || (self.modifiers & 0b0100 == 1) == modifiers.shift())
|
||||
&& (self.modifiers & 0b100000 == 1
|
||||
|| (self.modifiers & 0b010000 == 1) == modifiers.alt())
|
||||
&& (self.modifiers & 0b10000000 == 1
|
||||
|| (self.modifiers & 0b01000000 == 1) == modifiers.logo())
|
||||
&& (self.modifiers & 0b10 > 0 || (self.modifiers & 0b01 > 0) == modifiers.ctrl())
|
||||
&& (self.modifiers & 0b1000 > 0 || (self.modifiers & 0b0100 > 0) == modifiers.shift())
|
||||
&& (self.modifiers & 0b100000 > 0 || (self.modifiers & 0b010000 > 0) == modifiers.alt())
|
||||
&& (self.modifiers & 0b10000000 > 0
|
||||
|| (self.modifiers & 0b01000000 > 0) == modifiers.logo())
|
||||
}
|
||||
/// unlike noshift, this ignores the shift modifier
|
||||
pub fn new_key(key: VirtualKeyCode) -> Self {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#![allow(dead_code)]
|
||||
#![allow(unused_variables)]
|
||||
#![allow(clippy::type_complexity)]
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
use std::{
|
||||
io::{BufReader, Write},
|
||||
@@ -18,8 +20,8 @@ use musicdb_lib::data::cache_manager::CacheManager;
|
||||
use musicdb_lib::player::{Player, PlayerBackendFeat};
|
||||
use musicdb_lib::{
|
||||
data::{
|
||||
database::{ClientIo, Database},
|
||||
CoverId, SongId,
|
||||
database::{ClientIo, Database},
|
||||
},
|
||||
load::ToFromBytes,
|
||||
server::Command,
|
||||
@@ -107,7 +109,9 @@ fn main() {
|
||||
#[cfg(not(feature = "speedy2d"))]
|
||||
#[cfg(not(feature = "mers"))]
|
||||
#[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
|
||||
let args = Args::parse();
|
||||
// start
|
||||
@@ -215,7 +219,7 @@ fn main() {
|
||||
}
|
||||
#[cfg(feature = "playback")]
|
||||
if let Some(player) = &mut player {
|
||||
player.update_dont_uncache(&mut *db);
|
||||
player.update_dont_uncache(&mut db);
|
||||
}
|
||||
drop(db);
|
||||
#[cfg(feature = "speedy2d")]
|
||||
@@ -250,10 +254,12 @@ fn main() {
|
||||
Some(Arc::clone(&get_con));
|
||||
}
|
||||
let occasional_refresh_sender = Arc::clone(&sender);
|
||||
thread::spawn(move || loop {
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
if let Some(v) = &*occasional_refresh_sender.lock().unwrap() {
|
||||
v.send_event(GuiEvent::Refresh).unwrap();
|
||||
thread::spawn(move || {
|
||||
loop {
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
if let Some(v) = &*occasional_refresh_sender.lock().unwrap() {
|
||||
v.send_event(GuiEvent::Refresh).unwrap();
|
||||
}
|
||||
}
|
||||
});
|
||||
gui::main(
|
||||
@@ -328,12 +334,8 @@ fn main() {
|
||||
|
||||
pub fn accumulate<F: FnMut() -> Option<T>, T>(mut f: F) -> Vec<T> {
|
||||
let mut o = vec![];
|
||||
loop {
|
||||
if let Some(v) = f() {
|
||||
o.push(v);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
while let Some(v) = f() {
|
||||
o.push(v);
|
||||
}
|
||||
o
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ impl TextBuilder {
|
||||
}
|
||||
for part in &self.0 {
|
||||
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::SetScale(v) => *scale = *v,
|
||||
TextPart::SetHeightAlign(v) => *align = *v,
|
||||
@@ -124,17 +124,17 @@ impl TextBuilder {
|
||||
}
|
||||
}
|
||||
TextPart::AlbumName => {
|
||||
if let Some(s) = current_song {
|
||||
if let Some(album) = s.album.and_then(|id| db.albums().get(&id)) {
|
||||
push!(album.name.to_owned());
|
||||
}
|
||||
if let Some(s) = current_song
|
||||
&& let Some(album) = s.album.and_then(|id| db.albums().get(&id))
|
||||
{
|
||||
push!(album.name.to_owned());
|
||||
}
|
||||
}
|
||||
TextPart::ArtistName => {
|
||||
if let Some(s) = current_song {
|
||||
if let Some(artist) = db.artists().get(&s.artist) {
|
||||
push!(artist.name.to_owned());
|
||||
}
|
||||
if let Some(s) = current_song
|
||||
&& let Some(artist) = db.artists().get(&s.artist)
|
||||
{
|
||||
push!(artist.name.to_owned());
|
||||
}
|
||||
}
|
||||
TextPart::SongDuration(show_millis) => {
|
||||
@@ -152,7 +152,7 @@ impl TextBuilder {
|
||||
}
|
||||
TextPart::TagEq(p) => {
|
||||
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!(
|
||||
match i {
|
||||
0 => 's',
|
||||
@@ -223,174 +223,165 @@ impl TextBuilder {
|
||||
if current.starts_with(' ') {
|
||||
current = current.replacen(' ', "\u{00A0}", 1);
|
||||
}
|
||||
vec.push(TextPart::Literal(std::mem::replace(
|
||||
&mut current,
|
||||
String::new(),
|
||||
)));
|
||||
vec.push(TextPart::Literal(std::mem::take(&mut current)));
|
||||
}
|
||||
};
|
||||
}
|
||||
loop {
|
||||
if let Some(ch) = chars.next() {
|
||||
match ch {
|
||||
'\n' => {
|
||||
while let Some(ch) = chars.next() {
|
||||
match ch {
|
||||
'\n' => {
|
||||
done!();
|
||||
vec.push(TextPart::LineBreak);
|
||||
}
|
||||
'\\' => match chars.next() {
|
||||
None => current.push('\\'),
|
||||
Some('t') => {
|
||||
done!();
|
||||
vec.push(TextPart::LineBreak);
|
||||
vec.push(TextPart::SongTitle);
|
||||
}
|
||||
'\\' => match chars.next() {
|
||||
None => current.push('\\'),
|
||||
Some('t') => {
|
||||
done!();
|
||||
vec.push(TextPart::SongTitle);
|
||||
}
|
||||
Some('a') => {
|
||||
done!();
|
||||
vec.push(TextPart::AlbumName);
|
||||
}
|
||||
Some('A') => {
|
||||
done!();
|
||||
vec.push(TextPart::ArtistName);
|
||||
}
|
||||
Some('d') => {
|
||||
done!();
|
||||
vec.push(TextPart::SongDuration(false));
|
||||
}
|
||||
Some('D') => {
|
||||
done!();
|
||||
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();
|
||||
Some('a') => {
|
||||
done!();
|
||||
vec.push(TextPart::AlbumName);
|
||||
}
|
||||
Some('A') => {
|
||||
done!();
|
||||
vec.push(TextPart::ArtistName);
|
||||
}
|
||||
Some('d') => {
|
||||
done!();
|
||||
vec.push(TextPart::SongDuration(false));
|
||||
}
|
||||
Some('D') => {
|
||||
done!();
|
||||
vec.push(TextPart::SongDuration(true));
|
||||
}
|
||||
Some('s') => {
|
||||
done!();
|
||||
vec.push(TextPart::SetScale({
|
||||
let mut str = String::new();
|
||||
loop {
|
||||
match chars.next() {
|
||||
None => {
|
||||
return Err(TextBuilderParseError::InvalidImageSourceName(
|
||||
src,
|
||||
));
|
||||
}
|
||||
Some(':') => break,
|
||||
Some(c) => src.push(c),
|
||||
None | Some(';') => break,
|
||||
Some(c) => str.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),
|
||||
},
|
||||
'%' => {
|
||||
if let Ok(v) = str.parse() {
|
||||
v
|
||||
} else {
|
||||
return Err(TextBuilderParseError::CouldntParse(
|
||||
str,
|
||||
"number (float)".to_string(),
|
||||
));
|
||||
}
|
||||
}))
|
||||
}
|
||||
Some('h') => {
|
||||
done!();
|
||||
let mode = if let Some(ch) = chars.next() {
|
||||
ch
|
||||
} else {
|
||||
return Err(TextBuilderParseError::UnclosedPercent);
|
||||
};
|
||||
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 {
|
||||
match chars.next() {
|
||||
Some('%') => {
|
||||
let s = std::mem::replace(&mut current, String::new());
|
||||
vec.push(match mode {
|
||||
'=' => TextPart::TagEq(s),
|
||||
'>' => TextPart::TagEnd(s),
|
||||
'_' => TextPart::TagContains(s),
|
||||
c => return Err(TextBuilderParseError::TagModeUnknown(c)),
|
||||
});
|
||||
break;
|
||||
None => {
|
||||
return Err(TextBuilderParseError::InvalidImageSourceName(src));
|
||||
}
|
||||
Some(ch) => current.push(ch),
|
||||
None => return Err(TextBuilderParseError::UnclosedPercent),
|
||||
Some(':') => break,
|
||||
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));
|
||||
}
|
||||
});
|
||||
}
|
||||
'?' => {
|
||||
done!();
|
||||
vec.push(TextPart::If(
|
||||
Self::from_chars(chars)?,
|
||||
Self::from_chars(chars)?,
|
||||
Self::from_chars(chars)?,
|
||||
));
|
||||
Some(ch) => current.push(ch),
|
||||
},
|
||||
'%' => {
|
||||
done!();
|
||||
let mode = if let Some(ch) = chars.next() {
|
||||
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!();
|
||||
|
||||
Reference in New Issue
Block a user