add ways to modify tags, and add a Fav button to client

This commit is contained in:
Mark
2023-12-30 18:12:02 +01:00
parent daad5c6aae
commit b848a0d511
8 changed files with 501 additions and 77 deletions

View File

@@ -234,7 +234,7 @@ pub fn main(
),
(
"Year".to_owned(),
crate::gui_library::FilterType::TagWithValueInt("Year".to_owned(), 1990, 2000),
crate::gui_library::FilterType::TagWithValueInt("Year=".to_owned(), 1990, 2000),
),
],
filter_presets_album: vec![
@@ -244,7 +244,7 @@ pub fn main(
),
(
"Year".to_owned(),
crate::gui_library::FilterType::TagWithValueInt("Year".to_owned(), 1990, 2000),
crate::gui_library::FilterType::TagWithValueInt("Year=".to_owned(), 1990, 2000),
),
],
filter_presets_artist: vec![
@@ -254,7 +254,7 @@ pub fn main(
),
(
"Year".to_owned(),
crate::gui_library::FilterType::TagWithValueInt("Year".to_owned(), 1990, 2000),
crate::gui_library::FilterType::TagWithValueInt("Year=".to_owned(), 1990, 2000),
),
],
#[cfg(feature = "merscfg")]
@@ -371,6 +371,18 @@ impl Gui {
| Command::RemoveSong(_)
| Command::RemoveAlbum(_)
| Command::RemoveArtist(_)
| Command::TagSongFlagSet(..)
| Command::TagSongFlagUnset(..)
| Command::TagAlbumFlagSet(..)
| Command::TagAlbumFlagUnset(..)
| Command::TagArtistFlagSet(..)
| Command::TagArtistFlagUnset(..)
| Command::TagSongPropertySet(..)
| Command::TagSongPropertyUnset(..)
| Command::TagAlbumPropertySet(..)
| Command::TagAlbumPropertyUnset(..)
| Command::TagArtistPropertySet(..)
| Command::TagArtistPropertyUnset(..)
| Command::SetSongDuration(..) => {
if let Some(s) = &*event_sender_arc.lock().unwrap() {
_ = s.send_event(GuiEvent::UpdatedLibrary);

View File

@@ -1,4 +1,7 @@
use std::{sync::Arc, time::Instant};
use std::{
sync::{atomic::AtomicBool, Arc},
time::Instant,
};
use musicdb_lib::data::ArtistId;
use speedy2d::{color::Color, dimen::Vec2, image::ImageHandle, shape::Rectangle};
@@ -39,11 +42,14 @@ pub struct IdleDisplay {
pub artist_image_to_cover_margin: f32,
pub force_reset_texts: bool,
is_fav: (bool, Arc<AtomicBool>),
}
impl IdleDisplay {
pub fn new(config: GuiElemCfg) -> Self {
let cover_bottom = 0.79;
let is_fav = Arc::new(AtomicBool::new(false));
Self {
config,
idle_mode: 0.0,
@@ -67,7 +73,8 @@ impl IdleDisplay {
),
c_side1_label: AdvancedLabel::new(GuiElemCfg::default(), Vec2::new(0.0, 0.5), vec![]),
c_side2_label: AdvancedLabel::new(GuiElemCfg::default(), Vec2::new(0.0, 0.5), vec![]),
c_buttons: PlayPause::new(GuiElemCfg::default()),
is_fav: (false, Arc::clone(&is_fav)),
c_buttons: PlayPause::new(GuiElemCfg::default(), is_fav),
c_buttons_custom_pos: false,
cover_aspect_ratio: AnimationController::new(
1.0,
@@ -122,6 +129,19 @@ impl GuiElem for IdleDisplay {
self.current_info.update(info, g);
if self.current_info.new_song || self.force_reset_texts {
self.current_info.new_song = false;
self.force_reset_texts = false;
let is_fav = self
.current_info
.current_song
.and_then(|id| info.database.get_song(&id))
.map(|song| song.general.tags.iter().any(|v| v == "Fav"))
.unwrap_or(false);
if self.is_fav.0 != is_fav {
self.is_fav.0 = is_fav;
self.is_fav
.1
.store(is_fav, std::sync::atomic::Ordering::Relaxed);
}
self.c_top_label.content = if let Some(song) = self.current_info.current_song {
info.gui_config
.idle_top_text
@@ -278,9 +298,10 @@ impl GuiElem for IdleDisplay {
self.c_side2_label.config_mut().pos =
Rectangle::from_tuples((left, ai_top), (max_right, bottom));
// limit width of c_buttons
let buttons_right_pos = 0.99;
let buttons_width_max = info.pos.height() * 0.08 / 0.3 / info.pos.width();
let buttons_width = buttons_width_max.min(0.2);
let buttons_right_pos = 1.0;
let buttons_width_max = info.pos.height() * 0.08 * 4.0 / info.pos.width();
// buttons use at most half the width (set to 0.2 later, when screen space is used for other things)
let buttons_width = buttons_width_max.min(0.5);
if !self.c_buttons_custom_pos {
self.c_buttons.config_mut().pos = Rectangle::from_tuples(
(buttons_right_pos - buttons_width, 0.86),
@@ -309,6 +330,7 @@ impl GuiElem for IdleDisplay {
}
fn updated_library(&mut self) {
self.current_info.update = true;
self.force_reset_texts = true;
}
fn updated_queue(&mut self) {
self.current_info.update = true;

View File

@@ -1,3 +1,5 @@
use std::sync::{atomic::AtomicBool, Arc};
use musicdb_lib::server::Command;
use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle, Graphics2D};
@@ -8,17 +10,44 @@ use crate::{
pub struct PlayPause {
config: GuiElemCfg,
set_fav: Button<[FavIcon; 1]>,
to_zero: Button<[Panel<()>; 1]>,
play_pause: Button<[PlayPauseDisplay; 1]>,
to_end: Button<[NextSongShape; 1]>,
}
impl PlayPause {
pub fn new(config: GuiElemCfg) -> Self {
pub fn new(config: GuiElemCfg, is_fav: Arc<AtomicBool>) -> Self {
Self {
config,
set_fav: Button::new(
GuiElemCfg::at(Rectangle::from_tuples((0.01, 0.01), (0.24, 0.99))),
|_| {
vec![GuiAction::Build(Box::new(|db| {
if let Some(song_id) = db.queue.get_current_song() {
if let Some(song) = db.get_song(song_id) {
vec![GuiAction::SendToServer(
if song.general.tags.iter().any(|v| v == "Fav") {
Command::TagSongFlagUnset(*song_id, "Fav".to_owned())
} else {
Command::TagSongFlagSet(*song_id, "Fav".to_owned())
},
)]
} else {
vec![]
}
} else {
vec![]
}
}))]
},
[FavIcon::new(
GuiElemCfg::at(Rectangle::from_tuples((0.2, 0.2), (0.8, 0.8))),
is_fav,
)],
),
to_zero: Button::new(
GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (0.3, 1.0))),
GuiElemCfg::at(Rectangle::from_tuples((0.26, 0.01), (0.49, 0.99))),
|_| vec![GuiAction::SendToServer(Command::Stop)],
[Panel::with_background(
GuiElemCfg::at(Rectangle::from_tuples((0.2, 0.2), (0.8, 0.8))),
@@ -27,7 +56,7 @@ impl PlayPause {
)],
),
play_pause: Button::new(
GuiElemCfg::at(Rectangle::from_tuples((0.35, 0.0), (0.65, 1.0))),
GuiElemCfg::at(Rectangle::from_tuples((0.51, 0.01), (0.74, 0.99))),
|btn| {
vec![GuiAction::SendToServer(if btn.children[0].is_playing {
Command::Pause
@@ -40,7 +69,7 @@ impl PlayPause {
))],
),
to_end: Button::new(
GuiElemCfg::at(Rectangle::from_tuples((0.7, 0.0), (1.0, 1.0))),
GuiElemCfg::at(Rectangle::from_tuples((0.76, 0.01), (0.99, 0.99))),
|_| vec![GuiAction::SendToServer(Command::NextSong)],
[NextSongShape::new(GuiElemCfg::at(Rectangle::from_tuples(
(0.2, 0.2),
@@ -175,6 +204,90 @@ impl GuiElem for NextSongShape {
}
}
struct FavIcon {
config: GuiElemCfg,
is_fav: Arc<AtomicBool>,
}
impl FavIcon {
pub fn new(config: GuiElemCfg, is_fav: Arc<AtomicBool>) -> Self {
Self { config, is_fav }
}
}
impl GuiElem for FavIcon {
fn draw(&mut self, info: &mut DrawInfo, g: &mut Graphics2D) {
let clr = if self.is_fav.load(std::sync::atomic::Ordering::Relaxed) {
Color::from_rgb(0.7, 0.1, 0.1)
} else {
Color::from_rgb(0.3, 0.2, 0.2)
};
let pos = if info.pos.width() > info.pos.height() {
let c = info.pos.top_left().x + info.pos.width() * 0.5;
let d = info.pos.height() * 0.5;
Rectangle::from_tuples(
(c - d, info.pos.top_left().y),
(c + d, info.pos.bottom_right().y),
)
} else if info.pos.height() > info.pos.width() {
let c = info.pos.top_left().y + info.pos.height() * 0.5;
let d = info.pos.width() * 0.5;
Rectangle::from_tuples(
(info.pos.top_left().x, c - d),
(info.pos.bottom_right().x, c + d),
)
} else {
info.pos.clone()
};
let circle_radius = 0.25;
let out_dist = pos.height() * circle_radius * std::f32::consts::SQRT_2 * 0.5;
let x_cntr = pos.top_left().x + pos.width() * 0.5;
let left_circle_cntr = Vec2::new(
pos.top_left().x + pos.width() * circle_radius,
pos.top_left().y + pos.height() * circle_radius,
);
let right_circle_cntr = Vec2::new(
pos.bottom_right().x - pos.width() * circle_radius,
pos.top_left().y + pos.height() * circle_radius,
);
let circle_radius = circle_radius * pos.height();
let x1 = x_cntr - circle_radius - out_dist;
let x2 = x_cntr + circle_radius + out_dist;
let h1 = pos.top_left().y + circle_radius;
let h2 = pos.top_left().y + circle_radius + out_dist;
g.draw_circle(left_circle_cntr, circle_radius, clr);
g.draw_circle(right_circle_cntr, circle_radius, clr);
g.draw_rectangle(Rectangle::from_tuples((x1, h1), (x2, h2)), clr);
g.draw_triangle(
[
Vec2::new(x1, h2),
Vec2::new(x2, h2),
Vec2::new(x_cntr, pos.bottom_right().y),
],
clr,
)
}
fn config(&self) -> &GuiElemCfg {
&self.config
}
fn config_mut(&mut self) -> &mut GuiElemCfg {
&mut self.config
}
fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
Box::new([].into_iter())
}
fn any(&self) -> &dyn std::any::Any {
self
}
fn any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn elem(&self) -> &dyn GuiElem {
self
}
fn elem_mut(&mut self) -> &mut dyn GuiElem {
self
}
}
impl GuiElem for PlayPause {
fn config(&self) -> &GuiElemCfg {
&self.config
@@ -185,6 +298,7 @@ impl GuiElem for PlayPause {
fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
Box::new(
[
self.set_fav.elem_mut(),
self.to_zero.elem_mut(),
self.play_pause.elem_mut(),
self.to_end.elem_mut(),

View File

@@ -1,3 +1,4 @@
use musicdb_lib::server::Command;
use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle, Graphics2D};
use crate::{
@@ -55,6 +56,7 @@ pub struct SettingsContent {
pub line_height: Panel<(Label, Slider)>,
pub scroll_sensitivity: Panel<(Label, Slider)>,
pub idle_time: Panel<(Label, Slider)>,
pub save_button: Button<[Label; 1]>,
}
impl GuiElemChildren for SettingsContent {
fn iter(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
@@ -259,6 +261,17 @@ impl SettingsContent {
),
),
),
save_button: Button::new(
GuiElemCfg::default(),
|_| vec![GuiAction::SendToServer(Command::Save)],
[Label::new(
GuiElemCfg::default(),
"Server: Save Changes".to_string(),
Color::WHITE,
None,
Vec2::new(0.5, 0.5),
)],
),
}
}
}

View File

@@ -1,4 +1,7 @@
use std::time::Instant;
use std::{
sync::{atomic::AtomicBool, Arc},
time::Instant,
};
use speedy2d::{dimen::Vec2, shape::Rectangle};
@@ -18,10 +21,12 @@ pub struct StatusBar {
c_song_label: AdvancedLabel,
pub force_reset_texts: bool,
c_buttons: PlayPause,
is_fav: (bool, Arc<AtomicBool>),
}
impl StatusBar {
pub fn new(config: GuiElemCfg) -> Self {
let is_fav = Arc::new(AtomicBool::new(false));
Self {
config,
idle_mode: 0.0,
@@ -37,7 +42,8 @@ impl StatusBar {
),
c_song_label: AdvancedLabel::new(GuiElemCfg::default(), Vec2::new(0.0, 0.5), vec![]),
force_reset_texts: false,
c_buttons: PlayPause::new(GuiElemCfg::default()),
is_fav: (false, Arc::clone(&is_fav)),
c_buttons: PlayPause::new(GuiElemCfg::default(), is_fav),
}
}
}
@@ -50,6 +56,20 @@ impl GuiElem for StatusBar {
self.current_info.update(info, g);
if self.current_info.new_song || self.force_reset_texts {
self.current_info.new_song = false;
self.force_reset_texts = false;
let is_fav = self
.current_info
.current_song
.and_then(|id| info.database.get_song(&id))
.map(|song| song.general.tags.iter().any(|v| v == "Fav"))
.unwrap_or(false);
eprintln!("is_fav: {is_fav}");
if self.is_fav.0 != is_fav {
self.is_fav.0 = is_fav;
self.is_fav
.1
.store(is_fav, std::sync::atomic::Ordering::Relaxed);
}
self.c_song_label.content = if let Some(song) = self.current_info.current_song {
info.gui_config
.status_bar_text
@@ -78,7 +98,7 @@ impl GuiElem for StatusBar {
}
// limit width of c_buttons
let buttons_right_pos = 0.99;
let buttons_width_max = info.pos.height() * 0.7 / 0.3 / info.pos.width();
let buttons_width_max = info.pos.height() * 0.7 * 4.0 / info.pos.width();
let buttons_width = buttons_width_max.min(0.2);
self.c_buttons.config_mut().pos = Rectangle::from_tuples(
(buttons_right_pos - buttons_width, 0.15),
@@ -130,6 +150,7 @@ impl GuiElem for StatusBar {
}
fn updated_library(&mut self) {
self.current_info.update = true;
self.force_reset_texts = true;
}
fn updated_queue(&mut self) {
self.current_info.update = true;