mirror of
https://github.com/Dummi26/musicdb.git
synced 2026-08-13 09:42:40 +02:00
Compare commits
3 Commits
dae7daaf25
...
e3293fffe6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3293fffe6 | ||
|
|
8ceecf6bb6 | ||
|
|
8b68995475 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,2 @@
|
||||
*/Cargo.lock
|
||||
*/target
|
||||
TODO.txt
|
||||
|
||||
3175
musicdb-client/Cargo.lock
generated
Executable file
3175
musicdb-client/Cargo.lock
generated
Executable file
File diff suppressed because it is too large
Load Diff
@@ -7,11 +7,11 @@ edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
musicdb-lib = { path = "../musicdb-lib", default-features = false }
|
||||
clap = { version = "4.5.45", features = ["derive"] }
|
||||
clap = { version = "4.6.5", features = ["derive"] }
|
||||
directories = "6.0.0"
|
||||
regex = "1.11.1"
|
||||
speedy2d = { version = "2.1.0", optional = true }
|
||||
toml = "0.9.5"
|
||||
regex = "1.13.1"
|
||||
speedy2d = { version = "3.1.0", optional = true }
|
||||
toml = "1.1.4"
|
||||
# musicdb-mers = { version = "0.1.0", path = "../musicdb-mers", optional = true }
|
||||
uianimator = "0.1.1"
|
||||
|
||||
|
||||
@@ -3,22 +3,23 @@ use std::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
io::Cursor,
|
||||
net::TcpStream,
|
||||
sync::{mpsc::Sender, Arc, Mutex},
|
||||
sync::{Arc, Mutex, mpsc::Sender},
|
||||
thread::JoinHandle,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use musicdb_lib::{
|
||||
data::{
|
||||
AlbumId, ArtistId, CoverId, SongId,
|
||||
database::{ClientIo, Database},
|
||||
queue::Queue,
|
||||
song::Song,
|
||||
AlbumId, ArtistId, CoverId, SongId,
|
||||
},
|
||||
load::ToFromBytes,
|
||||
server::{get, Action},
|
||||
server::{Action, get},
|
||||
};
|
||||
use speedy2d::{
|
||||
Graphics2D,
|
||||
color::Color,
|
||||
dimen::{UVec2, Vec2},
|
||||
font::Font,
|
||||
@@ -28,7 +29,6 @@ use speedy2d::{
|
||||
KeyScancode, ModifiersState, MouseButton, MouseScrollDistance, UserEventSender,
|
||||
VirtualKeyCode, WindowCreationOptions, WindowHandler, WindowHelper,
|
||||
},
|
||||
Graphics2D,
|
||||
};
|
||||
|
||||
#[cfg(feature = "merscfg")]
|
||||
@@ -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
|
||||
},
|
||||
@@ -775,7 +772,7 @@ pub(crate) trait GuiElemInternal: GuiElem {
|
||||
v.config_mut().mouse_down.2 = true;
|
||||
v.config_mut().mouse_pressed.2 = true;
|
||||
}
|
||||
MouseButton::Other(_) => {}
|
||||
_ => {}
|
||||
}
|
||||
Some(v.mouse_down(e, button))
|
||||
} else {
|
||||
@@ -820,7 +817,7 @@ pub(crate) trait GuiElemInternal: GuiElem {
|
||||
v.config_mut().mouse_down.2 = false;
|
||||
v.config_mut().mouse_pressed.2 = false;
|
||||
}
|
||||
MouseButton::Other(_) => {}
|
||||
_ => {}
|
||||
}
|
||||
vec.extend(v.mouse_up(e, button));
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1791,7 +1810,9 @@ impl WindowHandler<GuiEvent> for Gui {
|
||||
MersCfg::run(&mut gc, self, |m| &m.func_library_updated);
|
||||
self.gui_config = Some(gc);
|
||||
} else {
|
||||
eprintln!("WARN: Skipping call to merscfg's library_updated because gui_config is not available");
|
||||
eprintln!(
|
||||
"WARN: Skipping call to merscfg's library_updated because gui_config is not available"
|
||||
);
|
||||
}
|
||||
self.gui._recursive_all(true, &mut |e| e.updated_library());
|
||||
helper.request_redraw();
|
||||
@@ -1802,7 +1823,9 @@ impl WindowHandler<GuiEvent> for Gui {
|
||||
MersCfg::run(&mut gc, self, |m| &m.func_queue_updated);
|
||||
self.gui_config = Some(gc);
|
||||
} else {
|
||||
eprintln!("WARN: Skipping call to merscfg's queue_updated because gui_config is not available");
|
||||
eprintln!(
|
||||
"WARN: Skipping call to merscfg's queue_updated because gui_config is not available"
|
||||
);
|
||||
}
|
||||
self.gui._recursive_all(true, &mut |e| e.updated_queue());
|
||||
helper.request_redraw();
|
||||
|
||||
@@ -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!();
|
||||
|
||||
467
musicdb-filldb/Cargo.lock
generated
Executable file
467
musicdb-filldb/Cargo.lock
generated
Executable file
@@ -0,0 +1,467 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b25655df2c3cdd83c5e5b293b88acd880332b2ddadd7c30ac43144fdc0033da9"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chacha20"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorize"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc17e449bc7854c50b943d113a98bc0e01dc6585d2c66eaa09ca645ebd8a7e62"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "debug-helper"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f578e8e2c440e7297e008bb5486a3a8a194775224bbc23729b0dbdfaeebf162e"
|
||||
|
||||
[[package]]
|
||||
name = "dispatch2"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"objc2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "id3"
|
||||
version = "1.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24993fcabcbc07c8ac076a8e62db8593d1a5c4dbe81d57e531d2b7cb7f737380"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"byteorder",
|
||||
"flate2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.175"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
|
||||
dependencies = [
|
||||
"adler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mp3-duration"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "348bdc7300502f0801e5b57c448815713cd843b744ef9bda252a2698fdf90a0f"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "musicdb-filldb"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"id3",
|
||||
"mp3-duration",
|
||||
"musicdb-lib",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "musicdb-lib"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"colorize",
|
||||
"rand",
|
||||
"rc-u8-reader",
|
||||
"sysinfo",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ntapi"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f"
|
||||
dependencies = [
|
||||
"objc2-encode",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-foundation"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"dispatch2",
|
||||
"objc2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-encode"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
|
||||
|
||||
[[package]]
|
||||
name = "objc2-foundation"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"objc2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-io-kit"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-open-directory"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb82bed227edf5201dfedf072bba4015a33d3d4a98519837295a90f0a23f676d"
|
||||
dependencies = [
|
||||
"objc2",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
|
||||
dependencies = [
|
||||
"chacha20",
|
||||
"getrandom",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
|
||||
|
||||
[[package]]
|
||||
name = "rc-u8-reader"
|
||||
version = "2.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef271f94154149f9480043517e7a1845ef1ae9713e7c8b32abe4acb014bdbab9"
|
||||
dependencies = [
|
||||
"debug-helper",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sysinfo"
|
||||
version = "0.39.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2071df9448915b71c4fe6d25deaf1c22f12bd234f01540b77312bb8e41361e6"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"memchr",
|
||||
"ntapi",
|
||||
"objc2-core-foundation",
|
||||
"objc2-io-kit",
|
||||
"objc2-open-directory",
|
||||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.62.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
|
||||
dependencies = [
|
||||
"windows-collections",
|
||||
"windows-core",
|
||||
"windows-future",
|
||||
"windows-numerics",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-collections"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.62.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-future"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
"windows-link",
|
||||
"windows-threading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-numerics"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-threading"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
@@ -6,6 +6,6 @@ edition = "2024"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
id3 = "1.16.3"
|
||||
id3 = "1.17.1"
|
||||
mp3-duration = "0.1.10"
|
||||
musicdb-lib = { version = "0.1.0", path = "../musicdb-lib" }
|
||||
|
||||
1
musicdb-lib/.gitignore
vendored
1
musicdb-lib/.gitignore
vendored
@@ -1,2 +1 @@
|
||||
/target
|
||||
/Cargo.lock
|
||||
|
||||
1792
musicdb-lib/Cargo.lock
generated
Executable file
1792
musicdb-lib/Cargo.lock
generated
Executable file
File diff suppressed because it is too large
Load Diff
@@ -4,13 +4,13 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.22.1"
|
||||
base64 = "0.23.0"
|
||||
colorize = "0.1.0"
|
||||
playback-rs = { version = "0.4.4", optional = true }
|
||||
rand = "0.9.2"
|
||||
rc-u8-reader = "2.0.16"
|
||||
rodio = { version = "0.21.1", optional = true }
|
||||
sysinfo = "0.37.0"
|
||||
playback-rs = { version = "0.4.8", optional = true }
|
||||
rand = "0.10.2"
|
||||
rc-u8-reader = "2.0.17"
|
||||
rodio = { version = "0.22.2", optional = true }
|
||||
sysinfo = "0.39.6"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
3646
musicdb-server/Cargo.lock
generated
Executable file
3646
musicdb-server/Cargo.lock
generated
Executable file
File diff suppressed because it is too large
Load Diff
@@ -7,13 +7,13 @@ edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
musicdb-lib = { path = "../musicdb-lib" }
|
||||
clap = { version = "4.5.45", features = ["derive"] }
|
||||
clap = { version = "4.6.5", features = ["derive"] }
|
||||
headers = "0.4.1"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
tokio = { version = "1.47.1", optional = true, features = ["rt"] }
|
||||
tokio = { version = "1.53.1", optional = true, features = ["rt"] }
|
||||
rocket = { version = "0.5.1", optional = true }
|
||||
html-escape = { version = "0.2.13", optional = true }
|
||||
html-escape = { version = "0.2.14", optional = true }
|
||||
rocket_ws = "0.1.1"
|
||||
rocket_seek_stream = "0.2.6"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user