artist image support

This commit is contained in:
Mark 2023-11-12 20:52:24 +01:00
parent 671bf17c53
commit 55190d1943
19 changed files with 2387 additions and 2767 deletions

View File

@ -1,13 +1,11 @@
use std::{ use std::{
any::Any, any::Any,
collections::HashMap, collections::HashMap,
eprintln,
io::Cursor, io::Cursor,
net::TcpStream, net::TcpStream,
sync::{Arc, Mutex}, sync::{mpsc::Sender, Arc, Mutex},
thread::JoinHandle, thread::JoinHandle,
time::{Duration, Instant}, time::{Duration, Instant},
usize,
}; };
use musicdb_lib::{ use musicdb_lib::{
@ -33,7 +31,6 @@ use crate::{
gui_notif::{NotifInfo, NotifOverlay}, gui_notif::{NotifInfo, NotifOverlay},
gui_screen::GuiScreen, gui_screen::GuiScreen,
gui_text::Label, gui_text::Label,
gui_wrappers::WithFocusHotkey,
textcfg, textcfg,
}; };
@ -87,6 +84,9 @@ pub fn main(
let mut scroll_lines_multiplier = 3.0; let mut scroll_lines_multiplier = 3.0;
let mut scroll_pages_multiplier = 0.75; let mut scroll_pages_multiplier = 0.75;
let status_bar_text; let status_bar_text;
let idle_top_text;
let idle_side1_text;
let idle_side2_text;
match std::fs::read_to_string(&config_file) { match std::fs::read_to_string(&config_file) {
Ok(cfg) => { Ok(cfg) => {
if let Ok(table) = cfg.parse::<toml::Table>() { if let Ok(table) = cfg.parse::<toml::Table>() {
@ -135,6 +135,42 @@ pub fn main(
eprintln!("[toml] missing the required `text.status_bar` string value."); eprintln!("[toml] missing the required `text.status_bar` string value.");
std::process::exit(30); std::process::exit(30);
} }
if let Some(v) = t.get("idle_top").and_then(|v| v.as_str()) {
match v.parse() {
Ok(v) => idle_top_text = v,
Err(e) => {
eprintln!("[toml] `text.idle_top couldn't be parsed: {e}`");
std::process::exit(30);
}
}
} else {
eprintln!("[toml] missing the required `text.idle_top` string value.");
std::process::exit(30);
}
if let Some(v) = t.get("idle_side1").and_then(|v| v.as_str()) {
match v.parse() {
Ok(v) => idle_side1_text = v,
Err(e) => {
eprintln!("[toml] `text.idle_side1 couldn't be parsed: {e}`");
std::process::exit(30);
}
}
} else {
eprintln!("[toml] missing the required `text.idle_side1` string value.");
std::process::exit(30);
}
if let Some(v) = t.get("idle_side2").and_then(|v| v.as_str()) {
match v.parse() {
Ok(v) => idle_side2_text = v,
Err(e) => {
eprintln!("[toml] `text.idle_side2 couldn't be parsed: {e}`");
std::process::exit(30);
}
}
} else {
eprintln!("[toml] missing the required `text.idle_side2` string value.");
std::process::exit(30);
}
} else { } else {
eprintln!("[toml] missing the required `[text]` section!"); eprintln!("[toml] missing the required `[text]` section!");
std::process::exit(30); std::process::exit(30);
@ -183,12 +219,53 @@ pub fn main(
scroll_pixels_multiplier, scroll_pixels_multiplier,
scroll_lines_multiplier, scroll_lines_multiplier,
scroll_pages_multiplier, scroll_pages_multiplier,
GuiConfig { status_bar_text }, GuiConfig {
status_bar_text,
idle_top_text,
idle_side1_text,
idle_side2_text,
filter_presets_song: vec![
(
"Fav".to_owned(),
crate::gui_library::FilterType::TagEq("Fav".to_owned()),
),
(
"Year".to_owned(),
crate::gui_library::FilterType::TagWithValueInt("Year".to_owned(), 1990, 2000),
),
],
filter_presets_album: vec![
(
"Fav".to_owned(),
crate::gui_library::FilterType::TagEq("Fav".to_owned()),
),
(
"Year".to_owned(),
crate::gui_library::FilterType::TagWithValueInt("Year".to_owned(), 1990, 2000),
),
],
filter_presets_artist: vec![
(
"Fav".to_owned(),
crate::gui_library::FilterType::TagEq("Fav".to_owned()),
),
(
"Year".to_owned(),
crate::gui_library::FilterType::TagWithValueInt("Year".to_owned(), 1990, 2000),
),
],
},
)); ));
} }
pub struct GuiConfig { pub struct GuiConfig {
pub status_bar_text: textcfg::TextBuilder, pub status_bar_text: textcfg::TextBuilder,
pub idle_top_text: textcfg::TextBuilder,
pub idle_side1_text: textcfg::TextBuilder,
pub idle_side2_text: textcfg::TextBuilder,
pub filter_presets_song: Vec<(String, crate::gui_library::FilterType)>,
pub filter_presets_album: Vec<(String, crate::gui_library::FilterType)>,
pub filter_presets_artist: Vec<(String, crate::gui_library::FilterType)>,
} }
pub struct Gui { pub struct Gui {
@ -196,17 +273,21 @@ pub struct Gui {
pub database: Arc<Mutex<Database>>, pub database: Arc<Mutex<Database>>,
pub connection: TcpStream, pub connection: TcpStream,
pub get_con: Arc<Mutex<get::Client<TcpStream>>>, pub get_con: Arc<Mutex<get::Client<TcpStream>>>,
pub gui: WithFocusHotkey<GuiScreen>, pub gui: GuiScreen,
pub notif_sender:
Sender<Box<dyn FnOnce(&NotifOverlay) -> (Box<dyn GuiElem>, NotifInfo) + Send>>,
pub size: UVec2, pub size: UVec2,
pub mouse_pos: Vec2, pub mouse_pos: Vec2,
pub font: Font, pub font: Font,
pub covers: Option<HashMap<CoverId, GuiCover>>, pub covers: Option<HashMap<CoverId, GuiServerImage>>,
pub custom_images: Option<HashMap<String, GuiServerImage>>,
pub last_draw: Instant, pub last_draw: Instant,
pub modifiers: ModifiersState, pub modifiers: ModifiersState,
pub dragging: Option<( pub dragging: Option<(
Dragging, Dragging,
Option<Box<dyn FnMut(&mut DrawInfo, &mut Graphics2D)>>, Option<Box<dyn FnMut(&mut DrawInfo, &mut Graphics2D)>>,
)>, )>,
pub no_animations: bool,
pub line_height: f32, pub line_height: f32,
pub last_height: f32, pub last_height: f32,
pub scroll_pixels_multiplier: f64, pub scroll_pixels_multiplier: f64,
@ -229,6 +310,7 @@ impl Gui {
gui_config: GuiConfig, gui_config: GuiConfig,
) -> Self { ) -> Self {
let (notif_overlay, notif_sender) = NotifOverlay::new(); let (notif_overlay, notif_sender) = NotifOverlay::new();
let notif_sender_two = notif_sender.clone();
database.lock().unwrap().update_endpoints.push( database.lock().unwrap().update_endpoints.push(
musicdb_lib::data::database::UpdateEndpoint::Custom(Box::new(move |cmd| match cmd { musicdb_lib::data::database::UpdateEndpoint::Custom(Box::new(move |cmd| match cmd {
Command::Resume Command::Resume
@ -265,12 +347,12 @@ impl Gui {
} }
Command::ErrorInfo(t, d) => { Command::ErrorInfo(t, d) => {
let (t, d) = (t.clone(), d.clone()); let (t, d) = (t.clone(), d.clone());
notif_sender notif_sender_two
.send(Box::new(move |_| { .send(Box::new(move |_| {
( (
Box::new(Panel::with_background( Box::new(Panel::with_background(
GuiElemCfg::default(), GuiElemCfg::default(),
vec![Box::new(Label::new( [Label::new(
GuiElemCfg::default(), GuiElemCfg::default(),
if t.is_empty() { if t.is_empty() {
format!("Server message\n{d}") format!("Server message\n{d}")
@ -280,7 +362,7 @@ impl Gui {
Color::WHITE, Color::WHITE,
None, None,
Vec2::new(0.5, 0.5), Vec2::new(0.5, 0.5),
))], )],
Color::from_rgba(0.0, 0.0, 0.0, 0.8), Color::from_rgba(0.0, 0.0, 0.0, 0.8),
)), )),
if t.is_empty() { if t.is_empty() {
@ -295,30 +377,32 @@ impl Gui {
} }
})), })),
); );
let no_animations = false;
Gui { Gui {
event_sender, event_sender,
database, database,
connection, connection,
get_con, get_con,
gui: WithFocusHotkey::new_noshift( gui: GuiScreen::new(
VirtualKeyCode::Escape, GuiElemCfg::default(),
GuiScreen::new( notif_overlay,
GuiElemCfg::default(), no_animations,
notif_overlay, line_height,
line_height, scroll_pixels_multiplier,
scroll_pixels_multiplier, scroll_lines_multiplier,
scroll_lines_multiplier, scroll_pages_multiplier,
scroll_pages_multiplier,
),
), ),
notif_sender,
size: UVec2::ZERO, size: UVec2::ZERO,
mouse_pos: Vec2::ZERO, mouse_pos: Vec2::ZERO,
font, font,
covers: Some(HashMap::new()), covers: Some(HashMap::new()),
custom_images: Some(HashMap::new()),
// font: Font::new(include_bytes!("/usr/share/fonts/TTF/FiraSans-Regular.ttf")).unwrap(), // font: Font::new(include_bytes!("/usr/share/fonts/TTF/FiraSans-Regular.ttf")).unwrap(),
last_draw: Instant::now(), last_draw: Instant::now(),
modifiers: ModifiersState::default(), modifiers: ModifiersState::default(),
dragging: None, dragging: None,
no_animations,
line_height, line_height,
last_height: 720.0, last_height: 720.0,
scroll_pixels_multiplier, scroll_pixels_multiplier,
@ -332,20 +416,20 @@ impl Gui {
/// the trait implemented by all Gui elements. /// the trait implemented by all Gui elements.
/// feel free to override the methods you wish to use. /// feel free to override the methods you wish to use.
#[allow(unused)] #[allow(unused)]
pub trait GuiElemTrait: 'static { pub trait GuiElem {
fn config(&self) -> &GuiElemCfg; fn config(&self) -> &GuiElemCfg;
fn config_mut(&mut self) -> &mut GuiElemCfg; fn config_mut(&mut self) -> &mut GuiElemCfg;
/// note: drawing happens from the last to the first element, while priority is from first to last. /// note: drawing happens from the last to the first element, while priority is from first to last.
/// if you wish to add a "high priority" child to a Vec<GuiElem> using push, .rev() the iterator in this method and change draw_rev to false. /// if you wish to add a "high priority" child to a Vec<GuiElem> using push, .rev() the iterator in this method and change draw_rev to false.
fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElemTrait> + '_>; fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_>;
/// defaults to true. /// defaults to true.
fn draw_rev(&self) -> bool { fn draw_rev(&self) -> bool {
true true
} }
fn any(&self) -> &dyn Any; fn any(&self) -> &dyn Any;
fn any_mut(&mut self) -> &mut dyn Any; fn any_mut(&mut self) -> &mut dyn Any;
fn elem(&self) -> &dyn GuiElemTrait; fn elem(&self) -> &dyn GuiElem;
fn elem_mut(&mut self) -> &mut dyn GuiElemTrait; fn elem_mut(&mut self) -> &mut dyn GuiElem;
/// handles drawing. /// handles drawing.
fn draw(&mut self, info: &mut DrawInfo, g: &mut Graphics2D) {} fn draw(&mut self, info: &mut DrawInfo, g: &mut Graphics2D) {}
/// an event that is invoked whenever a mouse button is pressed on the element. /// an event that is invoked whenever a mouse button is pressed on the element.
@ -393,7 +477,9 @@ pub trait GuiElemTrait: 'static {
} }
fn updated_library(&mut self) {} fn updated_library(&mut self) {}
fn updated_queue(&mut self) {} fn updated_queue(&mut self) {}
}
impl<T: GuiElem + ?Sized> GuiElemInternal for T {}
pub(crate) trait GuiElemInternal: GuiElem {
fn _draw(&mut self, info: &mut DrawInfo, g: &mut Graphics2D) { fn _draw(&mut self, info: &mut DrawInfo, g: &mut Graphics2D) {
if !self.config_mut().enabled { if !self.config_mut().enabled {
return; return;
@ -436,7 +522,7 @@ pub trait GuiElemTrait: 'static {
self.config_mut().pixel_pos = std::mem::replace(&mut info.pos, ppos); self.config_mut().pixel_pos = std::mem::replace(&mut info.pos, ppos);
} }
/// recursively applies the function to all gui elements below and including this one /// recursively applies the function to all gui elements below and including this one
fn _recursive_all(&mut self, f: &mut dyn FnMut(&mut dyn GuiElemTrait)) { fn _recursive_all(&mut self, f: &mut dyn FnMut(&mut dyn GuiElem)) {
f(self.elem_mut()); f(self.elem_mut());
for c in self.children() { for c in self.children() {
c._recursive_all(f); c._recursive_all(f);
@ -444,7 +530,7 @@ pub trait GuiElemTrait: 'static {
} }
fn _mouse_event( fn _mouse_event(
&mut self, &mut self,
condition: &mut dyn FnMut(&mut dyn GuiElemTrait) -> Option<Vec<GuiAction>>, condition: &mut dyn FnMut(&mut dyn GuiElem) -> Option<Vec<GuiAction>>,
pos: Vec2, pos: Vec2,
) -> Option<Vec<GuiAction>> { ) -> Option<Vec<GuiAction>> {
for c in &mut self.children() { for c in &mut self.children() {
@ -483,7 +569,7 @@ pub trait GuiElemTrait: 'static {
) -> Option<Vec<GuiAction>> { ) -> Option<Vec<GuiAction>> {
if down { if down {
self._mouse_event( self._mouse_event(
&mut |v: &mut dyn GuiElemTrait| { &mut |v: &mut dyn GuiElem| {
if v.config().mouse_events { if v.config().mouse_events {
match button { match button {
MouseButton::Left => v.config_mut().mouse_down.0 = true, MouseButton::Left => v.config_mut().mouse_down.0 = true,
@ -501,7 +587,7 @@ pub trait GuiElemTrait: 'static {
} else { } else {
let mut vec = vec![]; let mut vec = vec![];
if let Some(a) = self._mouse_event( if let Some(a) = self._mouse_event(
&mut |v: &mut dyn GuiElemTrait| { &mut |v: &mut dyn GuiElem| {
let down = v.config().mouse_down; let down = v.config().mouse_down;
if v.config().mouse_events if v.config().mouse_events
&& ((button == MouseButton::Left && down.0) && ((button == MouseButton::Left && down.0)
@ -545,8 +631,8 @@ pub trait GuiElemTrait: 'static {
} }
fn _keyboard_event( fn _keyboard_event(
&mut self, &mut self,
f_focus: &mut dyn FnMut(&mut dyn GuiElemTrait, &mut Vec<GuiAction>), f_focus: &mut dyn FnMut(&mut dyn GuiElem, &mut Vec<GuiAction>),
f_watch: &mut dyn FnMut(&mut dyn GuiElemTrait, &mut Vec<GuiAction>), f_watch: &mut dyn FnMut(&mut dyn GuiElem, &mut Vec<GuiAction>),
) -> Vec<GuiAction> { ) -> Vec<GuiAction> {
let mut o = vec![]; let mut o = vec![];
self._keyboard_event_inner(&mut Some(f_focus), f_watch, &mut o, true); self._keyboard_event_inner(&mut Some(f_focus), f_watch, &mut o, true);
@ -554,8 +640,8 @@ pub trait GuiElemTrait: 'static {
} }
fn _keyboard_event_inner( fn _keyboard_event_inner(
&mut self, &mut self,
f_focus: &mut Option<&mut dyn FnMut(&mut dyn GuiElemTrait, &mut Vec<GuiAction>)>, f_focus: &mut Option<&mut dyn FnMut(&mut dyn GuiElem, &mut Vec<GuiAction>)>,
f_watch: &mut dyn FnMut(&mut dyn GuiElemTrait, &mut Vec<GuiAction>), f_watch: &mut dyn FnMut(&mut dyn GuiElem, &mut Vec<GuiAction>),
events: &mut Vec<GuiAction>, events: &mut Vec<GuiAction>,
focus: bool, focus: bool,
) { ) {
@ -620,28 +706,149 @@ pub trait GuiElemTrait: 'static {
index != usize::MAX || wants index != usize::MAX || wants
} }
} }
pub trait GuiElemChildren {
fn iter(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElemTrait> + '_>; pub trait GuiElemWrapper {
fn as_elem(&self) -> &dyn GuiElem;
fn as_elem_mut(&mut self) -> &mut dyn GuiElem;
} }
impl<T: GuiElemTrait> GuiElemChildren for T { impl<T: GuiElem> GuiElemWrapper for Box<T> {
fn iter(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElemTrait> + '_> { fn as_elem(&self) -> &dyn GuiElem {
Box::new([self.elem_mut()].into_iter()) self.as_ref()
}
fn as_elem_mut(&mut self) -> &mut dyn GuiElem {
self.as_mut()
} }
} }
impl<A: GuiElemTrait, B: GuiElemTrait> GuiElemChildren for (A, B) { impl GuiElemWrapper for Box<dyn GuiElem> {
fn iter(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElemTrait> + '_> { fn as_elem(&self) -> &dyn GuiElem {
self.as_ref()
}
fn as_elem_mut(&mut self) -> &mut dyn GuiElem {
self.as_mut()
}
}
impl<T: GuiElemWrapper> GuiElem for T {
fn config(&self) -> &GuiElemCfg {
self.as_elem().config()
}
fn config_mut(&mut self) -> &mut GuiElemCfg {
self.as_elem_mut().config_mut()
}
fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
self.as_elem_mut().children()
}
fn draw_rev(&self) -> bool {
self.as_elem().draw_rev()
}
fn any(&self) -> &dyn Any {
self.as_elem().any()
}
fn any_mut(&mut self) -> &mut dyn Any {
self.as_elem_mut().any_mut()
}
fn elem(&self) -> &dyn GuiElem {
self.as_elem().elem()
}
fn elem_mut(&mut self) -> &mut dyn GuiElem {
self.as_elem_mut().elem_mut()
}
fn draw(&mut self, info: &mut DrawInfo, g: &mut Graphics2D) {
self.as_elem_mut().draw(info, g)
}
fn mouse_down(&mut self, button: MouseButton) -> Vec<GuiAction> {
self.as_elem_mut().mouse_down(button)
}
fn mouse_up(&mut self, button: MouseButton) -> Vec<GuiAction> {
self.as_elem_mut().mouse_up(button)
}
fn mouse_pressed(&mut self, button: MouseButton) -> Vec<GuiAction> {
self.as_elem_mut().mouse_pressed(button)
}
fn mouse_wheel(&mut self, diff: f32) -> Vec<GuiAction> {
self.as_elem_mut().mouse_wheel(diff)
}
fn char_watch(&mut self, modifiers: ModifiersState, key: char) -> Vec<GuiAction> {
self.as_elem_mut().char_watch(modifiers, key)
}
fn char_focus(&mut self, modifiers: ModifiersState, key: char) -> Vec<GuiAction> {
self.as_elem_mut().char_focus(modifiers, key)
}
fn key_watch(
&mut self,
modifiers: ModifiersState,
down: bool,
key: Option<VirtualKeyCode>,
scan: KeyScancode,
) -> Vec<GuiAction> {
self.as_elem_mut().key_watch(modifiers, down, key, scan)
}
fn key_focus(
&mut self,
modifiers: ModifiersState,
down: bool,
key: Option<VirtualKeyCode>,
scan: KeyScancode,
) -> Vec<GuiAction> {
self.as_elem_mut().key_focus(modifiers, down, key, scan)
}
fn dragged(&mut self, dragged: Dragging) -> Vec<GuiAction> {
self.as_elem_mut().dragged(dragged)
}
fn updated_library(&mut self) {
self.as_elem_mut().updated_library()
}
fn updated_queue(&mut self) {
self.as_elem_mut().updated_queue()
}
}
pub trait GuiElemChildren {
fn iter(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_>;
fn len(&self) -> usize;
}
impl GuiElemChildren for () {
fn iter(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
Box::new([].into_iter())
}
fn len(&self) -> usize {
0
}
}
impl<const N: usize, T: GuiElem> GuiElemChildren for [T; N] {
fn iter(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
Box::new(self.iter_mut().map(|v| v.elem_mut()))
}
fn len(&self) -> usize {
N
}
}
impl<T: GuiElem> GuiElemChildren for Vec<T> {
fn iter(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
Box::new(self.iter_mut().map(|v| v.elem_mut()))
}
fn len(&self) -> usize {
self.len()
}
}
impl<A: GuiElem, B: GuiElem> GuiElemChildren for (A, B) {
fn iter(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
Box::new([self.0.elem_mut(), self.1.elem_mut()].into_iter()) Box::new([self.0.elem_mut(), self.1.elem_mut()].into_iter())
} }
} fn len(&self) -> usize {
impl<A: GuiElemTrait, B: GuiElemTrait, C: GuiElemTrait> GuiElemChildren for (A, B, C) { 2
fn iter(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElemTrait> + '_> {
Box::new([self.0.elem_mut(), self.1.elem_mut(), self.2.elem_mut()].into_iter())
} }
} }
impl<A: GuiElemTrait, B: GuiElemTrait, C: GuiElemTrait, D: GuiElemTrait> GuiElemChildren impl<A: GuiElem, B: GuiElem, C: GuiElem> GuiElemChildren for (A, B, C) {
for (A, B, C, D) fn iter(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
{ Box::new([self.0.elem_mut(), self.1.elem_mut(), self.2.elem_mut()].into_iter())
fn iter(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElemTrait> + '_> { }
fn len(&self) -> usize {
3
}
}
impl<A: GuiElem, B: GuiElem, C: GuiElem, D: GuiElem> GuiElemChildren for (A, B, C, D) {
fn iter(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
Box::new( Box::new(
[ [
self.0.elem_mut(), self.0.elem_mut(),
@ -652,11 +859,14 @@ impl<A: GuiElemTrait, B: GuiElemTrait, C: GuiElemTrait, D: GuiElemTrait> GuiElem
.into_iter(), .into_iter(),
) )
} }
fn len(&self) -> usize {
4
}
} }
impl<A: GuiElemTrait, B: GuiElemTrait, C: GuiElemTrait, D: GuiElemTrait, E: GuiElemTrait> impl<A: GuiElem, B: GuiElem, C: GuiElem, D: GuiElem, E: GuiElem> GuiElemChildren
GuiElemChildren for (A, B, C, D, E) for (A, B, C, D, E)
{ {
fn iter(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElemTrait> + '_> { fn iter(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
Box::new( Box::new(
[ [
self.0.elem_mut(), self.0.elem_mut(),
@ -668,6 +878,9 @@ impl<A: GuiElemTrait, B: GuiElemTrait, C: GuiElemTrait, D: GuiElemTrait, E: GuiE
.into_iter(), .into_iter(),
) )
} }
fn len(&self) -> usize {
5
}
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -703,6 +916,7 @@ pub struct GuiElemCfg {
/// if this is true, things can be dragged into this element via drag-n-drop /// if this is true, things can be dragged into this element via drag-n-drop
pub drag_target: bool, pub drag_target: bool,
} }
#[allow(unused)]
impl GuiElemCfg { impl GuiElemCfg {
pub fn at(pos: Rectangle) -> Self { pub fn at(pos: Rectangle) -> Self {
Self { Self {
@ -757,15 +971,17 @@ impl Default for GuiElemCfg {
} }
} }
} }
#[allow(unused)]
pub enum GuiAction { pub enum GuiAction {
OpenMain, OpenMain,
SetIdle(bool), SetIdle(bool),
SetAnimationsDisabled(bool),
OpenSettings(bool), OpenSettings(bool),
OpenEditPanel(Box<dyn GuiElemTrait>), ShowNotification(Box<dyn FnOnce(&NotifOverlay) -> (Box<dyn GuiElem>, NotifInfo) + Send>),
CloseEditPanel,
/// Build the GuiAction(s) later, when we have access to the Database (can turn an AlbumId into a QueueContent::Folder, etc) /// Build the GuiAction(s) later, when we have access to the Database (can turn an AlbumId into a QueueContent::Folder, etc)
Build(Box<dyn FnOnce(&mut Database) -> Vec<Self>>), Build(Box<dyn FnOnce(&mut Database) -> Vec<Self>>),
SendToServer(Command), SendToServer(Command),
ContextMenu(Option<Box<dyn GuiElem>>),
/// unfocuses all gui elements, then assigns keyboard focus to one with config().request_keyboard_focus == true if there is one. /// unfocuses all gui elements, then assigns keyboard focus to one with config().request_keyboard_focus == true if there is one.
ResetKeyboardFocus, ResetKeyboardFocus,
SetDragging( SetDragging(
@ -791,6 +1007,7 @@ pub enum Dragging {
/// GuiElems have access to this within draw. /// GuiElems have access to this within draw.
/// Except for `actions`, they should not change any of these values - GuiElem::draw will handle everything automatically. /// Except for `actions`, they should not change any of these values - GuiElem::draw will handle everything automatically.
pub struct DrawInfo<'a> { pub struct DrawInfo<'a> {
pub time: Instant,
pub actions: Vec<GuiAction>, pub actions: Vec<GuiAction>,
pub pos: Rectangle, pub pos: Rectangle,
pub database: &'a mut Database, pub database: &'a mut Database,
@ -800,7 +1017,8 @@ pub struct DrawInfo<'a> {
pub mouse_pos: Vec2, pub mouse_pos: Vec2,
pub helper: Option<&'a mut WindowHelper<GuiEvent>>, pub helper: Option<&'a mut WindowHelper<GuiEvent>>,
pub get_con: Arc<Mutex<get::Client<TcpStream>>>, pub get_con: Arc<Mutex<get::Client<TcpStream>>>,
pub covers: &'a mut HashMap<CoverId, GuiCover>, pub covers: &'a mut HashMap<CoverId, GuiServerImage>,
pub custom_images: &'a mut HashMap<String, GuiServerImage>,
pub has_keyboard_focus: bool, pub has_keyboard_focus: bool,
pub child_has_keyboard_focus: bool, pub child_has_keyboard_focus: bool,
/// the height of one line of text (in pixels) /// the height of one line of text (in pixels)
@ -809,7 +1027,9 @@ pub struct DrawInfo<'a> {
Dragging, Dragging,
Option<Box<dyn FnMut(&mut DrawInfo, &mut Graphics2D)>>, Option<Box<dyn FnMut(&mut DrawInfo, &mut Graphics2D)>>,
)>, )>,
pub gui_config: &'a GuiConfig, pub context_menu: Option<Box<dyn GuiElem>>,
pub gui_config: &'a mut GuiConfig,
pub no_animations: bool,
} }
pub fn adjust_area(outer: &Rectangle, rel_area: &Rectangle) -> Rectangle { pub fn adjust_area(outer: &Rectangle, rel_area: &Rectangle) -> Rectangle {
@ -839,8 +1059,11 @@ impl Gui {
eprintln!("Error sending command to server: {e}"); eprintln!("Error sending command to server: {e}");
} }
} }
GuiAction::ShowNotification(func) => _ = self.notif_sender.send(func),
GuiAction::ResetKeyboardFocus => _ = self.gui._keyboard_reset_focus(), GuiAction::ResetKeyboardFocus => _ = self.gui._keyboard_reset_focus(),
GuiAction::SetDragging(d) => self.dragging = d, GuiAction::SetDragging(d) => self.dragging = d,
GuiAction::SetAnimationsDisabled(d) => self.no_animations = d,
GuiAction::ContextMenu(m) => self.gui.c_context_menu = m,
GuiAction::SetLineHeight(h) => { GuiAction::SetLineHeight(h) => {
self.line_height = h; self.line_height = h;
self.gui self.gui
@ -850,77 +1073,25 @@ impl Gui {
self.covers self.covers
.as_mut() .as_mut()
.unwrap() .unwrap()
.insert(id, GuiCover::new(id, Arc::clone(&self.get_con))); .insert(id, GuiServerImage::new_cover(id, Arc::clone(&self.get_con)));
} }
GuiAction::Do(mut f) => f(self), GuiAction::Do(mut f) => f(self),
GuiAction::Exit => _ = self.event_sender.send_event(GuiEvent::Exit), GuiAction::Exit => _ = self.event_sender.send_event(GuiEvent::Exit),
GuiAction::SetIdle(v) => { GuiAction::SetIdle(v) => {
if let Some(gui) = self self.gui.idle.target = if v { 1.0 } else { 0.0 };
.gui
.any_mut()
.downcast_mut::<WithFocusHotkey<GuiScreen>>()
{
if gui.inner.idle.0 != v {
gui.inner.idle = (v, Some(Instant::now()));
}
}
} }
GuiAction::OpenSettings(v) => { GuiAction::OpenSettings(v) => {
if let Some(gui) = self self.gui.idle.target = 0.0;
.gui self.gui.last_interaction = Instant::now();
.any_mut() if self.gui.settings.0 != v {
.downcast_mut::<WithFocusHotkey<GuiScreen>>() self.gui.settings = (v, Some(Instant::now()));
{
if gui.inner.idle.0 {
gui.inner.idle = (false, Some(Instant::now()));
}
if gui.inner.settings.0 != v {
gui.inner.settings = (v, Some(Instant::now()));
}
} }
} }
GuiAction::OpenMain => { GuiAction::OpenMain => {
if let Some(gui) = self self.gui.idle.target = 0.0;
.gui self.gui.last_interaction = Instant::now();
.any_mut() if self.gui.settings.0 {
.downcast_mut::<WithFocusHotkey<GuiScreen>>() self.gui.settings = (false, Some(Instant::now()));
{
if gui.inner.idle.0 {
gui.inner.idle = (false, Some(Instant::now()));
}
if gui.inner.settings.0 {
gui.inner.settings = (false, Some(Instant::now()));
}
}
}
GuiAction::OpenEditPanel(p) => {
if let Some(gui) = self
.gui
.any_mut()
.downcast_mut::<WithFocusHotkey<GuiScreen>>()
{
if gui.inner.idle.0 {
gui.inner.idle = (false, Some(Instant::now()));
}
if gui.inner.settings.0 {
gui.inner.settings = (false, Some(Instant::now()));
}
gui.inner.open_edit(p);
}
}
GuiAction::CloseEditPanel => {
if let Some(gui) = self
.gui
.any_mut()
.downcast_mut::<WithFocusHotkey<GuiScreen>>()
{
if gui.inner.idle.0 {
gui.inner.idle = (false, Some(Instant::now()));
}
if gui.inner.settings.0 {
gui.inner.settings = (false, Some(Instant::now()));
}
gui.inner.close_edit();
} }
} }
} }
@ -935,8 +1106,10 @@ impl WindowHandler<GuiEvent> for Gui {
); );
let mut dblock = self.database.lock().unwrap(); let mut dblock = self.database.lock().unwrap();
let mut covers = self.covers.take().unwrap(); let mut covers = self.covers.take().unwrap();
let cfg = self.gui_config.take().unwrap(); let mut custom_images = self.custom_images.take().unwrap();
let mut cfg = self.gui_config.take().unwrap();
let mut info = DrawInfo { let mut info = DrawInfo {
time: Instant::now(),
actions: Vec::with_capacity(0), actions: Vec::with_capacity(0),
pos: Rectangle::new(Vec2::ZERO, self.size.into_f32()), pos: Rectangle::new(Vec2::ZERO, self.size.into_f32()),
database: &mut *dblock, database: &mut *dblock,
@ -944,15 +1117,19 @@ impl WindowHandler<GuiEvent> for Gui {
mouse_pos: self.mouse_pos, mouse_pos: self.mouse_pos,
get_con: Arc::clone(&self.get_con), get_con: Arc::clone(&self.get_con),
covers: &mut covers, covers: &mut covers,
custom_images: &mut custom_images,
helper: Some(helper), helper: Some(helper),
has_keyboard_focus: false, has_keyboard_focus: false,
child_has_keyboard_focus: true, child_has_keyboard_focus: true,
line_height: self.line_height, line_height: self.line_height,
no_animations: self.no_animations,
dragging: self.dragging.take(), dragging: self.dragging.take(),
gui_config: &cfg, context_menu: self.gui.c_context_menu.take(),
gui_config: &mut cfg,
}; };
self.gui._draw(&mut info, graphics); self.gui._draw(&mut info, graphics);
let actions = std::mem::replace(&mut info.actions, Vec::with_capacity(0)); let actions = std::mem::replace(&mut info.actions, Vec::with_capacity(0));
self.gui.c_context_menu = info.context_menu.take();
self.dragging = info.dragging.take(); self.dragging = info.dragging.take();
if let Some((d, f)) = &mut self.dragging { if let Some((d, f)) = &mut self.dragging {
if let Some(f) = f { if let Some(f) = f {
@ -991,6 +1168,7 @@ impl WindowHandler<GuiEvent> for Gui {
drop(info); drop(info);
self.gui_config = Some(cfg); self.gui_config = Some(cfg);
self.covers = Some(covers); self.covers = Some(covers);
self.custom_images = Some(custom_images);
drop(dblock); drop(dblock);
for a in actions { for a in actions {
self.exec_gui_action(a); self.exec_gui_action(a);
@ -1172,13 +1350,14 @@ impl WindowHandler<GuiEvent> for Gui {
} }
} }
pub enum GuiCover { pub enum GuiServerImage {
Loading(JoinHandle<Option<Vec<u8>>>), Loading(JoinHandle<Option<Vec<u8>>>),
Loaded(ImageHandle), Loaded(ImageHandle),
Error, Error,
} }
impl GuiCover { #[allow(unused)]
pub fn new(id: CoverId, get_con: Arc<Mutex<get::Client<TcpStream>>>) -> Self { impl GuiServerImage {
pub fn new_cover(id: CoverId, get_con: Arc<Mutex<get::Client<TcpStream>>>) -> Self {
Self::Loading(std::thread::spawn(move || { Self::Loading(std::thread::spawn(move || {
get_con get_con
.lock() .lock()
@ -1188,12 +1367,25 @@ impl GuiCover {
.and_then(|v| v.ok()) .and_then(|v| v.ok())
})) }))
} }
pub fn new_custom_file(file: String, get_con: Arc<Mutex<get::Client<TcpStream>>>) -> Self {
Self::Loading(std::thread::spawn(move || {
get_con
.lock()
.unwrap()
.custom_file(&file)
.ok()
.and_then(|v| v.ok())
}))
}
pub fn get(&self) -> Option<ImageHandle> { pub fn get(&self) -> Option<ImageHandle> {
match self { match self {
Self::Loaded(handle) => Some(handle.clone()), Self::Loaded(handle) => Some(handle.clone()),
Self::Loading(_) | Self::Error => None, Self::Loading(_) | Self::Error => None,
} }
} }
pub fn is_err(&self) -> bool {
matches!(self, Self::Error)
}
pub fn get_init(&mut self, g: &mut Graphics2D) -> Option<ImageHandle> { pub fn get_init(&mut self, g: &mut Graphics2D) -> Option<ImageHandle> {
match self { match self {
Self::Loaded(handle) => Some(handle.clone()), Self::Loaded(handle) => Some(handle.clone()),

View File

@ -0,0 +1,130 @@
use std::{
ops::{Add, AddAssign, Mul, MulAssign, Sub},
time::{Duration, Instant},
};
pub struct AnimationController<F> {
pub last_updated: Instant,
pub value: F,
pub speed: F,
pub max_speed: F,
pub target: F,
/// while the time remaining to finish the animation is above this, we accelerate (higher -> stop acceleration earlier)
pub accel_until: F,
/// if the time remaining to finish the animation drops below this, we decelerate (higher -> start decelerating earlier)
pub decel_while: F,
pub acceleration: F,
}
pub trait Float:
Sized
+ Clone
+ Copy
+ Add<Self, Output = Self>
+ Sub<Self, Output = Self>
+ std::ops::Neg<Output = Self>
+ Mul<Self, Output = Self>
+ MulAssign<Self>
+ AddAssign<Self>
+ PartialOrd<Self>
{
fn zero() -> Self;
/// 1/1000
fn milli() -> Self;
fn duration_secs(d: Duration) -> Self;
fn abs(self) -> Self {
if self < Self::zero() {
-self
} else {
self
}
}
}
impl<F: Float> AnimationController<F> {
pub fn new(
value: F,
target: F,
acceleration: F,
max_speed: F,
accel_until: F,
decel_while: F,
now: Instant,
) -> Self {
AnimationController {
last_updated: now,
value,
speed: F::zero(),
max_speed,
target,
accel_until,
decel_while,
acceleration,
}
}
pub fn ignore_elapsed_time(&mut self, now: Instant) {
self.last_updated = now;
}
pub fn update(&mut self, now: Instant, instant: bool) -> bool {
let changed = if self.target != self.value {
if instant {
self.value = self.target;
} else {
let inc = self.target > self.value;
let seconds = F::duration_secs(now.duration_since(self.last_updated));
let ref1 = self.value + self.speed * self.accel_until;
let ref2 = self.value + self.speed * self.decel_while;
let speed_diff = match (ref1 < self.target, ref2 > self.target) {
(true, false) => self.acceleration,
(false, true) => -self.acceleration,
(true, true) | (false, false) => F::zero(),
};
self.speed += speed_diff;
if self.speed.abs() > self.max_speed {
if self.speed < F::zero() {
self.speed = -self.max_speed;
} else {
self.speed = self.max_speed;
}
}
self.value += self.speed * seconds;
self.speed += speed_diff;
if (self.target - self.value).abs() < self.speed * F::milli()
|| inc != (self.target > self.value)
{
// overshoot or target reached
self.value = self.target;
self.speed = F::zero();
}
}
true
} else {
false
};
self.last_updated = now;
changed
}
}
impl Float for f32 {
fn zero() -> Self {
0.0
}
fn milli() -> Self {
0.001
}
fn duration_secs(d: Duration) -> Self {
d.as_secs_f32().min(0.1)
}
}
impl Float for f64 {
fn zero() -> Self {
0.0
}
fn milli() -> Self {
0.001
}
fn duration_secs(d: Duration) -> Self {
d.as_secs_f64().min(0.1)
}
}

View File

@ -3,7 +3,7 @@ use std::{sync::Arc, time::Instant};
use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle, window::MouseButton}; use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle, window::MouseButton};
use crate::{ use crate::{
gui::{DrawInfo, GuiAction, GuiElemCfg, GuiElemTrait}, gui::{DrawInfo, GuiAction, GuiElem, GuiElemCfg, GuiElemChildren},
gui_text::Label, gui_text::Label,
}; };
@ -15,24 +15,20 @@ Mostly containers for other GuiElems.
*/ */
/// A simple container for zero, one, or multiple child GuiElems. Can optionally fill the background with a color. /// A simple container for zero, one, or multiple child GuiElems. Can optionally fill the background with a color.
pub struct Panel { pub struct Panel<C: GuiElemChildren> {
config: GuiElemCfg, config: GuiElemCfg,
pub children: Vec<Box<dyn GuiElemTrait>>, pub children: C,
pub background: Option<Color>, pub background: Option<Color>,
} }
impl Panel { impl<C: GuiElemChildren> Panel<C> {
pub fn new(config: GuiElemCfg, children: Vec<Box<dyn GuiElemTrait>>) -> Self { pub fn new(config: GuiElemCfg, children: C) -> Self {
Self { Self {
config, config,
children, children,
background: None, background: None,
} }
} }
pub fn with_background( pub fn with_background(config: GuiElemCfg, children: C, background: Color) -> Self {
config: GuiElemCfg,
children: Vec<Box<dyn GuiElemTrait>>,
background: Color,
) -> Self {
Self { Self {
config, config,
children, children,
@ -40,15 +36,15 @@ impl Panel {
} }
} }
} }
impl GuiElemTrait for Panel { impl<C: GuiElemChildren + 'static> GuiElem for Panel<C> {
fn config(&self) -> &GuiElemCfg { fn config(&self) -> &GuiElemCfg {
&self.config &self.config
} }
fn config_mut(&mut self) -> &mut GuiElemCfg { fn config_mut(&mut self) -> &mut GuiElemCfg {
&mut self.config &mut self.config
} }
fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElemTrait> + '_> { fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
Box::new(self.children.iter_mut().map(|v| v.as_mut())) self.children.iter()
} }
fn any(&self) -> &dyn std::any::Any { fn any(&self) -> &dyn std::any::Any {
self self
@ -56,10 +52,10 @@ impl GuiElemTrait for Panel {
fn any_mut(&mut self) -> &mut dyn std::any::Any { fn any_mut(&mut self) -> &mut dyn std::any::Any {
self self
} }
fn elem(&self) -> &dyn GuiElemTrait { fn elem(&self) -> &dyn GuiElem {
self self
} }
fn elem_mut(&mut self) -> &mut dyn GuiElemTrait { fn elem_mut(&mut self) -> &mut dyn GuiElem {
self self
} }
fn draw(&mut self, info: &mut DrawInfo, g: &mut speedy2d::Graphics2D) { fn draw(&mut self, info: &mut DrawInfo, g: &mut speedy2d::Graphics2D) {
@ -70,24 +66,25 @@ impl GuiElemTrait for Panel {
} }
#[derive(Clone)] #[derive(Clone)]
pub struct Square<T: GuiElemTrait> { pub struct Square<T: GuiElem> {
config: GuiElemCfg, config: GuiElemCfg,
pub inner: T, pub inner: T,
} }
impl<T: GuiElemTrait> Square<T> { #[allow(unused)]
impl<T: GuiElem> Square<T> {
pub fn new(mut config: GuiElemCfg, inner: T) -> Self { pub fn new(mut config: GuiElemCfg, inner: T) -> Self {
config.redraw = true; config.redraw = true;
Self { config, inner } Self { config, inner }
} }
} }
impl<T: GuiElemTrait> GuiElemTrait for Square<T> { impl<T: GuiElem + 'static> GuiElem for Square<T> {
fn config(&self) -> &GuiElemCfg { fn config(&self) -> &GuiElemCfg {
&self.config &self.config
} }
fn config_mut(&mut self) -> &mut GuiElemCfg { fn config_mut(&mut self) -> &mut GuiElemCfg {
&mut self.config &mut self.config
} }
fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElemTrait> + '_> { fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
Box::new([self.inner.elem_mut()].into_iter()) Box::new([self.inner.elem_mut()].into_iter())
} }
fn any(&self) -> &dyn std::any::Any { fn any(&self) -> &dyn std::any::Any {
@ -96,10 +93,10 @@ impl<T: GuiElemTrait> GuiElemTrait for Square<T> {
fn any_mut(&mut self) -> &mut dyn std::any::Any { fn any_mut(&mut self) -> &mut dyn std::any::Any {
self self
} }
fn elem(&self) -> &dyn GuiElemTrait { fn elem(&self) -> &dyn GuiElem {
self self
} }
fn elem_mut(&mut self) -> &mut dyn GuiElemTrait { fn elem_mut(&mut self) -> &mut dyn GuiElem {
self self
} }
fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) { fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) {
@ -121,9 +118,10 @@ impl<T: GuiElemTrait> GuiElemTrait for Square<T> {
} }
} }
pub struct ScrollBox { pub struct ScrollBox<C: GuiElemChildren> {
config: GuiElemCfg, config: GuiElemCfg,
pub children: Vec<(Box<dyn GuiElemTrait>, f32)>, pub children: C,
pub children_heights: Vec<f32>,
pub size_unit: ScrollBoxSizeUnit, pub size_unit: ScrollBoxSizeUnit,
pub scroll_target: f32, pub scroll_target: f32,
pub scroll_display: f32, pub scroll_display: f32,
@ -136,20 +134,22 @@ pub struct ScrollBox {
mouse_scroll_margin_right: f32, mouse_scroll_margin_right: f32,
} }
#[derive(Clone)] #[derive(Clone)]
#[allow(unused)]
pub enum ScrollBoxSizeUnit { pub enum ScrollBoxSizeUnit {
Relative, Relative,
Pixels, Pixels,
} }
impl ScrollBox { impl<C: GuiElemChildren> ScrollBox<C> {
pub fn new( pub fn new(
config: GuiElemCfg, config: GuiElemCfg,
size_unit: ScrollBoxSizeUnit, size_unit: ScrollBoxSizeUnit,
children: Vec<(Box<dyn GuiElemTrait>, f32)>, children: C,
children_heights: Vec<f32>,
) -> Self { ) -> Self {
// config.redraw = true;
Self { Self {
config: config.w_scroll().w_mouse(), config: config.w_scroll().w_mouse(),
children, children,
children_heights,
size_unit, size_unit,
scroll_target: 0.0, scroll_target: 0.0,
scroll_display: 0.0, scroll_display: 0.0,
@ -163,21 +163,15 @@ impl ScrollBox {
} }
} }
} }
impl GuiElemTrait for ScrollBox { impl<C: GuiElemChildren + 'static> GuiElem for ScrollBox<C> {
fn config(&self) -> &GuiElemCfg { fn config(&self) -> &GuiElemCfg {
&self.config &self.config
} }
fn config_mut(&mut self) -> &mut GuiElemCfg { fn config_mut(&mut self) -> &mut GuiElemCfg {
&mut self.config &mut self.config
} }
fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElemTrait> + '_> { fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
Box::new( self.children.iter()
self.children
.iter_mut()
.map(|(v, _)| v.as_mut())
.skip_while(|v| v.config().pos.bottom_right().y < 0.0)
.take_while(|v| v.config().pos.top_left().y <= 1.0),
)
} }
fn draw_rev(&self) -> bool { fn draw_rev(&self) -> bool {
false false
@ -188,10 +182,10 @@ impl GuiElemTrait for ScrollBox {
fn any_mut(&mut self) -> &mut dyn std::any::Any { fn any_mut(&mut self) -> &mut dyn std::any::Any {
self self
} }
fn elem(&self) -> &dyn GuiElemTrait { fn elem(&self) -> &dyn GuiElem {
self self
} }
fn elem_mut(&mut self) -> &mut dyn GuiElemTrait { fn elem_mut(&mut self) -> &mut dyn GuiElem {
self self
} }
fn draw(&mut self, info: &mut DrawInfo, g: &mut speedy2d::Graphics2D) { fn draw(&mut self, info: &mut DrawInfo, g: &mut speedy2d::Graphics2D) {
@ -204,22 +198,37 @@ impl GuiElemTrait for ScrollBox {
} else if self.scroll_target < 0.0 { } else if self.scroll_target < 0.0 {
self.scroll_target = 0.0; self.scroll_target = 0.0;
} }
self.scroll_display = 0.2 * self.scroll_target + 0.8 * self.scroll_display; if self.scroll_target != self.scroll_display {
if self.scroll_display != self.scroll_target {
self.config.redraw = true; self.config.redraw = true;
if (self.scroll_display - self.scroll_target).abs() < 1.0 / info.pos.height() { if info.no_animations {
self.scroll_display = self.scroll_target; self.scroll_display = self.scroll_target;
} else if let Some(h) = &info.helper { } else {
h.request_redraw(); self.scroll_display = 0.2 * self.scroll_target + 0.8 * self.scroll_display;
if (self.scroll_display - self.scroll_target).abs() < 1.0 / info.pos.height() {
self.scroll_display = self.scroll_target;
} else if let Some(h) = &info.helper {
h.request_redraw();
}
} }
} }
// recalculate positions // 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();
while self.children_heights.len() < target {
self.children_heights.push(0.0);
}
while self.children_heights.len() > target {
self.children_heights.pop();
}
}
//
self.mouse_scroll_margin_right = info.line_height * 0.2; self.mouse_scroll_margin_right = info.line_height * 0.2;
let max_x = 1.0 - self.mouse_scroll_margin_right / info.pos.width(); let max_x = 1.0 - self.mouse_scroll_margin_right / info.pos.width();
self.config.redraw = false; self.config.redraw = false;
let mut y_pos = -self.scroll_display; let mut y_pos = -self.scroll_display;
for (e, h) in self.children.iter_mut() { for (e, h) in self.children.iter().zip(self.children_heights.iter()) {
let h_rel = self.size_unit.to_rel(*h, info.pos.height()); let h_rel = self.size_unit.to_rel(*h, info.pos.height());
let y_rel = self.size_unit.to_rel(y_pos, info.pos.height()); let y_rel = self.size_unit.to_rel(y_pos, info.pos.height());
if y_rel + h_rel >= 0.0 && y_rel <= 1.0 { if y_rel + h_rel >= 0.0 && y_rel <= 1.0 {
@ -315,17 +324,17 @@ impl ScrollBoxSizeUnit {
} }
} }
pub struct Button { pub struct Button<C: GuiElemChildren> {
config: GuiElemCfg, config: GuiElemCfg,
pub children: Vec<Box<dyn GuiElemTrait>>, pub children: C,
action: Arc<dyn Fn(&mut Self) -> Vec<GuiAction> + 'static>, action: Arc<dyn Fn(&mut Self) -> Vec<GuiAction> + 'static>,
} }
impl Button { impl<C: GuiElemChildren> Button<C> {
/// automatically adds w_mouse to config /// automatically adds w_mouse to config
pub fn new<F: Fn(&mut Self) -> Vec<GuiAction> + 'static>( pub fn new<F: Fn(&mut Self) -> Vec<GuiAction> + 'static>(
config: GuiElemCfg, config: GuiElemCfg,
action: F, action: F,
children: Vec<Box<dyn GuiElemTrait>>, children: C,
) -> Self { ) -> Self {
Self { Self {
config: config.w_mouse(), config: config.w_mouse(),
@ -334,15 +343,15 @@ impl Button {
} }
} }
} }
impl GuiElemTrait for Button { impl<C: GuiElemChildren + 'static> GuiElem for Button<C> {
fn config(&self) -> &GuiElemCfg { fn config(&self) -> &GuiElemCfg {
&self.config &self.config
} }
fn config_mut(&mut self) -> &mut GuiElemCfg { fn config_mut(&mut self) -> &mut GuiElemCfg {
&mut self.config &mut self.config
} }
fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElemTrait> + '_> { fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
Box::new(self.children.iter_mut().map(|v| v.as_mut())) self.children.iter()
} }
fn any(&self) -> &dyn std::any::Any { fn any(&self) -> &dyn std::any::Any {
self self
@ -350,10 +359,10 @@ impl GuiElemTrait for Button {
fn any_mut(&mut self) -> &mut dyn std::any::Any { fn any_mut(&mut self) -> &mut dyn std::any::Any {
self self
} }
fn elem(&self) -> &dyn GuiElemTrait { fn elem(&self) -> &dyn GuiElem {
self self
} }
fn elem_mut(&mut self) -> &mut dyn GuiElemTrait { fn elem_mut(&mut self) -> &mut dyn GuiElem {
self self
} }
fn mouse_pressed(&mut self, button: MouseButton) -> Vec<GuiAction> { fn mouse_pressed(&mut self, button: MouseButton) -> Vec<GuiAction> {
@ -381,7 +390,7 @@ impl GuiElemTrait for Button {
pub struct Slider { pub struct Slider {
pub config: GuiElemCfg, pub config: GuiElemCfg,
pub children: Vec<Box<dyn GuiElemTrait>>, pub children: Vec<Box<dyn GuiElem>>,
pub slider_pos: Rectangle, pub slider_pos: Rectangle,
pub min: f64, pub min: f64,
pub max: f64, pub max: f64,
@ -395,6 +404,7 @@ pub struct Slider {
pub display_since: Option<Instant>, pub display_since: Option<Instant>,
pub on_update: Arc<dyn Fn(&mut Self, &mut DrawInfo)>, pub on_update: Arc<dyn Fn(&mut Self, &mut DrawInfo)>,
} }
#[allow(unused)]
impl Slider { impl Slider {
/// returns true if the value of the slider has changed since the last time this function was called. /// returns true if the value of the slider has changed since the last time this function was called.
/// this is usually used by the closure responsible for directly handling updates. if you wish to check for changes /// this is usually used by the closure responsible for directly handling updates. if you wish to check for changes
@ -418,7 +428,7 @@ impl Slider {
min: f64, min: f64,
max: f64, max: f64,
val: f64, val: f64,
children: Vec<Box<dyn GuiElemTrait>>, children: Vec<Box<dyn GuiElem>>,
on_update: F, on_update: F,
) -> Self { ) -> Self {
Self { Self {
@ -507,15 +517,15 @@ impl Slider {
) )
} }
} }
impl GuiElemTrait for Slider { impl GuiElem for Slider {
fn config(&self) -> &GuiElemCfg { fn config(&self) -> &GuiElemCfg {
&self.config &self.config
} }
fn config_mut(&mut self) -> &mut GuiElemCfg { fn config_mut(&mut self) -> &mut GuiElemCfg {
&mut self.config &mut self.config
} }
fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElemTrait> + '_> { fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
Box::new(self.children.iter_mut().map(|v| v.as_mut())) Box::new(self.children.iter_mut().map(|v| v.elem_mut()))
} }
fn any(&self) -> &dyn std::any::Any { fn any(&self) -> &dyn std::any::Any {
self self
@ -523,10 +533,10 @@ impl GuiElemTrait for Slider {
fn any_mut(&mut self) -> &mut dyn std::any::Any { fn any_mut(&mut self) -> &mut dyn std::any::Any {
self self
} }
fn elem(&self) -> &dyn GuiElemTrait { fn elem(&self) -> &dyn GuiElem {
self self
} }
fn elem_mut(&mut self) -> &mut dyn GuiElemTrait { fn elem_mut(&mut self) -> &mut dyn GuiElem {
self self
} }
fn draw(&mut self, info: &mut DrawInfo, g: &mut speedy2d::Graphics2D) { fn draw(&mut self, info: &mut DrawInfo, g: &mut speedy2d::Graphics2D) {

View File

@ -1,637 +0,0 @@
use std::{
collections::{HashMap, HashSet},
sync::mpsc,
};
use musicdb_lib::{
data::{
album::Album, artist::Artist, queue::QueueContent, song::Song, AlbumId, ArtistId, CoverId,
SongId,
},
server::Command,
};
use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle};
use crate::{
gui::{Dragging, DrawInfo, GuiAction, GuiElemCfg, GuiElemTrait},
gui_base::{Button, Panel, ScrollBox},
gui_text::{Label, TextField},
};
pub struct GuiEdit {
config: GuiElemCfg,
children: Vec<Box<dyn GuiElemTrait>>,
editable: Editable,
editing: Editing,
reload: bool,
rebuild_main: bool,
rebuild_changes: bool,
send: bool,
apply_change: mpsc::Sender<Box<dyn FnOnce(&mut Self)>>,
change_recv: mpsc::Receiver<Box<dyn FnOnce(&mut Self)>>,
}
#[derive(Clone)]
pub enum Editable {
Artist(Vec<ArtistId>),
Album(Vec<AlbumId>),
Song(Vec<SongId>),
}
#[derive(Clone)]
pub enum Editing {
NotLoaded,
Artist(Vec<Artist>, Vec<ArtistChange>),
Album(Vec<Album>, Vec<AlbumChange>),
Song(Vec<Song>, Vec<SongChange>),
}
#[derive(Clone)]
pub enum ArtistChange {
SetName(String),
SetCover(Option<CoverId>),
AddAlbum(AlbumId),
}
#[derive(Clone)]
pub enum AlbumChange {
SetName(String),
SetCover(Option<ArtistId>),
RemoveSong(SongId),
AddSong(SongId),
}
#[derive(Clone)]
pub enum SongChange {
SetTitle(String),
SetCover(Option<ArtistId>),
}
impl GuiEdit {
pub fn new(config: GuiElemCfg, edit: Editable) -> Self {
let (apply_change, change_recv) = mpsc::channel();
let ac1 = apply_change.clone();
let ac2 = apply_change.clone();
Self {
config: config.w_drag_target(),
editable: edit,
editing: Editing::NotLoaded,
reload: true,
rebuild_main: true,
rebuild_changes: true,
send: false,
apply_change,
change_recv,
children: vec![
Box::new(ScrollBox::new(
GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (1.0, 0.6))),
crate::gui_base::ScrollBoxSizeUnit::Pixels,
vec![],
)),
Box::new(ScrollBox::new(
GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.6), (1.0, 0.9))),
crate::gui_base::ScrollBoxSizeUnit::Pixels,
vec![],
)),
Box::new(Button::new(
GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.95), (0.33, 1.0))),
|_| vec![GuiAction::CloseEditPanel],
vec![Box::new(Label::new(
GuiElemCfg::default(),
"Back".to_string(),
Color::WHITE,
None,
Vec2::new(0.5, 0.5),
))],
)),
Box::new(Button::new(
GuiElemCfg::at(Rectangle::from_tuples((0.33, 0.95), (0.67, 1.0))),
move |_| {
_ = ac1.send(Box::new(|s| s.reload = true));
vec![]
},
vec![Box::new(Label::new(
GuiElemCfg::default(),
"Reload".to_string(),
Color::WHITE,
None,
Vec2::new(0.5, 0.5),
))],
)),
Box::new(Button::new(
GuiElemCfg::at(Rectangle::from_tuples((0.67, 0.95), (1.0, 1.0))),
move |_| {
_ = ac2.send(Box::new(|s| s.send = true));
vec![]
},
vec![Box::new(Label::new(
GuiElemCfg::default(),
"Send".to_string(),
Color::WHITE,
None,
Vec2::new(0.5, 0.5),
))],
)),
],
}
}
}
impl GuiElemTrait for GuiEdit {
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 GuiElemTrait> + '_> {
Box::new(self.children.iter_mut().map(|v| v.as_mut()))
}
fn any(&self) -> &dyn std::any::Any {
self
}
fn any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn elem(&self) -> &dyn GuiElemTrait {
self
}
fn elem_mut(&mut self) -> &mut dyn GuiElemTrait {
self
}
fn draw(&mut self, info: &mut crate::gui::DrawInfo, g: &mut speedy2d::Graphics2D) {
loop {
if let Ok(func) = self.change_recv.try_recv() {
func(self);
} else {
break;
}
}
if self.send {
self.send = false;
self.rebuild_main = true;
self.rebuild_changes = true;
self.config.redraw = true;
match &mut self.editing {
Editing::NotLoaded => {}
Editing::Artist(v, changes) => {
for change in changes.iter() {
match change {
ArtistChange::SetName(n) => {
for artist in v.iter_mut() {
artist.name = n.clone();
info.actions.push(GuiAction::SendToServer(
Command::ModifyArtist(artist.clone()),
));
}
}
ArtistChange::SetCover(c) => {
for artist in v.iter_mut() {
artist.cover = c.clone();
info.actions.push(GuiAction::SendToServer(
Command::ModifyArtist(artist.clone()),
));
}
}
ArtistChange::AddAlbum(id) => {
// use the first artist for the artist fields
let mut editing = v.first().unwrap().clone();
if let Some(album) = info.database.albums().get(id) {
let mut album = album.clone();
// find the previous artist for this album and remove them
if let Some(prev) = info.database.artists().get(&album.artist) {
let mut prev = prev.clone();
if let Some(i) = prev.albums.iter().position(|v| v == id) {
prev.albums.remove(i);
info.actions.push(GuiAction::SendToServer(
Command::ModifyArtist(prev),
));
}
}
// update the artist field on the album so it points to the new artist
album.artist = editing.id;
info.actions
.push(GuiAction::SendToServer(Command::ModifyAlbum(album)));
// add the album to the artist we are editing
if !editing.albums.contains(id) {
editing.albums.push(*id);
info.actions.push(GuiAction::SendToServer(
Command::ModifyArtist(editing),
));
}
}
}
}
}
}
Editing::Album(v, changes) => {
for v in v {
let mut v = v.clone();
for change in changes.iter() {
todo!()
}
info.actions
.push(GuiAction::SendToServer(Command::ModifyAlbum(v)));
}
}
Editing::Song(v, changes) => {
for v in v {
let mut v = v.clone();
for change in changes.iter() {
todo!()
}
info.actions
.push(GuiAction::SendToServer(Command::ModifySong(v)));
}
}
}
}
if self.reload {
self.reload = false;
let prev = std::mem::replace(&mut self.editing, Editing::NotLoaded);
self.editing = match &self.editable {
Editable::Artist(id) => {
let v = id
.iter()
.filter_map(|id| info.database.artists().get(id).cloned())
.collect::<Vec<_>>();
if !v.is_empty() {
Editing::Artist(
v,
if let Editing::Artist(_, c) = prev {
c
} else {
vec![]
},
)
} else {
Editing::NotLoaded
}
}
Editable::Album(id) => {
let v = id
.iter()
.filter_map(|id| info.database.albums().get(id).cloned())
.collect::<Vec<_>>();
if !v.is_empty() {
Editing::Album(
v,
if let Editing::Album(_, c) = prev {
c
} else {
vec![]
},
)
} else {
Editing::NotLoaded
}
}
Editable::Song(id) => {
let v = id
.iter()
.filter_map(|id| info.database.songs().get(id).cloned())
.collect::<Vec<_>>();
if !v.is_empty() {
Editing::Song(
v,
if let Editing::Song(_, c) = prev {
c
} else {
vec![]
},
)
} else {
Editing::NotLoaded
}
}
};
self.config.redraw = true;
self.rebuild_main = true;
self.rebuild_changes = true;
}
if let Some(sb) = self.children[0].any_mut().downcast_mut::<ScrollBox>() {
for (c, _) in sb.children.iter() {
if let Some(p) = c
.any()
.downcast_ref::<Panel>()
.and_then(|p| p.children.get(0))
.and_then(|e| e.any().downcast_ref::<TextField>())
{
if p.label_input().content.will_redraw() {
if let Some((key, _)) = p.label_hint().content.get_text().split_once(':') {
match (&mut self.editing, key) {
(Editing::Artist(_, changes), "name") => {
let mut c = changes.iter_mut();
loop {
if let Some(c) = c.next() {
if let ArtistChange::SetName(n) = c {
*n = p.label_input().content.get_text().clone();
break;
}
} else {
changes.push(ArtistChange::SetName(
p.label_input().content.get_text().clone(),
));
break;
}
}
self.rebuild_changes = true;
}
(Editing::Artist(_, changes), "cover") => {
let mut c = changes.iter_mut();
loop {
if let Some(c) = c.next() {
if let ArtistChange::SetCover(n) = c {
*n =
p.label_input().content.get_text().parse().ok();
break;
}
} else {
changes.push(ArtistChange::SetCover(
p.label_input().content.get_text().parse().ok(),
));
break;
}
}
self.rebuild_changes = true;
}
_ => {}
}
}
}
}
}
}
if self.rebuild_main {
self.rebuild_main = false;
self.rebuild_main(info);
}
if self.rebuild_changes {
self.rebuild_changes = false;
self.rebuild_changes(info);
}
if self.config.redraw {
self.config.redraw = false;
if let Some(sb) = self.children[0].any_mut().downcast_mut::<ScrollBox>() {
for c in sb.children.iter_mut() {
c.1 = info.line_height;
}
}
}
}
fn dragged(&mut self, dragged: Dragging) -> Vec<GuiAction> {
let dragged = match dragged {
Dragging::Artist(_) | Dragging::Album(_) | Dragging::Song(_) | Dragging::Queues(_) => {
dragged
}
Dragging::Queue(q) => match q.content() {
QueueContent::Song(id) => Dragging::Song(*id),
_ => Dragging::Queue(q),
},
};
match dragged {
Dragging::Artist(id) => {
if let Editing::Artist(a, _) = &self.editing {
self.editable = Editable::Artist(a.iter().map(|v| v.id).chain([id]).collect())
}
}
Dragging::Album(id) => {
if let Editing::Album(a, _) = &self.editing {
self.editable = Editable::Album(a.iter().map(|v| v.id).chain([id]).collect())
}
}
Dragging::Song(id) => {
if let Editing::Song(a, _) = &self.editing {
self.editable = Editable::Song(a.iter().map(|v| v.id).chain([id]).collect())
}
}
Dragging::Queue(_) => return vec![],
Dragging::Queues(_) => return vec![],
}
self.reload = true;
vec![]
}
}
impl GuiEdit {
fn rebuild_main(&mut self, info: &mut DrawInfo) {
if let Some(sb) = self.children[0].any_mut().downcast_mut::<ScrollBox>() {
sb.children.clear();
sb.config_mut().redraw = true;
match &self.editing {
Editing::NotLoaded => {}
Editing::Artist(v, _) => {
// name
let mut names = v
.iter()
.map(|v| &v.name)
.collect::<HashSet<_>>()
.into_iter()
.collect::<Vec<_>>();
names.sort_unstable();
let name = if names.len() == 1 {
format!("name: {}", names[0])
} else {
let mut name = format!("name: {}", names[0]);
for n in names.iter().skip(1) {
name.push_str(" / ");
name.push_str(n);
}
name
};
sb.children.push((
Box::new(Panel::new(
GuiElemCfg::default(),
vec![Box::new(TextField::new(
GuiElemCfg::default(),
name,
Color::LIGHT_GRAY,
Color::WHITE,
))],
)),
info.line_height,
));
// cover
let covers = v.iter().filter_map(|v| v.cover).collect::<Vec<_>>();
let cover = if covers.is_empty() {
format!("cover: None")
} else {
let mut cover = format!("cover: {}", covers[0]);
for c in covers.iter().skip(1) {
cover.push('/');
cover.push_str(&format!("{c}"));
}
cover
};
sb.children.push((
Box::new(Panel::new(
GuiElemCfg::default(),
vec![Box::new(TextField::new(
GuiElemCfg::default(),
cover,
Color::LIGHT_GRAY,
Color::WHITE,
))],
)),
info.line_height,
));
// albums
let mut albums = HashMap::new();
for v in v {
for album in &v.albums {
if let Some(count) = albums.get_mut(album) {
*count += 1;
} else {
albums.insert(*album, 1);
}
}
}
{
fn get_id(s: &mut GuiEdit) -> Option<AlbumId> {
s.children[0]
.children()
.collect::<Vec<_>>()
.into_iter()
.rev()
.nth(2)
.unwrap()
.any_mut()
.downcast_mut::<Panel>()
.unwrap()
.children()
.next()
.unwrap()
.any_mut()
.downcast_mut::<TextField>()
.unwrap()
.label_input()
.content
.get_text()
.parse()
.ok()
}
let add_button = {
let apply_change = self.apply_change.clone();
Box::new(Button::new(
GuiElemCfg::at(Rectangle::from_tuples((0.9, 0.0), (1.0, 1.0))),
move |_| {
_ = apply_change.send(Box::new(move |s| {
if let Some(album_id) = get_id(s) {
if let Editing::Artist(_, c) = &mut s.editing {
if let Some(i) = c.iter().position(|c| {
matches!(c, ArtistChange::AddAlbum(id) if *id == album_id)
}) {
c.remove(i);
}
c.push(ArtistChange::AddAlbum(album_id));
s.rebuild_changes = true;
}
}
}));
vec![]
},
vec![Box::new(Label::new(
GuiElemCfg::default(),
format!("add"),
Color::GREEN,
None,
Vec2::new(0.5, 0.5),
))],
))
};
let name = Box::new(TextField::new(
GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (0.9, 1.0))),
"add album by id".to_string(),
Color::LIGHT_GRAY,
Color::WHITE,
));
sb.children.push((
Box::new(Panel::new(GuiElemCfg::default(), vec![name, add_button])),
info.line_height * 2.0,
));
}
for (album_id, count) in albums {
let album = info.database.albums().get(&album_id);
let name = Box::new(Button::new(
GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (1.0, 1.0))),
move |_| {
vec![GuiAction::OpenEditPanel(Box::new(GuiEdit::new(
GuiElemCfg::default(),
Editable::Album(vec![album_id]),
)))]
},
vec![Box::new(Label::new(
GuiElemCfg::default(),
if let Some(a) = album {
a.name.clone()
} else {
format!("#{album_id}")
},
Color::WHITE,
None,
Vec2::new(0.0, 0.5),
))],
));
sb.children.push((
Box::new(Panel::new(GuiElemCfg::default(), vec![name])),
info.line_height,
));
}
}
Editing::Album(v, _) => {}
Editing::Song(v, _) => {}
}
}
}
fn rebuild_changes(&mut self, info: &mut DrawInfo) {
if let Some(sb) = self.children[1].any_mut().downcast_mut::<ScrollBox>() {
sb.children.clear();
sb.config_mut().redraw = true;
match &self.editing {
Editing::NotLoaded => {}
Editing::Artist(_, a) => {
for (i, v) in a.iter().enumerate() {
let text = match v {
ArtistChange::SetName(v) => format!("set name to \"{v}\""),
ArtistChange::SetCover(c) => {
if let Some(c) = c {
format!("set cover to {c}")
} else {
"remove cover".to_string()
}
}
ArtistChange::AddAlbum(v) => format!("add album {v}"),
};
let s = self.apply_change.clone();
sb.children.push((
Box::new(Button::new(
GuiElemCfg::default(),
move |_| {
_ = s.send(Box::new(move |s| {
if !s.rebuild_changes {
if let Editing::Artist(_, v) = &mut s.editing {
if i < v.len() {
v.remove(i);
}
s.rebuild_changes = true;
}
}
}));
vec![]
},
vec![Box::new(Label::new(
GuiElemCfg::default(),
text,
Color::WHITE,
None,
Vec2::new(0.0, 0.5),
))],
)),
info.line_height,
));
}
}
_ => todo!(),
}
}
}
}
impl Clone for GuiEdit {
fn clone(&self) -> Self {
Self::new(self.config.clone(), self.editable.clone())
}
}

View File

@ -0,0 +1,271 @@
use std::{sync::Arc, time::Instant};
use musicdb_lib::data::ArtistId;
use speedy2d::{color::Color, dimen::Vec2, image::ImageHandle, shape::Rectangle};
use crate::{
gui::{DrawInfo, GuiElem, GuiElemCfg, GuiServerImage},
gui_anim::AnimationController,
gui_playback::{get_right_x, image_display, CurrentInfo},
gui_text::AdvancedLabel,
};
pub struct IdleDisplay {
config: GuiElemCfg,
pub idle_mode: f32,
current_info: CurrentInfo,
current_artist_image: Option<(ArtistId, Option<(String, Option<Option<ImageHandle>>)>)>,
c_top_label: AdvancedLabel,
c_side1_label: AdvancedLabel,
c_side2_label: AdvancedLabel,
cover_aspect_ratio: AnimationController<f32>,
artist_image_aspect_ratio: AnimationController<f32>,
cover_left: f32,
cover_top: f32,
cover_bottom: f32,
/// 0.0 -> same height as cover,
/// 0.5 -> lower half of cover
artist_image_top: f32,
artist_image_to_cover_margin: f32,
}
impl IdleDisplay {
pub fn new(config: GuiElemCfg) -> Self {
Self {
config,
idle_mode: 0.0,
current_info: CurrentInfo::new(),
current_artist_image: None,
c_top_label: AdvancedLabel::new(
GuiElemCfg::at(Rectangle::from_tuples((0.05, 0.02), (0.95, 0.18))),
Vec2::new(0.5, 0.5),
vec![],
),
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![]),
cover_aspect_ratio: AnimationController::new(
1.0,
1.0,
0.01,
1.0,
0.8,
0.6,
Instant::now(),
),
artist_image_aspect_ratio: AnimationController::new(
0.0,
0.0,
0.01,
1.0,
0.8,
0.6,
Instant::now(),
),
cover_left: 0.01,
cover_top: 0.21,
cover_bottom: 0.79,
artist_image_top: 0.5,
artist_image_to_cover_margin: 0.01,
}
}
}
impl GuiElem for IdleDisplay {
fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
Box::new(
[
self.c_top_label.elem_mut(),
self.c_side1_label.elem_mut(),
self.c_side2_label.elem_mut(),
]
.into_iter(),
)
}
fn draw(&mut self, info: &mut DrawInfo, g: &mut speedy2d::Graphics2D) {
// draw background
g.draw_rectangle(
info.pos.clone(),
Color::from_rgba(0.0, 0.0, 0.0, 0.5 + 0.5 * self.idle_mode),
);
// update current_info
self.current_info.update(info, g);
if self.current_info.new_song {
self.current_info.new_song = false;
self.c_top_label.content = if let Some(song) = self.current_info.current_song {
info.gui_config
.idle_top_text
.gen(&info.database, info.database.get_song(&song))
} else {
vec![]
};
self.c_top_label.config_mut().redraw = true;
self.c_side1_label.content = if let Some(song) = self.current_info.current_song {
info.gui_config
.idle_side1_text
.gen(&info.database, info.database.get_song(&song))
} else {
vec![]
};
self.c_side1_label.config_mut().redraw = true;
self.c_side2_label.content = if let Some(song) = self.current_info.current_song {
info.gui_config
.idle_side2_text
.gen(&info.database, info.database.get_song(&song))
} else {
vec![]
};
self.c_side2_label.config_mut().redraw = true;
// check artist
if let Some(artist_id) = self
.current_info
.current_song
.as_ref()
.and_then(|id| info.database.songs().get(id))
.map(|song| song.artist)
{
if self.current_artist_image.is_none()
|| self
.current_artist_image
.as_ref()
.is_some_and(|(a, _)| *a != artist_id)
{
self.current_artist_image = Some((artist_id, None));
self.artist_image_aspect_ratio.target = 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..]);
self.current_artist_image =
Some((artist_id, Some((filename.clone(), None))));
if !info.custom_images.contains_key(&filename) {
info.custom_images.insert(
filename.clone(),
GuiServerImage::new_custom_file(
filename,
Arc::clone(&info.get_con),
),
);
}
break;
}
}
}
}
} else {
if self.current_artist_image.is_some() {
self.current_artist_image = None;
self.artist_image_aspect_ratio.target = 0.0;
}
}
}
if self.current_info.new_cover {
self.current_info.new_cover = false;
match self.current_info.current_cover {
None | Some((_, Some(None))) => {
self.cover_aspect_ratio.target = 0.0;
}
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);
}
}
}
}
// draw cover
if let Some(Some(cover)) = self
.current_info
.current_cover
.as_ref()
.map(|v| v.1.as_ref())
{
image_display(
g,
cover.as_ref(),
info.pos.top_left().x + info.pos.height() * self.cover_left,
info.pos.top_left().y + info.pos.height() * self.cover_top,
info.pos.top_left().y + info.pos.height() * self.cover_bottom,
&mut self.cover_aspect_ratio,
);
}
// draw artist image
if let Some((_, Some((_, Some(img))))) = &self.current_artist_image {
let top = info.pos.top_left().y + info.pos.height() * self.cover_top;
let bottom = info.pos.top_left().y + info.pos.height() * self.cover_bottom;
image_display(
g,
img.as_ref(),
get_right_x(
info.pos.top_left().x + info.pos.height() * self.cover_left,
top,
bottom,
self.cover_aspect_ratio.value,
) + info.pos.height() * self.artist_image_to_cover_margin,
top + (bottom - top) * self.artist_image_top,
bottom,
&mut self.artist_image_aspect_ratio,
);
}
// move children to make space for cover
let ar_updated = self
.cover_aspect_ratio
.update(info.time.clone(), info.no_animations)
| self
.artist_image_aspect_ratio
.update(info.time.clone(), info.no_animations);
if ar_updated || info.pos.size() != self.config.pixel_pos.size() {
if let Some(h) = &info.helper {
h.request_redraw();
}
// make thing be relative to width instead of to height by multiplying with this
let top = self.cover_top;
let bottom = self.cover_bottom;
let left = (get_right_x(self.cover_left, top, bottom, self.cover_aspect_ratio.value)
+ self.artist_image_to_cover_margin)
* info.pos.height()
/ info.pos.width();
let ai_top = top + (bottom - top) * self.artist_image_top;
let max_right = 1.0 - self.cover_left * info.pos.height() / info.pos.width();
self.c_side1_label.config_mut().pos =
Rectangle::from_tuples((left, top), (max_right, ai_top));
let left = get_right_x(
left,
ai_top * info.pos.height() / info.pos.width(),
bottom * info.pos.height() / info.pos.width(),
self.artist_image_aspect_ratio.value,
);
self.c_side2_label.config_mut().pos =
Rectangle::from_tuples((left, ai_top), (max_right, bottom));
}
}
fn config(&self) -> &GuiElemCfg {
&self.config
}
fn config_mut(&mut self) -> &mut GuiElemCfg {
&mut self.config
}
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
}
fn updated_library(&mut self) {
self.current_info.update = true;
}
fn updated_queue(&mut self) {
self.current_info.update = true;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -5,21 +5,21 @@ use std::{
use speedy2d::{color::Color, dimen::Vector2, shape::Rectangle}; use speedy2d::{color::Color, dimen::Vector2, shape::Rectangle};
use crate::gui::{GuiElemCfg, GuiElemTrait}; use crate::gui::{GuiElem, GuiElemCfg};
/// This should be added on top of overything else and set to fullscreen. /// This should be added on top of overything else and set to fullscreen.
/// It will respond to notification events. /// It will respond to notification events.
pub struct NotifOverlay { pub struct NotifOverlay {
config: GuiElemCfg, config: GuiElemCfg,
notifs: Vec<(Box<dyn GuiElemTrait>, NotifInfo)>, notifs: Vec<(Box<dyn GuiElem>, NotifInfo)>,
light: Option<(Instant, Color)>, light: Option<(Instant, Color)>,
receiver: mpsc::Receiver<Box<dyn FnOnce(&Self) -> (Box<dyn GuiElemTrait>, NotifInfo) + Send>>, receiver: mpsc::Receiver<Box<dyn FnOnce(&Self) -> (Box<dyn GuiElem>, NotifInfo) + Send>>,
} }
impl NotifOverlay { impl NotifOverlay {
pub fn new() -> ( pub fn new() -> (
Self, Self,
mpsc::Sender<Box<dyn FnOnce(&Self) -> (Box<dyn GuiElemTrait>, NotifInfo) + Send>>, mpsc::Sender<Box<dyn FnOnce(&Self) -> (Box<dyn GuiElem>, NotifInfo) + Send>>,
) { ) {
let (sender, receiver) = mpsc::channel(); let (sender, receiver) = mpsc::channel();
( (
@ -152,7 +152,7 @@ impl Clone for NotifOverlay {
} }
} }
impl GuiElemTrait for NotifOverlay { impl GuiElem for NotifOverlay {
fn draw(&mut self, info: &mut crate::gui::DrawInfo, g: &mut speedy2d::Graphics2D) { fn draw(&mut self, info: &mut crate::gui::DrawInfo, g: &mut speedy2d::Graphics2D) {
if let Ok(notif) = self.receiver.try_recv() { if let Ok(notif) = self.receiver.try_recv() {
let mut n = notif(self); let mut n = notif(self);
@ -196,8 +196,8 @@ impl GuiElemTrait for NotifOverlay {
// fn children(&mut self) -> Box<dyn Iterator<Item = &mut GuiElem> + '_> { // fn children(&mut self) -> Box<dyn Iterator<Item = &mut GuiElem> + '_> {
// Box::new(self.notifs.iter_mut().map(|(v, _)| v)) // Box::new(self.notifs.iter_mut().map(|(v, _)| v))
// } // }
fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElemTrait> + '_> { fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
Box::new(self.notifs.iter_mut().map(|(v, _)| v.as_mut())) Box::new(self.notifs.iter_mut().map(|(v, _)| v.elem_mut()))
} }
fn any(&self) -> &dyn std::any::Any { fn any(&self) -> &dyn std::any::Any {
self self
@ -205,10 +205,10 @@ impl GuiElemTrait for NotifOverlay {
fn any_mut(&mut self) -> &mut dyn std::any::Any { fn any_mut(&mut self) -> &mut dyn std::any::Any {
self self
} }
fn elem(&self) -> &dyn GuiElemTrait { fn elem(&self) -> &dyn GuiElem {
self self
} }
fn elem_mut(&mut self) -> &mut dyn GuiElemTrait { fn elem_mut(&mut self) -> &mut dyn GuiElem {
self self
} }
} }

View File

@ -1,426 +1,172 @@
use std::{collections::VecDeque, sync::Arc, time::Instant}; use std::{sync::Arc, time::Duration};
use musicdb_lib::{ use musicdb_lib::data::{CoverId, SongId};
data::{CoverId, SongId}, use speedy2d::{color::Color, dimen::Vec2, image::ImageHandle, shape::Rectangle};
server::Command,
};
use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle, window::MouseButton};
use crate::{ use crate::{
gui::{adjust_area, adjust_pos, morph_rect, GuiAction, GuiCover, GuiElemCfg, GuiElemTrait}, gui::{DrawInfo, GuiAction, GuiElemCfg, GuiServerImage},
gui_text::AdvancedLabel, gui_anim::AnimationController,
gui_base::Panel,
gui_notif::NotifInfo,
gui_text::Label,
}; };
/* pub struct CurrentInfo {
pub update: bool,
Components for the StatusBar. pub new_song: bool,
This file could probably have a better name. pub new_cover: bool,
pub current_song: Option<SongId>,
*/ pub current_cover: Option<(CoverId, Option<Option<ImageHandle>>)>,
pub struct CurrentSong {
config: GuiElemCfg,
/// 0: AdvancedLabel for small mode
/// 1: AdvancedLabel for big mode heading
/// 2: AdvancedLabel for big mode info text
children: Vec<Box<dyn GuiElemTrait>>,
prev_song: Option<SongId>,
cover_pos: Rectangle,
covers: VecDeque<(CoverId, Option<(bool, Instant)>)>,
idle_changed: bool,
idle: f32,
text_updated: Option<Instant>,
text_pos_s: Rectangle,
text_pos_l: Rectangle,
cover_pos_s: Rectangle,
cover_pos_l: Rectangle,
} }
impl CurrentSong {
pub fn new(config: GuiElemCfg) -> Self { impl CurrentInfo {
let text_pos_s = Rectangle::from_tuples((0.4, 0.0), (1.0, 1.0)); pub fn new() -> Self {
let text_pos_l = Rectangle::from_tuples((0.05, 0.0), (0.95, 0.25));
let cover_pos_s = Rectangle::from_tuples((0.0, 0.0), (0.1, 1.0));
let cover_pos_l = Rectangle::from_tuples((0.0, 0.26), (0.4, 0.80));
Self { Self {
config, update: true,
children: vec![Box::new(AdvancedLabel::new( new_song: false,
GuiElemCfg::at(text_pos_s.clone()), new_cover: false,
Vec2::new(0.0, 0.5), current_song: None,
vec![], current_cover: None,
))],
cover_pos: Rectangle::new(Vec2::ZERO, Vec2::ZERO),
covers: VecDeque::new(),
prev_song: None,
idle_changed: false,
idle: 0.0,
text_updated: None,
text_pos_s,
text_pos_l,
cover_pos_s,
cover_pos_l,
} }
} }
pub fn set_idle_mode(&mut self, idle_mode: f32) { pub fn update(&mut self, info: &mut DrawInfo, g: &mut speedy2d::Graphics2D) {
self.idle = idle_mode; if self.update {
self.idle_changed = true; self.update = false;
let label = self.children[0] let current_song = info.database.queue.get_current_song().cloned();
.any_mut() if current_song != self.current_song {
.downcast_mut::<AdvancedLabel>() self.current_song = current_song;
.unwrap(); self.new_song = true;
label.config_mut().pos = morph_rect(&self.text_pos_s, &self.text_pos_l, idle_mode);
label.align = Vec2::new(0.5 * idle_mode, 0.5);
}
fn color_title(a: f32) -> Color {
Self::color_with_alpha(&Color::WHITE, a)
}
fn color_artist(a: f32) -> Color {
Color::from_rgba(0.32, 0.20, 0.49, a)
}
fn color_album(a: f32) -> Color {
Color::from_rgba(0.03, 0.24, 0.18, a)
}
fn color_by(a: f32) -> Color {
Self::color_with_alpha(&Color::DARK_GRAY, a)
}
fn color_on(a: f32) -> Color {
Self::color_with_alpha(&Color::DARK_GRAY, a)
}
fn color_with_alpha(c: &Color, a: f32) -> Color {
Color::from_rgba(c.r(), c.g(), c.b(), a)
}
}
impl GuiElemTrait for CurrentSong {
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 GuiElemTrait> + '_> {
Box::new(self.children.iter_mut().map(|v| v.as_mut()))
}
fn any(&self) -> &dyn std::any::Any {
self
}
fn any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn elem(&self) -> &dyn GuiElemTrait {
self
}
fn elem_mut(&mut self) -> &mut dyn GuiElemTrait {
self
}
fn draw(&mut self, info: &mut crate::gui::DrawInfo, g: &mut speedy2d::Graphics2D) {
// check if there is a new song
let new_song = if let Some(song) = info.database.queue.get_current_song() {
if Some(*song) == self.prev_song {
// same song as before
None
} else {
Some(Some(*song))
} }
} else if self.prev_song.is_none() { if let Some(current_song) = current_song {
// no song, nothing in queue let current_songs_cover =
None info.database.songs().get(&current_song).and_then(|song| {
} else { song.cover
// end of last song .or_else(|| {
Some(None) song.album
}; .and_then(|album| info.database.albums().get(&album))
if let Some(new_song) = new_song { .and_then(|album| album.cover)
// if there is a new song: })
if self.prev_song != new_song { .or_else(|| {
self.config.redraw = true; info.database
self.prev_song = new_song; .artists()
} .get(&song.artist)
// get cover .and_then(|artist| artist.cover)
let get_cover = |song: Option<u64>| crate::get_cover(song?, info.database); })
let cover = get_cover(new_song); });
// fade out all covers if let Some(current_songs_cover) = current_songs_cover {
for (_, t) in &mut self.covers { if let Some(cover) = info.covers.get_mut(&current_songs_cover) {
if !t.is_some_and(|t| t.0) { if let Some(cover) = cover.get_init(g) {
// not fading out yet, so make it start // cover loaded
*t = Some((true, Instant::now())); if self.current_cover.is_none()
} || self.current_cover.as_ref().is_some_and(|(cc, h)| {
} *cc != current_songs_cover || !matches!(h, Some(Some(_)))
// cover fades in now. })
if let Some(cover) = cover { {
self.covers self.current_cover = Some((current_songs_cover, Some(Some(cover))));
.push_back((cover, Some((false, Instant::now())))); self.new_cover = true;
} }
if let Some(next_cover) = get_cover(info.database.queue.get_next_song().cloned()) { } else if cover.is_err() {
if !info.covers.contains_key(&next_cover) { // no cover with that ID
info.covers.insert( if self.current_cover.is_none()
next_cover, || self.current_cover.as_ref().is_some_and(|(csc, h)| {
GuiCover::new(next_cover, Arc::clone(&info.get_con)), *csc != current_songs_cover || !matches!(h, Some(None))
); })
} {
} // is_err and `current` is old
// redraw self.current_cover = Some((current_songs_cover, Some(None)));
if self.config.redraw { self.new_cover = true;
self.config.redraw = false; // show notification
self.children[0] info.actions.push(GuiAction::ShowNotification(Box::new(
.any_mut() move |_| {
.downcast_mut::<AdvancedLabel>() (
.unwrap() Box::new(Panel::with_background(
.content = if let Some(song) = new_song { GuiElemCfg::default(),
self.text_updated = Some(Instant::now()); [Label::new(
info.gui_config GuiElemCfg::default(),
.status_bar_text format!("Couldn't load cover"),
.gen(&info.database, info.database.get_song(&song)) Color::WHITE,
} else { None,
vec![] Vec2::new(0.5, 0.5),
}; )],
} Color::from_rgba(0.0, 0.0, 0.0, 0.8),
} )),
if let Some(updated) = &self.text_updated { NotifInfo::new(Duration::from_secs(1)),
if let Some(h) = &info.helper { )
h.request_redraw(); },
} )));
let mut prog = updated.elapsed().as_secs_f32(); }
if prog >= 1.0 { } else {
prog = 1.0; // Cover loading, check again later
self.text_updated = None; if self.current_cover.is_none()
} || self.current_cover.as_ref().is_some_and(|(cc, h)| {
for c in self.children[0] *cc != current_songs_cover || h.is_some()
.any_mut() })
.downcast_mut::<AdvancedLabel>() {
.unwrap() self.current_cover = Some((current_songs_cover, None));
.content self.new_cover = true;
.iter_mut() }
{ self.update = true;
for c in c {
*c.0.color() = Self::color_with_alpha(c.0.color(), prog);
}
}
}
// calculate cover pos
if self.idle_changed || self.config.pixel_pos.size() != info.pos.size() {
let cw = (info.pos.height() / info.pos.width()).min(0.5);
let padl = 0.1;
let padr = 1.0 - padl;
let padp = (self.idle * 1.5).min(1.0);
self.cover_pos_s =
Rectangle::from_tuples((cw * padl, padl + 0.7 * padp), (cw * padr, padr));
self.text_pos_s = Rectangle::from_tuples((cw, 0.0), (1.0, 1.0));
// to resize the text
self.set_idle_mode(self.idle);
self.idle_changed = false;
// cover pos
let pixel_pos = adjust_area(
&info.pos,
&morph_rect(&self.cover_pos_s, &self.cover_pos_l, self.idle),
);
let pad = 0.5 * (pixel_pos.width() - pixel_pos.height());
self.cover_pos = if pad >= 0.0 {
Rectangle::from_tuples(
(
pixel_pos.top_left().x + pad - info.pos.top_left().x,
pixel_pos.top_left().y - info.pos.top_left().y,
),
(
pixel_pos.bottom_right().x - pad - info.pos.top_left().x,
pixel_pos.bottom_right().y - info.pos.top_left().y,
),
)
} else {
Rectangle::from_tuples(
(
pixel_pos.top_left().x - info.pos.top_left().x,
pixel_pos.top_left().y - pad - info.pos.top_left().y,
),
(
pixel_pos.bottom_right().x - info.pos.top_left().x,
pixel_pos.bottom_right().y + pad - info.pos.top_left().y,
),
)
};
}
let mut cover_to_remove = None;
for (cover_index, (cover_id, time)) in self.covers.iter_mut().enumerate() {
let pos = match time {
None => 1.0,
Some((false, t)) => {
let el = t.elapsed().as_secs_f32();
if el >= 1.0 {
*time = None;
1.0
} else {
if let Some(h) = &info.helper {
h.request_redraw();
} }
el
}
}
Some((true, t)) => {
let el = t.elapsed().as_secs_f32();
if el >= 1.0 {
cover_to_remove = Some(cover_index);
2.0
} else { } else {
if let Some(h) = &info.helper { info.covers.insert(
h.request_redraw(); current_songs_cover,
} GuiServerImage::new_cover(
1.0 + el current_songs_cover,
} Arc::clone(&info.get_con),
}
};
if let Some(cover) = info.covers.get_mut(cover_id) {
if let Some(cover) = cover.get_init(g) {
let rect = Rectangle::new(
Vec2::new(
info.pos.top_left().x + self.cover_pos.top_left().x,
info.pos.top_left().y + self.cover_pos.top_left().y,
),
Vec2::new(
info.pos.top_left().x + self.cover_pos.bottom_right().x,
info.pos.top_left().y + self.cover_pos.bottom_right().y,
),
);
if pos == 1.0 {
g.draw_rectangle_image(rect, &cover);
} else {
let prog = (pos - 1.0).abs();
// shrink to half (0.5x0.5) size while moving left and fading out
let lx = rect.top_left().x + rect.width() * prog * 0.25;
let rx = rect.bottom_right().x - rect.width() * prog * 0.25;
let ty = rect.top_left().y + rect.height() * prog * 0.25;
let by = rect.bottom_right().y - rect.height() * prog * 0.25;
let mut moved = rect.width() * prog * prog;
if pos > 1.0 {
moved = -moved;
}
g.draw_rectangle_image_tinted(
Rectangle::from_tuples((lx + moved, ty), (rx + moved, by)),
Color::from_rgba(
1.0,
1.0,
1.0,
if pos > 1.0 { 2.0 - pos } else { pos },
), ),
&cover,
); );
if self.current_cover.is_none()
|| self
.current_cover
.as_ref()
.is_some_and(|(cc, h)| *cc != current_songs_cover || h.is_some())
{
self.current_cover = Some((current_songs_cover, None));
self.new_cover = true;
}
self.update = true;
} }
} else { } else {
// cover still loading, just wait // no cover
if self.current_cover.is_some() {
self.current_cover = None;
self.new_cover = true;
}
} }
} else { } else {
// cover not loading or loaded, start loading! // no song
info.covers if self.current_cover.is_some() {
.insert(*cover_id, GuiCover::new(*cover_id, info.get_con.clone())); self.current_cover = None;
self.new_cover = true;
}
} }
} }
// removing one cover per frame is good enough
if let Some(index) = cover_to_remove {
self.covers.remove(index);
}
} }
} }
pub struct PlayPauseToggle { pub fn image_display(
config: GuiElemCfg, g: &mut speedy2d::Graphics2D,
children: Vec<Box<dyn GuiElemTrait>>, img: Option<&ImageHandle>,
playing_target: bool, left: f32,
playing_waiting_for_change: bool, top: f32,
} bottom: f32,
impl PlayPauseToggle { aspect_ratio: &mut AnimationController<f32>,
/// automatically adds w_mouse to config ) {
pub fn new(config: GuiElemCfg, playing: bool) -> Self { if let Some(cover) = &img {
Self { let cover_size = cover.size();
config: config.w_mouse(), aspect_ratio.target = if cover_size.x > 0 && cover_size.y > 0 {
children: vec![], let right_x = get_right_x(left, top, bottom, aspect_ratio.value);
playing_target: playing, let pos = Rectangle::from_tuples((left, top), (right_x, bottom));
playing_waiting_for_change: false, let aspect_ratio = cover_size.x as f32 / cover_size.y as f32;
} g.draw_rectangle_image(pos, cover);
} aspect_ratio
}
impl GuiElemTrait for PlayPauseToggle {
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 GuiElemTrait> + '_> {
Box::new(self.children.iter_mut().map(|v| v.as_mut()))
}
fn any(&self) -> &dyn std::any::Any {
self
}
fn any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn elem(&self) -> &dyn GuiElemTrait {
self
}
fn elem_mut(&mut self) -> &mut dyn GuiElemTrait {
self
}
fn draw(&mut self, info: &mut crate::gui::DrawInfo, g: &mut speedy2d::Graphics2D) {
if self.playing_waiting_for_change {
if info.database.playing == self.playing_target {
self.playing_waiting_for_change = false;
}
} else { } else {
// not waiting for change, update if the value changes 0.0
self.playing_target = info.database.playing;
}
let pos = if info.pos.width() > info.pos.height() {
let a = 0.5 * info.pos.height();
let m = 0.5 * (info.pos.top_left().x + info.pos.bottom_right().x);
Rectangle::new(
Vec2::new(m - a, info.pos.top_left().y),
Vec2::new(m + a, info.pos.bottom_right().y),
)
} else {
let a = 0.5 * info.pos.width();
let m = 0.5 * (info.pos.top_left().y + info.pos.bottom_right().y);
Rectangle::new(
Vec2::new(info.pos.top_left().x, m - a),
Vec2::new(info.pos.bottom_right().x, m + a),
)
}; };
if self.playing_target { } else {
g.draw_triangle( aspect_ratio.target = 0.0;
[
adjust_pos(&pos, &Vec2::new(0.25, 0.25)),
adjust_pos(&pos, &Vec2::new(0.75, 0.5)),
adjust_pos(&pos, &Vec2::new(0.25, 0.75)),
],
if self.playing_waiting_for_change {
Color::GRAY
} else {
Color::GREEN
},
)
} else {
g.draw_rectangle(
adjust_area(&pos, &Rectangle::from_tuples((0.25, 0.25), (0.75, 0.75))),
if self.playing_waiting_for_change {
Color::RED
} else {
Color::GRAY
},
);
}
}
fn mouse_pressed(&mut self, button: MouseButton) -> Vec<GuiAction> {
match button {
MouseButton::Left => {
if !self.playing_waiting_for_change {
self.playing_target = !self.playing_target;
self.playing_waiting_for_change = true;
vec![GuiAction::SendToServer(if self.playing_target {
Command::Resume
} else {
Command::Pause
})]
} else {
vec![]
}
}
MouseButton::Right => vec![GuiAction::SendToServer(Command::NextSong)],
_ => vec![],
}
} }
} }
pub fn get_right_x(left: f32, top: f32, bottom: f32, aspect_ratio: f32) -> f32 {
left + aspect_ratio * (bottom - top)
}

View File

@ -17,7 +17,7 @@ use speedy2d::{
}; };
use crate::{ use crate::{
gui::{Dragging, DrawInfo, GuiAction, GuiElemCfg, GuiElemTrait}, gui::{Dragging, DrawInfo, GuiAction, GuiElem, GuiElemCfg},
gui_base::{Panel, ScrollBox}, gui_base::{Panel, ScrollBox},
gui_text::{self, AdvancedLabel, Label}, gui_text::{self, AdvancedLabel, Label},
}; };
@ -33,7 +33,10 @@ because simple clicks have to be GoTo events.
pub struct QueueViewer { pub struct QueueViewer {
config: GuiElemCfg, config: GuiElemCfg,
children: Vec<Box<dyn GuiElemTrait>>, c_scroll_box: ScrollBox<Vec<Box<dyn GuiElem>>>,
c_empty_space_drag_handler: QueueEmptySpaceDragHandler,
c_control_flow_elements: Panel<(QueueLoop, QueueLoop, QueueRandom, QueueShuffle)>,
c_duration: AdvancedLabel,
queue_updated: bool, queue_updated: bool,
} }
const QP_QUEUE1: f32 = 0.0; const QP_QUEUE1: f32 = 0.0;
@ -42,112 +45,91 @@ const QP_INV1: f32 = QP_QUEUE2;
const QP_INV2: f32 = 1.0; const QP_INV2: f32 = 1.0;
impl QueueViewer { impl QueueViewer {
pub fn new(config: GuiElemCfg) -> Self { pub fn new(config: GuiElemCfg) -> Self {
let control_flow_elements = (
QueueLoop::new(
GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (0.5, 0.5))).w_mouse(),
vec![],
QueueContent::Loop(
0,
0,
Box::new(QueueContent::Folder(0, vec![], "in loop".to_string()).into()),
)
.into(),
false,
)
.alwayscopy(),
QueueLoop::new(
GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.5), (0.5, 1.0))).w_mouse(),
vec![],
QueueContent::Loop(
2,
0,
Box::new(QueueContent::Folder(0, vec![], "in loop".to_string()).into()),
)
.into(),
false,
)
.alwayscopy(),
QueueRandom::new(
GuiElemCfg::at(Rectangle::from_tuples((0.5, 0.0), (1.0, 0.5))).w_mouse(),
vec![],
QueueContent::Random(VecDeque::new()).into(),
false,
)
.alwayscopy(),
QueueShuffle::new(
GuiElemCfg::at(Rectangle::from_tuples((0.5, 0.5), (1.0, 1.0))).w_mouse(),
vec![],
QueueContent::Shuffle {
inner: Box::new(QueueContent::Folder(0, vec![], String::new()).into()),
state: ShuffleState::NotShuffled,
}
.into(),
false,
)
.alwayscopy(),
);
Self { Self {
config, config,
children: vec![ c_scroll_box: ScrollBox::new(
Box::new(ScrollBox::new( GuiElemCfg::at(Rectangle::from_tuples((0.0, QP_QUEUE1), (1.0, QP_QUEUE2))),
GuiElemCfg::at(Rectangle::from_tuples((0.0, QP_QUEUE1), (1.0, QP_QUEUE2))), crate::gui_base::ScrollBoxSizeUnit::Pixels,
crate::gui_base::ScrollBoxSizeUnit::Pixels, vec![],
vec![( vec![],
Box::new(Label::new( ),
GuiElemCfg::default(), c_empty_space_drag_handler: QueueEmptySpaceDragHandler::new(GuiElemCfg::at(
"loading...".to_string(), Rectangle::from_tuples((0.0, QP_QUEUE1), (1.0, QP_QUEUE2)),
Color::DARK_GRAY, )),
None, c_control_flow_elements: Panel::new(
Vec2::new(0.5, 0.5), GuiElemCfg::at(Rectangle::from_tuples((0.0, QP_INV1), (0.5, QP_INV2))),
)), control_flow_elements,
1.0, ),
)], c_duration: AdvancedLabel::new(
)), GuiElemCfg::at(Rectangle::from_tuples((0.5, QP_INV1), (1.0, QP_INV2))),
Box::new(QueueEmptySpaceDragHandler::new(GuiElemCfg::at( Vec2::new(0.0, 0.5),
Rectangle::from_tuples((0.0, QP_QUEUE1), (1.0, QP_QUEUE2)), vec![],
))), ),
Box::new(Panel::new(
GuiElemCfg::at(Rectangle::from_tuples((0.0, QP_INV1), (0.5, QP_INV2))),
vec![
Box::new(
QueueLoop::new(
GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (0.5, 0.5)))
.w_mouse(),
vec![],
QueueContent::Loop(
0,
0,
Box::new(
QueueContent::Folder(0, vec![], "in loop".to_string())
.into(),
),
)
.into(),
false,
)
.alwayscopy(),
),
Box::new(
QueueLoop::new(
GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.5), (0.5, 1.0)))
.w_mouse(),
vec![],
QueueContent::Loop(
2,
0,
Box::new(
QueueContent::Folder(0, vec![], "in loop".to_string())
.into(),
),
)
.into(),
false,
)
.alwayscopy(),
),
Box::new(
QueueRandom::new(
GuiElemCfg::at(Rectangle::from_tuples((0.5, 0.0), (1.0, 0.5)))
.w_mouse(),
vec![],
QueueContent::Random(VecDeque::new()).into(),
false,
)
.alwayscopy(),
),
Box::new(
QueueShuffle::new(
GuiElemCfg::at(Rectangle::from_tuples((0.5, 0.5), (1.0, 1.0)))
.w_mouse(),
vec![],
QueueContent::Shuffle {
inner: Box::new(
QueueContent::Folder(0, vec![], String::new()).into(),
),
state: ShuffleState::NotShuffled,
}
.into(),
false,
)
.alwayscopy(),
),
],
)),
Box::new(AdvancedLabel::new(
GuiElemCfg::at(Rectangle::from_tuples((0.5, QP_INV1), (1.0, QP_INV2))),
Vec2::new(0.0, 0.5),
vec![],
)),
],
queue_updated: false, queue_updated: false,
} }
} }
} }
impl GuiElemTrait for QueueViewer { impl GuiElem for QueueViewer {
fn config(&self) -> &GuiElemCfg { fn config(&self) -> &GuiElemCfg {
&self.config &self.config
} }
fn config_mut(&mut self) -> &mut GuiElemCfg { fn config_mut(&mut self) -> &mut GuiElemCfg {
&mut self.config &mut self.config
} }
fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElemTrait> + '_> { fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
Box::new(self.children.iter_mut().map(|v| v.as_mut())) Box::new(
[
self.c_scroll_box.elem_mut(),
self.c_empty_space_drag_handler.elem_mut(),
self.c_control_flow_elements.elem_mut(),
self.c_duration.elem_mut(),
]
.into_iter(),
)
} }
fn any(&self) -> &dyn std::any::Any { fn any(&self) -> &dyn std::any::Any {
self self
@ -155,19 +137,16 @@ impl GuiElemTrait for QueueViewer {
fn any_mut(&mut self) -> &mut dyn std::any::Any { fn any_mut(&mut self) -> &mut dyn std::any::Any {
self self
} }
fn elem(&self) -> &dyn GuiElemTrait { fn elem(&self) -> &dyn GuiElem {
self self
} }
fn elem_mut(&mut self) -> &mut dyn GuiElemTrait { fn elem_mut(&mut self) -> &mut dyn GuiElem {
self self
} }
fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) { fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) {
if self.queue_updated { if self.queue_updated {
self.queue_updated = false; self.queue_updated = false;
let label = self.children[3] let label = &mut self.c_duration;
.any_mut()
.downcast_mut::<AdvancedLabel>()
.unwrap();
fn fmt_dur(dur: QueueDuration) -> String { fn fmt_dur(dur: QueueDuration) -> String {
if dur.infinite { if dur.infinite {
"".to_owned() "".to_owned()
@ -216,6 +195,7 @@ impl GuiElemTrait for QueueViewer {
if self.config.redraw || info.pos.size() != self.config.pixel_pos.size() { if self.config.redraw || info.pos.size() != self.config.pixel_pos.size() {
self.config.redraw = false; self.config.redraw = false;
let mut c = vec![]; let mut c = vec![];
let mut h = vec![];
queue_gui( queue_gui(
&info.database.queue, &info.database.queue,
&info.database, &info.database,
@ -223,15 +203,14 @@ impl GuiElemTrait for QueueViewer {
0.02, 0.02,
info.line_height, info.line_height,
&mut c, &mut c,
&mut h,
vec![], vec![],
true, true,
true, true,
); );
let mut scroll_box = self.children[0] let scroll_box = &mut self.c_scroll_box;
.any_mut()
.downcast_mut::<ScrollBox>()
.unwrap();
scroll_box.children = c; scroll_box.children = c;
scroll_box.children_heights = h;
scroll_box.config_mut().redraw = true; scroll_box.config_mut().redraw = true;
} }
} }
@ -247,7 +226,8 @@ fn queue_gui(
depth: f32, depth: f32,
depth_inc_by: f32, depth_inc_by: f32,
line_height: f32, line_height: f32,
target: &mut Vec<(Box<dyn GuiElemTrait>, f32)>, target: &mut Vec<Box<dyn GuiElem>>,
target_h: &mut Vec<f32>,
path: Vec<usize>, path: Vec<usize>,
current: bool, current: bool,
skip_folder: bool, skip_folder: bool,
@ -256,30 +236,26 @@ fn queue_gui(
match queue.content() { match queue.content() {
QueueContent::Song(id) => { QueueContent::Song(id) => {
if let Some(s) = db.songs().get(id) { if let Some(s) = db.songs().get(id) {
target.push(( target.push(Box::new(QueueSong::new(
Box::new(QueueSong::new( cfg,
cfg, path,
path, s.clone(),
s.clone(), current,
current, db,
db, depth_inc_by * 0.33,
depth_inc_by * 0.33, )));
)), target_h.push(line_height * 1.75);
line_height * 1.75,
));
} }
} }
QueueContent::Folder(ia, q, _) => { QueueContent::Folder(ia, q, _) => {
if !skip_folder { if !skip_folder {
target.push(( target.push(Box::new(QueueFolder::new(
Box::new(QueueFolder::new( cfg.clone(),
cfg.clone(), path.clone(),
path.clone(), queue.clone(),
queue.clone(), current,
current, )));
)), target_h.push(line_height * 0.8);
line_height * 0.8,
));
} }
for (i, q) in q.iter().enumerate() { for (i, q) in q.iter().enumerate() {
let mut p = path.clone(); let mut p = path.clone();
@ -291,6 +267,7 @@ fn queue_gui(
depth_inc_by, depth_inc_by,
line_height, line_height,
target, target,
target_h,
p, p,
current && *ia == i, current && *ia == i,
false, false,
@ -299,10 +276,8 @@ fn queue_gui(
if !skip_folder { if !skip_folder {
let mut p1 = path; let mut p1 = path;
let p2 = p1.pop().unwrap_or(0) + 1; let p2 = p1.pop().unwrap_or(0) + 1;
target.push(( target.push(Box::new(QueueIndentEnd::new(cfg, (p1, p2))));
Box::new(QueueIndentEnd::new(cfg, (p1, p2))), target_h.push(line_height * 0.4);
line_height * 0.4,
));
} }
} }
QueueContent::Loop(_, _, inner) => { QueueContent::Loop(_, _, inner) => {
@ -310,10 +285,13 @@ fn queue_gui(
p.push(0); p.push(0);
let mut p1 = path.clone(); let mut p1 = path.clone();
let p2 = p1.pop().unwrap_or(0) + 1; let p2 = p1.pop().unwrap_or(0) + 1;
target.push(( target.push(Box::new(QueueLoop::new(
Box::new(QueueLoop::new(cfg.clone(), path, queue.clone(), current)), cfg.clone(),
line_height * 0.8, path,
)); queue.clone(),
current,
)));
target_h.push(line_height * 0.8);
queue_gui( queue_gui(
&inner, &inner,
db, db,
@ -321,25 +299,22 @@ fn queue_gui(
depth_inc_by, depth_inc_by,
line_height, line_height,
target, target,
target_h,
p, p,
current, current,
true, true,
); );
target.push(( target.push(Box::new(QueueIndentEnd::new(cfg, (p1, p2))));
Box::new(QueueIndentEnd::new(cfg, (p1, p2))), target_h.push(line_height * 0.4);
line_height * 0.4,
));
} }
QueueContent::Random(q) => { QueueContent::Random(q) => {
target.push(( target.push(Box::new(QueueRandom::new(
Box::new(QueueRandom::new( cfg.clone(),
cfg.clone(), path.clone(),
path.clone(), queue.clone(),
queue.clone(), current,
current, )));
)), target_h.push(line_height);
line_height,
));
for (i, inner) in q.iter().enumerate() { for (i, inner) in q.iter().enumerate() {
let mut p = path.clone(); let mut p = path.clone();
p.push(i); p.push(i);
@ -350,6 +325,7 @@ fn queue_gui(
depth_inc_by, depth_inc_by,
line_height, line_height,
target, target,
target_h,
p, p,
current && i == q.len().saturating_sub(2), current && i == q.len().saturating_sub(2),
false, false,
@ -357,21 +333,17 @@ fn queue_gui(
} }
let mut p1 = path.clone(); let mut p1 = path.clone();
let p2 = p1.pop().unwrap_or(0) + 1; let p2 = p1.pop().unwrap_or(0) + 1;
target.push(( target.push(Box::new(QueueIndentEnd::new(cfg, (p1, p2))));
Box::new(QueueIndentEnd::new(cfg, (p1, p2))), target_h.push(line_height * 0.4);
line_height * 0.4,
));
} }
QueueContent::Shuffle { inner, state: _ } => { QueueContent::Shuffle { inner, state: _ } => {
target.push(( target.push(Box::new(QueueShuffle::new(
Box::new(QueueShuffle::new( cfg.clone(),
cfg.clone(), path.clone(),
path.clone(), queue.clone(),
queue.clone(), current,
current, )));
)), target_h.push(line_height * 0.8);
line_height * 0.8,
));
let mut p = path.clone(); let mut p = path.clone();
p.push(0); p.push(0);
queue_gui( queue_gui(
@ -381,23 +353,22 @@ fn queue_gui(
depth_inc_by, depth_inc_by,
line_height, line_height,
target, target,
target_h,
p, p,
current, current,
true, true,
); );
let mut p1 = path.clone(); let mut p1 = path.clone();
let p2 = p1.pop().unwrap_or(0) + 1; let p2 = p1.pop().unwrap_or(0) + 1;
target.push(( target.push(Box::new(QueueIndentEnd::new(cfg, (p1, p2))));
Box::new(QueueIndentEnd::new(cfg, (p1, p2))), target_h.push(line_height * 0.4);
line_height * 0.4,
));
} }
} }
} }
struct QueueEmptySpaceDragHandler { struct QueueEmptySpaceDragHandler {
config: GuiElemCfg, config: GuiElemCfg,
children: Vec<Box<dyn GuiElemTrait>>, children: Vec<Box<dyn GuiElem>>,
} }
impl QueueEmptySpaceDragHandler { impl QueueEmptySpaceDragHandler {
pub fn new(config: GuiElemCfg) -> Self { pub fn new(config: GuiElemCfg) -> Self {
@ -407,15 +378,15 @@ impl QueueEmptySpaceDragHandler {
} }
} }
} }
impl GuiElemTrait for QueueEmptySpaceDragHandler { impl GuiElem for QueueEmptySpaceDragHandler {
fn config(&self) -> &GuiElemCfg { fn config(&self) -> &GuiElemCfg {
&self.config &self.config
} }
fn config_mut(&mut self) -> &mut GuiElemCfg { fn config_mut(&mut self) -> &mut GuiElemCfg {
&mut self.config &mut self.config
} }
fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElemTrait> + '_> { fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
Box::new(self.children.iter_mut().map(|v| v.as_mut())) Box::new(self.children.iter_mut().map(|v| v.elem_mut()))
} }
fn any(&self) -> &dyn std::any::Any { fn any(&self) -> &dyn std::any::Any {
self self
@ -423,10 +394,10 @@ impl GuiElemTrait for QueueEmptySpaceDragHandler {
fn any_mut(&mut self) -> &mut dyn std::any::Any { fn any_mut(&mut self) -> &mut dyn std::any::Any {
self self
} }
fn elem(&self) -> &dyn GuiElemTrait { fn elem(&self) -> &dyn GuiElem {
self self
} }
fn elem_mut(&mut self) -> &mut dyn GuiElemTrait { fn elem_mut(&mut self) -> &mut dyn GuiElem {
self self
} }
fn dragged(&mut self, dragged: Dragging) -> Vec<GuiAction> { fn dragged(&mut self, dragged: Dragging) -> Vec<GuiAction> {
@ -454,7 +425,7 @@ fn generic_queue_draw(
struct QueueSong { struct QueueSong {
config: GuiElemCfg, config: GuiElemCfg,
children: Vec<Box<dyn GuiElemTrait>>, children: Vec<Box<dyn GuiElem>>,
path: Vec<usize>, path: Vec<usize>,
song: Song, song: Song,
current: bool, current: bool,
@ -549,22 +520,17 @@ impl QueueSong {
copy_on_mouse_down: false, copy_on_mouse_down: false,
} }
} }
fn alwayscopy(mut self) -> Self {
self.always_copy = true;
self.copy = true;
self
}
} }
impl GuiElemTrait for QueueSong { impl GuiElem for QueueSong {
fn config(&self) -> &GuiElemCfg { fn config(&self) -> &GuiElemCfg {
&self.config &self.config
} }
fn config_mut(&mut self) -> &mut GuiElemCfg { fn config_mut(&mut self) -> &mut GuiElemCfg {
&mut self.config &mut self.config
} }
fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElemTrait> + '_> { fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
Box::new(self.children.iter_mut().map(|v| v.as_mut())) Box::new(self.children.iter_mut().map(|v| v.elem_mut()))
} }
fn any(&self) -> &dyn std::any::Any { fn any(&self) -> &dyn std::any::Any {
self self
@ -572,10 +538,10 @@ impl GuiElemTrait for QueueSong {
fn any_mut(&mut self) -> &mut dyn std::any::Any { fn any_mut(&mut self) -> &mut dyn std::any::Any {
self self
} }
fn elem(&self) -> &dyn GuiElemTrait { fn elem(&self) -> &dyn GuiElem {
self self
} }
fn elem_mut(&mut self) -> &mut dyn GuiElemTrait { fn elem_mut(&mut self) -> &mut dyn GuiElem {
self self
} }
fn mouse_down(&mut self, button: MouseButton) -> Vec<GuiAction> { fn mouse_down(&mut self, button: MouseButton) -> Vec<GuiAction> {
@ -630,9 +596,6 @@ impl GuiElemTrait for QueueSong {
); );
} }
if generic_queue_draw(info, &self.path, &mut self.mouse, self.copy_on_mouse_down) { if generic_queue_draw(info, &self.path, &mut self.mouse, self.copy_on_mouse_down) {
let mouse_pos = self.mouse_pos;
let w = self.config.pixel_pos.width();
let h = self.config.pixel_pos.height();
info.actions.push(GuiAction::SetDragging(Some(( info.actions.push(GuiAction::SetDragging(Some((
Dragging::Queue(QueueContent::Song(self.song.id).into()), Dragging::Queue(QueueContent::Song(self.song.id).into()),
None, None,
@ -668,7 +631,7 @@ impl GuiElemTrait for QueueSong {
struct QueueFolder { struct QueueFolder {
config: GuiElemCfg, config: GuiElemCfg,
children: Vec<Box<dyn GuiElemTrait>>, children: Vec<Box<dyn GuiElem>>,
path: Vec<usize>, path: Vec<usize>,
queue: Queue, queue: Queue,
current: bool, current: bool,
@ -717,21 +680,16 @@ impl QueueFolder {
copy_on_mouse_down: false, copy_on_mouse_down: false,
} }
} }
fn alwayscopy(mut self) -> Self {
self.always_copy = true;
self.copy = true;
self
}
} }
impl GuiElemTrait for QueueFolder { impl GuiElem for QueueFolder {
fn config(&self) -> &GuiElemCfg { fn config(&self) -> &GuiElemCfg {
&self.config &self.config
} }
fn config_mut(&mut self) -> &mut GuiElemCfg { fn config_mut(&mut self) -> &mut GuiElemCfg {
&mut self.config &mut self.config
} }
fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElemTrait> + '_> { fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
Box::new(self.children.iter_mut().map(|v| v.as_mut())) Box::new(self.children.iter_mut().map(|v| v.elem_mut()))
} }
fn any(&self) -> &dyn std::any::Any { fn any(&self) -> &dyn std::any::Any {
self self
@ -739,10 +697,10 @@ impl GuiElemTrait for QueueFolder {
fn any_mut(&mut self) -> &mut dyn std::any::Any { fn any_mut(&mut self) -> &mut dyn std::any::Any {
self self
} }
fn elem(&self) -> &dyn GuiElemTrait { fn elem(&self) -> &dyn GuiElem {
self self
} }
fn elem_mut(&mut self) -> &mut dyn GuiElemTrait { fn elem_mut(&mut self) -> &mut dyn GuiElem {
self self
} }
fn draw(&mut self, info: &mut DrawInfo, g: &mut speedy2d::Graphics2D) { fn draw(&mut self, info: &mut DrawInfo, g: &mut speedy2d::Graphics2D) {
@ -776,9 +734,6 @@ impl GuiElemTrait for QueueFolder {
); );
} }
if generic_queue_draw(info, &self.path, &mut self.mouse, self.copy_on_mouse_down) { if generic_queue_draw(info, &self.path, &mut self.mouse, self.copy_on_mouse_down) {
let mouse_pos = self.mouse_pos;
let w = self.config.pixel_pos.width();
let h = self.config.pixel_pos.height();
info.actions.push(GuiAction::SetDragging(Some(( info.actions.push(GuiAction::SetDragging(Some((
Dragging::Queue(self.queue.clone()), Dragging::Queue(self.queue.clone()),
None, None,
@ -835,7 +790,7 @@ impl GuiElemTrait for QueueFolder {
} }
pub struct QueueIndentEnd { pub struct QueueIndentEnd {
config: GuiElemCfg, config: GuiElemCfg,
children: Vec<Box<dyn GuiElemTrait>>, children: Vec<Box<dyn GuiElem>>,
path_insert: (Vec<usize>, usize), path_insert: (Vec<usize>, usize),
} }
impl QueueIndentEnd { impl QueueIndentEnd {
@ -847,15 +802,15 @@ impl QueueIndentEnd {
} }
} }
} }
impl GuiElemTrait for QueueIndentEnd { impl GuiElem for QueueIndentEnd {
fn config(&self) -> &GuiElemCfg { fn config(&self) -> &GuiElemCfg {
&self.config &self.config
} }
fn config_mut(&mut self) -> &mut GuiElemCfg { fn config_mut(&mut self) -> &mut GuiElemCfg {
&mut self.config &mut self.config
} }
fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElemTrait> + '_> { fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
Box::new(self.children.iter_mut().map(|v| v.as_mut())) Box::new(self.children.iter_mut().map(|v| v.elem_mut()))
} }
fn any(&self) -> &dyn std::any::Any { fn any(&self) -> &dyn std::any::Any {
self self
@ -863,10 +818,10 @@ impl GuiElemTrait for QueueIndentEnd {
fn any_mut(&mut self) -> &mut dyn std::any::Any { fn any_mut(&mut self) -> &mut dyn std::any::Any {
self self
} }
fn elem(&self) -> &dyn GuiElemTrait { fn elem(&self) -> &dyn GuiElem {
self self
} }
fn elem_mut(&mut self) -> &mut dyn GuiElemTrait { fn elem_mut(&mut self) -> &mut dyn GuiElem {
self self
} }
fn draw(&mut self, info: &mut DrawInfo, g: &mut speedy2d::Graphics2D) { fn draw(&mut self, info: &mut DrawInfo, g: &mut speedy2d::Graphics2D) {
@ -896,7 +851,7 @@ impl GuiElemTrait for QueueIndentEnd {
struct QueueLoop { struct QueueLoop {
config: GuiElemCfg, config: GuiElemCfg,
children: Vec<Box<dyn GuiElemTrait>>, children: Vec<Box<dyn GuiElem>>,
path: Vec<usize>, path: Vec<usize>,
queue: Queue, queue: Queue,
current: bool, current: bool,
@ -953,15 +908,15 @@ impl QueueLoop {
} }
} }
} }
impl GuiElemTrait for QueueLoop { impl GuiElem for QueueLoop {
fn config(&self) -> &GuiElemCfg { fn config(&self) -> &GuiElemCfg {
&self.config &self.config
} }
fn config_mut(&mut self) -> &mut GuiElemCfg { fn config_mut(&mut self) -> &mut GuiElemCfg {
&mut self.config &mut self.config
} }
fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElemTrait> + '_> { fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
Box::new(self.children.iter_mut().map(|v| v.as_mut())) Box::new(self.children.iter_mut().map(|v| v.elem_mut()))
} }
fn any(&self) -> &dyn std::any::Any { fn any(&self) -> &dyn std::any::Any {
self self
@ -969,10 +924,10 @@ impl GuiElemTrait for QueueLoop {
fn any_mut(&mut self) -> &mut dyn std::any::Any { fn any_mut(&mut self) -> &mut dyn std::any::Any {
self self
} }
fn elem(&self) -> &dyn GuiElemTrait { fn elem(&self) -> &dyn GuiElem {
self self
} }
fn elem_mut(&mut self) -> &mut dyn GuiElemTrait { fn elem_mut(&mut self) -> &mut dyn GuiElem {
self self
} }
fn mouse_wheel(&mut self, diff: f32) -> Vec<GuiAction> { fn mouse_wheel(&mut self, diff: f32) -> Vec<GuiAction> {
@ -1001,9 +956,6 @@ impl GuiElemTrait for QueueLoop {
); );
} }
if generic_queue_draw(info, &self.path, &mut self.mouse, self.copy_on_mouse_down) { if generic_queue_draw(info, &self.path, &mut self.mouse, self.copy_on_mouse_down) {
let mouse_pos = self.mouse_pos;
let w = self.config.pixel_pos.width();
let h = self.config.pixel_pos.height();
info.actions.push(GuiAction::SetDragging(Some(( info.actions.push(GuiAction::SetDragging(Some((
Dragging::Queue(self.queue.clone()), Dragging::Queue(self.queue.clone()),
None, None,
@ -1054,7 +1006,7 @@ impl GuiElemTrait for QueueLoop {
struct QueueRandom { struct QueueRandom {
config: GuiElemCfg, config: GuiElemCfg,
children: Vec<Box<dyn GuiElemTrait>>, children: Vec<Box<dyn GuiElem>>,
path: Vec<usize>, path: Vec<usize>,
queue: Queue, queue: Queue,
current: bool, current: bool,
@ -1101,15 +1053,15 @@ impl QueueRandom {
self self
} }
} }
impl GuiElemTrait for QueueRandom { impl GuiElem for QueueRandom {
fn config(&self) -> &GuiElemCfg { fn config(&self) -> &GuiElemCfg {
&self.config &self.config
} }
fn config_mut(&mut self) -> &mut GuiElemCfg { fn config_mut(&mut self) -> &mut GuiElemCfg {
&mut self.config &mut self.config
} }
fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElemTrait> + '_> { fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
Box::new(self.children.iter_mut().map(|v| v.as_mut())) Box::new(self.children.iter_mut().map(|v| v.elem_mut()))
} }
fn any(&self) -> &dyn std::any::Any { fn any(&self) -> &dyn std::any::Any {
self self
@ -1117,10 +1069,10 @@ impl GuiElemTrait for QueueRandom {
fn any_mut(&mut self) -> &mut dyn std::any::Any { fn any_mut(&mut self) -> &mut dyn std::any::Any {
self self
} }
fn elem(&self) -> &dyn GuiElemTrait { fn elem(&self) -> &dyn GuiElem {
self self
} }
fn elem_mut(&mut self) -> &mut dyn GuiElemTrait { fn elem_mut(&mut self) -> &mut dyn GuiElem {
self self
} }
fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) { fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) {
@ -1131,9 +1083,6 @@ impl GuiElemTrait for QueueRandom {
); );
} }
if generic_queue_draw(info, &self.path, &mut self.mouse, self.copy_on_mouse_down) { if generic_queue_draw(info, &self.path, &mut self.mouse, self.copy_on_mouse_down) {
let mouse_pos = self.mouse_pos;
let w = self.config.pixel_pos.width();
let h = self.config.pixel_pos.height();
info.actions.push(GuiAction::SetDragging(Some(( info.actions.push(GuiAction::SetDragging(Some((
Dragging::Queue(self.queue.clone()), Dragging::Queue(self.queue.clone()),
None, None,
@ -1183,7 +1132,7 @@ impl GuiElemTrait for QueueRandom {
struct QueueShuffle { struct QueueShuffle {
config: GuiElemCfg, config: GuiElemCfg,
children: Vec<Box<dyn GuiElemTrait>>, children: Vec<Box<dyn GuiElem>>,
path: Vec<usize>, path: Vec<usize>,
queue: Queue, queue: Queue,
current: bool, current: bool,
@ -1230,15 +1179,15 @@ impl QueueShuffle {
self self
} }
} }
impl GuiElemTrait for QueueShuffle { impl GuiElem for QueueShuffle {
fn config(&self) -> &GuiElemCfg { fn config(&self) -> &GuiElemCfg {
&self.config &self.config
} }
fn config_mut(&mut self) -> &mut GuiElemCfg { fn config_mut(&mut self) -> &mut GuiElemCfg {
&mut self.config &mut self.config
} }
fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElemTrait> + '_> { fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
Box::new(self.children.iter_mut().map(|v| v.as_mut())) Box::new(self.children.iter_mut().map(|v| v.elem_mut()))
} }
fn any(&self) -> &dyn std::any::Any { fn any(&self) -> &dyn std::any::Any {
self self
@ -1246,10 +1195,10 @@ impl GuiElemTrait for QueueShuffle {
fn any_mut(&mut self) -> &mut dyn std::any::Any { fn any_mut(&mut self) -> &mut dyn std::any::Any {
self self
} }
fn elem(&self) -> &dyn GuiElemTrait { fn elem(&self) -> &dyn GuiElem {
self self
} }
fn elem_mut(&mut self) -> &mut dyn GuiElemTrait { fn elem_mut(&mut self) -> &mut dyn GuiElem {
self self
} }
fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) { fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) {
@ -1260,9 +1209,6 @@ impl GuiElemTrait for QueueShuffle {
); );
} }
if generic_queue_draw(info, &self.path, &mut self.mouse, self.copy_on_mouse_down) { if generic_queue_draw(info, &self.path, &mut self.mouse, self.copy_on_mouse_down) {
let mouse_pos = self.mouse_pos;
let w = self.config.pixel_pos.width();
let h = self.config.pixel_pos.height();
info.actions.push(GuiAction::SetDragging(Some(( info.actions.push(GuiAction::SetDragging(Some((
Dragging::Queue(self.queue.clone()), Dragging::Queue(self.queue.clone()),
None, None,

View File

@ -4,14 +4,17 @@ use musicdb_lib::{data::queue::QueueContent, server::Command};
use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle, window::VirtualKeyCode, Graphics2D}; use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle, window::VirtualKeyCode, Graphics2D};
use crate::{ use crate::{
gui::{morph_rect, DrawInfo, GuiAction, GuiElemCfg, GuiElemTrait}, gui::{DrawInfo, GuiAction, GuiElem, GuiElemCfg},
gui_anim::AnimationController,
gui_base::{Button, Panel}, gui_base::{Button, Panel},
gui_idle_display::IdleDisplay,
gui_library::LibraryBrowser, gui_library::LibraryBrowser,
gui_notif::NotifOverlay, gui_notif::NotifOverlay,
gui_playback::{CurrentSong, PlayPauseToggle},
gui_queue::QueueViewer, gui_queue::QueueViewer,
gui_settings::Settings, gui_settings::Settings,
gui_statusbar::StatusBar,
gui_text::Label, gui_text::Label,
gui_wrappers::Hotkey,
}; };
/* /*
@ -34,48 +37,31 @@ pub fn transition(p: f32) -> f32 {
pub struct GuiScreen { pub struct GuiScreen {
config: GuiElemCfg, config: GuiElemCfg,
/// 0: Notifications c_notif_overlay: NotifOverlay,
/// 1: StatusBar / Idle display c_idle_display: IdleDisplay,
/// 2: Settings c_status_bar: StatusBar,
/// 3: Panel for Main view c_settings: Settings,
/// 0: settings button c_main_view: Panel<(
/// 1: exit button Button<[Label; 1]>,
/// 2: library browser Button<[Label; 1]>,
/// 3: queue LibraryBrowser,
/// 4: queue clear button QueueViewer,
/// 4: Edit Panel Button<[Label; 1]>,
children: Vec<Box<dyn GuiElemTrait>>, )>,
pub idle: (bool, Option<Instant>), pub c_context_menu: Option<Box<dyn GuiElem>>,
pub idle: AnimationController<f32>,
// pub settings: (bool, Option<Instant>),
pub settings: (bool, Option<Instant>), pub settings: (bool, Option<Instant>),
pub edit_panel: (bool, Option<Instant>),
pub last_interaction: Instant, pub last_interaction: Instant,
idle_timeout: Option<f64>, idle_timeout: Option<f64>,
pub prev_mouse_pos: Vec2, pub prev_mouse_pos: Vec2,
pub hotkey: Hotkey,
} }
impl GuiScreen { impl GuiScreen {
pub fn open_edit(&mut self, mut edit: Box<dyn GuiElemTrait>) {
if !self.edit_panel.0 {
self.edit_panel = (true, Some(Instant::now()));
edit.config_mut().pos = Rectangle::from_tuples((-0.5, 0.0), (0.0, 0.9));
} else {
edit.config_mut().pos = Rectangle::from_tuples((0.0, 0.0), (0.5, 0.9));
}
if let Some(prev) = self.children.get_mut(4) {
prev.config_mut().enabled = false;
}
self.children.insert(4, edit);
}
pub fn close_edit(&mut self) {
if self.children.len() > 5 {
self.children.remove(4);
self.children[4].config_mut().enabled = true;
} else if self.edit_panel.0 {
self.edit_panel = (false, Some(Instant::now()));
}
}
pub fn new( pub fn new(
config: GuiElemCfg, config: GuiElemCfg,
notif_overlay: NotifOverlay, c_notif_overlay: NotifOverlay,
no_animations: bool,
line_height: f32, line_height: f32,
scroll_sensitivity_pixels: f64, scroll_sensitivity_pixels: f64,
scroll_sensitivity_lines: f64, scroll_sensitivity_lines: f64,
@ -83,81 +69,82 @@ impl GuiScreen {
) -> Self { ) -> Self {
Self { Self {
config: config.w_keyboard_watch().w_mouse().w_keyboard_focus(), config: config.w_keyboard_watch().w_mouse().w_keyboard_focus(),
children: vec![ c_notif_overlay,
Box::new(notif_overlay), c_status_bar: StatusBar::new(GuiElemCfg::at(Rectangle::from_tuples(
Box::new(StatusBar::new( (0.0, 0.9),
GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.9), (1.0, 1.0))), (1.0, 1.0),
true, ))),
)), c_idle_display: IdleDisplay::new(GuiElemCfg::default().disabled()),
Box::new(Settings::new( c_settings: Settings::new(
GuiElemCfg::default().disabled(), GuiElemCfg::default().disabled(),
line_height, no_animations,
scroll_sensitivity_pixels, line_height,
scroll_sensitivity_lines, scroll_sensitivity_pixels,
scroll_sensitivity_pages, scroll_sensitivity_lines,
)), scroll_sensitivity_pages,
Box::new(Panel::new( ),
GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (1.0, 0.9))), c_main_view: Panel::new(
vec![ GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (1.0, 0.9))),
Box::new(Button::new( (
GuiElemCfg::at(Rectangle::from_tuples((0.75, 0.0), (0.875, 0.03))), Button::new(
|_| vec![GuiAction::OpenSettings(true)], GuiElemCfg::at(Rectangle::from_tuples((0.75, 0.0), (0.875, 0.03))),
vec![Box::new(Label::new( |_| vec![GuiAction::OpenSettings(true)],
GuiElemCfg::default(), [Label::new(
"Settings".to_string(), GuiElemCfg::default(),
Color::WHITE, "Settings".to_string(),
None, Color::WHITE,
Vec2::new(0.5, 0.5), None,
))], Vec2::new(0.5, 0.5),
)), )],
Box::new(Button::new( ),
GuiElemCfg::at(Rectangle::from_tuples((0.875, 0.0), (1.0, 0.03))), Button::new(
|_| vec![GuiAction::Exit], GuiElemCfg::at(Rectangle::from_tuples((0.875, 0.0), (1.0, 0.03))),
vec![Box::new(Label::new( |_| vec![GuiAction::Exit],
GuiElemCfg::default(), [Label::new(
"Exit".to_string(), GuiElemCfg::default(),
Color::WHITE, "Exit".to_string(),
None, Color::WHITE,
Vec2::new(0.5, 0.5), None,
))], Vec2::new(0.5, 0.5),
)), )],
Box::new(LibraryBrowser::new(GuiElemCfg::at(Rectangle::from_tuples( ),
(0.0, 0.0), LibraryBrowser::new(GuiElemCfg::at(Rectangle::from_tuples(
(0.5, 1.0), (0.0, 0.0),
)))), (0.5, 1.0),
Box::new(QueueViewer::new(GuiElemCfg::at(Rectangle::from_tuples( ))),
(0.5, 0.03), QueueViewer::new(GuiElemCfg::at(Rectangle::from_tuples(
(1.0, 1.0), (0.5, 0.03),
)))), (1.0, 1.0),
Box::new(Button::new( ))),
GuiElemCfg::at(Rectangle::from_tuples((0.5, 0.0), (0.75, 0.03))), Button::new(
|_| { GuiElemCfg::at(Rectangle::from_tuples((0.5, 0.0), (0.75, 0.03))),
vec![GuiAction::SendToServer( |_| {
musicdb_lib::server::Command::QueueUpdate( vec![GuiAction::SendToServer(
musicdb_lib::server::Command::QueueUpdate(
vec![],
musicdb_lib::data::queue::QueueContent::Folder(
0,
vec![], vec![],
musicdb_lib::data::queue::QueueContent::Folder( String::new(),
0, )
vec![], .into(),
String::new(), ),
) )]
.into(), },
), [Label::new(
)] GuiElemCfg::default(),
}, "Clear Queue".to_string(),
vec![Box::new(Label::new( Color::WHITE,
GuiElemCfg::default(), None,
"Clear Queue".to_string(), Vec2::new(0.5, 0.5),
Color::WHITE, )],
None, ),
Vec2::new(0.5, 0.5), ),
))], ),
)), c_context_menu: None,
], hotkey: Hotkey::new_noshift(VirtualKeyCode::Escape),
)), idle: AnimationController::new(0.0, 0.0, 0.01, 1.0, 0.8, 0.6, Instant::now()),
],
idle: (false, None),
settings: (false, None), settings: (false, None),
edit_panel: (false, None),
last_interaction: Instant::now(), last_interaction: Instant::now(),
idle_timeout: Some(60.0), idle_timeout: Some(60.0),
prev_mouse_pos: Vec2::ZERO, prev_mouse_pos: Vec2::ZERO,
@ -188,29 +175,37 @@ impl GuiScreen {
} }
fn not_idle(&mut self) { fn not_idle(&mut self) {
self.last_interaction = Instant::now(); self.last_interaction = Instant::now();
if self.idle.0 { self.idle.target = 0.0;
self.idle = (false, Some(Instant::now()));
}
} }
fn idle_check(&mut self) { fn idle_check(&mut self) {
if !self.idle.0 { if self.idle.target == 0.0 {
if let Some(dur) = &self.idle_timeout { if let Some(dur) = &self.idle_timeout {
if self.last_interaction.elapsed().as_secs_f64() > *dur { if self.last_interaction.elapsed().as_secs_f64() > *dur {
self.idle = (true, Some(Instant::now())); self.idle.target = 1.0;
} }
} }
} }
} }
} }
impl GuiElemTrait for GuiScreen { impl GuiElem for GuiScreen {
fn config(&self) -> &GuiElemCfg { fn config(&self) -> &GuiElemCfg {
&self.config &self.config
} }
fn config_mut(&mut self) -> &mut GuiElemCfg { fn config_mut(&mut self) -> &mut GuiElemCfg {
&mut self.config &mut self.config
} }
fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElemTrait> + '_> { fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
Box::new(self.children.iter_mut().map(|v| v.as_mut())) Box::new(
[
self.c_notif_overlay.elem_mut(),
self.c_idle_display.elem_mut(),
self.c_status_bar.elem_mut(),
self.c_settings.elem_mut(),
self.c_main_view.elem_mut(),
]
.into_iter()
.chain(self.c_context_menu.as_mut().map(|v| v.elem_mut())),
)
} }
fn any(&self) -> &dyn std::any::Any { fn any(&self) -> &dyn std::any::Any {
self self
@ -218,21 +213,26 @@ impl GuiElemTrait for GuiScreen {
fn any_mut(&mut self) -> &mut dyn std::any::Any { fn any_mut(&mut self) -> &mut dyn std::any::Any {
self self
} }
fn elem(&self) -> &dyn GuiElemTrait { fn elem(&self) -> &dyn GuiElem {
self self
} }
fn elem_mut(&mut self) -> &mut dyn GuiElemTrait { fn elem_mut(&mut self) -> &mut dyn GuiElem {
self self
} }
fn key_watch( fn key_watch(
&mut self, &mut self,
_modifiers: speedy2d::window::ModifiersState, modifiers: speedy2d::window::ModifiersState,
_down: bool, down: bool,
_key: Option<speedy2d::window::VirtualKeyCode>, key: Option<speedy2d::window::VirtualKeyCode>,
_scan: speedy2d::window::KeyScancode, _scan: speedy2d::window::KeyScancode,
) -> Vec<GuiAction> { ) -> Vec<GuiAction> {
self.not_idle(); self.not_idle();
vec![] if self.hotkey.triggered(modifiers, down, key) {
self.config.request_keyboard_focus = true;
vec![GuiAction::ResetKeyboardFocus]
} else {
vec![]
}
} }
fn mouse_down(&mut self, _button: speedy2d::window::MouseButton) -> Vec<GuiAction> { fn mouse_down(&mut self, _button: speedy2d::window::MouseButton) -> Vec<GuiAction> {
self.not_idle(); self.not_idle();
@ -243,7 +243,7 @@ impl GuiElemTrait for GuiScreen {
if self.prev_mouse_pos != info.mouse_pos { if self.prev_mouse_pos != info.mouse_pos {
self.prev_mouse_pos = info.mouse_pos; self.prev_mouse_pos = info.mouse_pos;
self.not_idle(); self.not_idle();
} else if !self.idle.0 && self.config.pixel_pos.size() != info.pos.size() { } else if self.idle.target == 0.0 && self.config.pixel_pos.size() != info.pos.size() {
// resizing prevents idle, but doesn't un-idle // resizing prevents idle, but doesn't un-idle
self.not_idle(); self.not_idle();
} }
@ -254,77 +254,48 @@ impl GuiElemTrait for GuiScreen {
self.idle_check(); self.idle_check();
} }
// request_redraw for animations // request_redraw for animations
if self.idle.1.is_some() || self.settings.1.is_some() || self.edit_panel.1.is_some() { let idle_changed = self.idle.update(info.time, info.no_animations);
if idle_changed || self.settings.1.is_some() {
if let Some(h) = &info.helper { if let Some(h) = &info.helper {
h.request_redraw() h.request_redraw()
} }
} }
// animations: idle // animations: idle
if self.idle.1.is_some() { if idle_changed {
let seconds = if self.idle.0 { 2.0 } else { 0.5 }; let enable_normal_ui = self.idle.value < 1.0;
let p1 = Self::get_prog(&mut self.idle, seconds); self.c_main_view.config_mut().enabled = enable_normal_ui;
if !self.idle.0 || self.idle.1.is_none() { self.c_settings.config_mut().enabled = enable_normal_ui;
if let Some(h) = &info.helper { self.c_status_bar.config_mut().enabled = enable_normal_ui;
h.set_cursor_visible(!self.idle.0); if let Some(h) = &info.helper {
if self.settings.0 { h.set_cursor_visible(enable_normal_ui);
self.children[2].config_mut().enabled = !self.idle.0;
}
if self.edit_panel.0 {
if let Some(c) = self.children.get_mut(4) {
c.config_mut().enabled = !self.idle.0;
}
}
self.children[3].config_mut().enabled = !self.idle.0;
}
} }
let p = transition(p1); let idcfg = self.c_idle_display.config_mut();
self.children[1].config_mut().pos = let top = 1.0 - self.idle.value;
Rectangle::from_tuples((0.0, 0.9 - 0.9 * p), (1.0, 1.0)); let bottom = top + 1.0;
self.children[1] idcfg.pos = Rectangle::from_tuples((0.0, top), (1.0, bottom));
.any_mut() idcfg.enabled = self.idle.value > 0.0;
.downcast_mut::<StatusBar>() self.c_status_bar.idle_mode = self.idle.value;
.unwrap() self.c_idle_display.idle_mode = self.idle.value;
.idle_mode = p;
} }
// animations: settings // animations: settings
if self.settings.1.is_some() { if self.settings.1.is_some() {
let p1 = Self::get_prog(&mut self.settings, 0.3); let p1 = Self::get_prog(&mut self.settings, 0.3);
let p = transition(p1); let p = transition(p1);
let cfg = self.children[2].config_mut(); let cfg = self.c_settings.config_mut();
cfg.enabled = p > 0.0; cfg.enabled = p > 0.0;
cfg.pos = Rectangle::from_tuples((0.0, 0.9 - 0.9 * p), (1.0, 0.9)); cfg.pos = Rectangle::from_tuples((0.0, 0.9 - 0.9 * p), (1.0, 0.9));
} }
// animations: edit_panel
if self.edit_panel.1.is_some() {
let p1 = Self::get_prog(&mut self.edit_panel, 0.3);
let p = transition(p1);
if let Some(c) = self.children.get_mut(4) {
c.config_mut().enabled = p > 0.0;
c.config_mut().pos = Rectangle::from_tuples((-0.5 + 0.5 * p, 0.0), (0.5 * p, 0.9));
}
if !self.edit_panel.0 && p == 0.0 {
while self.children.len() > 4 {
self.children.pop();
}
}
self.children[3].config_mut().pos =
Rectangle::from_tuples((0.5 * p, 0.0), (1.0 + 0.5 * p, 0.9));
}
// set idle timeout (only when settings are open) // set idle timeout (only when settings are open)
if self.settings.0 || self.settings.1.is_some() { if self.settings.0 || self.settings.1.is_some() {
self.idle_timeout = self.children[2] self.idle_timeout = self.c_settings.get_timeout_val();
.any()
.downcast_ref::<Settings>()
.unwrap()
.get_timeout_val();
} }
} }
fn key_focus( fn key_focus(
&mut self, &mut self,
modifiers: speedy2d::window::ModifiersState, _modifiers: speedy2d::window::ModifiersState,
down: bool, down: bool,
key: Option<speedy2d::window::VirtualKeyCode>, key: Option<speedy2d::window::VirtualKeyCode>,
scan: speedy2d::window::KeyScancode, _scan: speedy2d::window::KeyScancode,
) -> Vec<GuiAction> { ) -> Vec<GuiAction> {
if down && matches!(key, Some(VirtualKeyCode::Space)) { if down && matches!(key, Some(VirtualKeyCode::Space)) {
vec![GuiAction::Build(Box::new(|db| { vec![GuiAction::Build(Box::new(|db| {
@ -344,116 +315,3 @@ impl GuiElemTrait for GuiScreen {
} }
} }
} }
pub struct StatusBar {
config: GuiElemCfg,
children: Vec<Box<dyn GuiElemTrait>>,
idle_mode: f32,
idle_prev: f32,
pos_current_song_s: Rectangle,
pos_current_song_l: Rectangle,
pos_play_pause_s: Rectangle,
pos_play_pause_l: Rectangle,
}
impl StatusBar {
pub fn new(config: GuiElemCfg, playing: bool) -> Self {
let pos_current_song_s = Rectangle::new(Vec2::ZERO, Vec2::new(0.8, 1.0));
let pos_current_song_l = Rectangle::new(Vec2::ZERO, Vec2::new(1.0, 1.0));
let pos_play_pause_s = Rectangle::from_tuples((0.85, 0.0), (0.95, 1.0));
let pos_play_pause_l = Rectangle::from_tuples((0.85, 0.8), (0.95, 1.0));
Self {
config,
children: vec![
Box::new(CurrentSong::new(GuiElemCfg::at(pos_current_song_s.clone()))),
Box::new(PlayPauseToggle::new(
GuiElemCfg::at(pos_play_pause_s.clone()),
playing,
)),
Box::new(Panel::new(GuiElemCfg::default(), vec![])),
],
idle_mode: 0.0,
idle_prev: 0.0,
pos_current_song_s,
pos_current_song_l,
pos_play_pause_s,
pos_play_pause_l,
}
}
const fn index_current_song() -> usize {
0
}
const fn index_play_pause_toggle() -> usize {
1
}
const fn index_bgpanel() -> usize {
2
}
pub fn set_background(&mut self, bg: Option<Color>) {
self.children[Self::index_bgpanel()]
.any_mut()
.downcast_mut::<Panel>()
.unwrap()
.background = bg;
}
}
impl GuiElemTrait for StatusBar {
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 GuiElemTrait> + '_> {
Box::new(self.children.iter_mut().map(|v| v.as_mut()))
}
fn any(&self) -> &dyn std::any::Any {
self
}
fn any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn elem(&self) -> &dyn GuiElemTrait {
self
}
fn elem_mut(&mut self) -> &mut dyn GuiElemTrait {
self
}
fn draw(&mut self, info: &mut DrawInfo, g: &mut Graphics2D) {
// the line that separates this section from the rest of the ui.
// fades away when idle_mode approaches 1.0
if self.idle_mode < 1.0 {
g.draw_line(
info.pos.top_left(),
info.pos.top_right(),
2.0,
Color::from_rgba(1.0, 1.0, 1.0, 1.0 - self.idle_mode),
);
}
if self.idle_mode != self.idle_prev {
// if exiting the moving stage, set background to transparent.
// if entering the moving stage, set background to black.
if self.idle_mode == 1.0 || self.idle_mode == 0.0 {
self.set_background(None);
} else if self.idle_prev == 1.0 || self.idle_prev == 0.0 {
self.set_background(Some(Color::BLACK));
}
// position the text
let l = self.idle_mode;
let current_song = self.children[Self::index_current_song()]
.any_mut()
.downcast_mut::<CurrentSong>()
.unwrap();
current_song.set_idle_mode(self.idle_mode);
current_song.config_mut().pos =
morph_rect(&self.pos_current_song_s, &self.pos_current_song_l, l);
let play_pause = self.children[Self::index_play_pause_toggle()]
.any_mut()
.downcast_mut::<PlayPauseToggle>()
.unwrap();
play_pause.config_mut().pos =
morph_rect(&self.pos_play_pause_s, &self.pos_play_pause_l, l);
// - - - - -
self.idle_prev = self.idle_mode;
}
}
}

View File

@ -1,18 +1,20 @@
use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle, Graphics2D}; use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle, Graphics2D};
use crate::{ use crate::{
gui::{DrawInfo, GuiAction, GuiElemCfg, GuiElemTrait}, gui::{DrawInfo, GuiAction, GuiElem, GuiElemCfg, GuiElemChildren},
gui_base::{Button, Panel, ScrollBox, Slider}, gui_base::{Button, Panel, ScrollBox, Slider},
gui_text::Label, gui_text::Label,
}; };
pub struct Settings { pub struct Settings {
pub config: GuiElemCfg, pub config: GuiElemCfg,
pub children: Vec<Box<dyn GuiElemTrait>>, c_scroll_box: ScrollBox<SettingsContent>,
c_background: Panel<()>,
} }
impl Settings { impl Settings {
pub fn new( pub fn new(
mut config: GuiElemCfg, mut config: GuiElemCfg,
no_animations: bool,
line_height: f32, line_height: f32,
scroll_sensitivity_pixels: f64, scroll_sensitivity_pixels: f64,
scroll_sensitivity_lines: f64, scroll_sensitivity_lines: f64,
@ -21,224 +23,23 @@ impl Settings {
config.redraw = true; config.redraw = true;
Self { Self {
config, config,
children: vec![ c_scroll_box: ScrollBox::new(
Box::new(ScrollBox::new( GuiElemCfg::default(),
GuiElemCfg::default(), crate::gui_base::ScrollBoxSizeUnit::Pixels,
crate::gui_base::ScrollBoxSizeUnit::Pixels, SettingsContent::new(
vec![ no_animations,
( line_height,
Box::new(Button::new( scroll_sensitivity_pixels,
GuiElemCfg::at(Rectangle::from_tuples((0.75, 0.0), (1.0, 1.0))), scroll_sensitivity_lines,
|btn| vec![GuiAction::OpenSettings(false)], scroll_sensitivity_pages,
vec![Box::new(Label::new( ),
GuiElemCfg::default(), vec![],
"Back".to_string(), ),
Color::WHITE, c_background: Panel::with_background(GuiElemCfg::default().w_mouse(), (), Color::BLACK),
None,
Vec2::new(0.5, 0.5),
))],
)),
0.0,
),
(
Box::new(Panel::new(
GuiElemCfg::default(),
vec![
Box::new(Label::new(
GuiElemCfg::at(Rectangle::from_tuples(
(0.0, 0.0),
(0.33, 1.0),
)),
"Settings panel opacity".to_string(),
Color::WHITE,
None,
Vec2::new(0.9, 0.5),
)),
Box::new({
let mut s = Slider::new_labeled(
GuiElemCfg::at(Rectangle::from_tuples(
(0.33, 0.0),
(1.0, 1.0),
)),
0.0,
1.0,
1.0,
|slider, label, _info| {
if slider.val_changed() {
*label.content.text() =
format!("{:.0}%", slider.val * 100.0);
}
},
);
s.val_changed_subs.push(false);
s
}),
],
)),
0.0,
),
(
Box::new(Panel::new(
GuiElemCfg::default(),
vec![
Box::new(Label::new(
GuiElemCfg::at(Rectangle::from_tuples(
(0.0, 0.0),
(0.33, 1.0),
)),
"Line Height / Text Size".to_string(),
Color::WHITE,
None,
Vec2::new(0.9, 0.5),
)),
Box::new(Slider::new_labeled(
GuiElemCfg::at(Rectangle::from_tuples(
(0.33, 0.0),
(1.0, 1.0),
)),
16.0,
80.0,
line_height as _,
|slider, label, info| {
if slider.val_changed() {
*label.content.text() =
format!("line height: {:.0}", slider.val);
let h = slider.val as _;
info.actions.push(GuiAction::SetLineHeight(h));
}
},
)),
],
)),
0.0,
),
(
Box::new(Panel::new(
GuiElemCfg::default(),
vec![
Box::new(Label::new(
GuiElemCfg::at(Rectangle::from_tuples(
(0.0, 0.0),
(0.33, 1.0),
)),
"Scroll Sensitivity".to_string(),
Color::WHITE,
None,
Vec2::new(0.9, 0.5),
)),
Box::new(Slider::new_labeled(
GuiElemCfg::at(Rectangle::from_tuples(
(0.33, 0.0),
(1.0, 1.0),
)),
0.0,
12.0,
scroll_sensitivity_lines,
|slider, label, info| {
if slider.val_changed() {
*label.content.text() =
format!("{:.1}", slider.val);
let h = slider.val as _;
info.actions.push(GuiAction::Do(Box::new(
move |gui| gui.scroll_lines_multiplier = h,
)));
}
},
)),
],
)),
0.0,
),
(
Box::new(Panel::new(
GuiElemCfg::default(),
vec![
Box::new(Label::new(
GuiElemCfg::at(Rectangle::from_tuples(
(0.0, 0.0),
(0.33, 1.0),
)),
"Idle time".to_string(),
Color::WHITE,
None,
Vec2::new(0.9, 0.5),
)),
Box::new(Slider::new_labeled(
GuiElemCfg::at(Rectangle::from_tuples(
(0.33, 0.0),
(1.0, 1.0),
)),
0.0,
(60.0f64 * 60.0 * 6.0).sqrt(),
60.0f64.sqrt(),
|slider, label, info| {
if slider.val_changed() {
*label.content.text() = if slider.val > 0.0 {
let mut s = String::new();
let seconds = (slider.val * slider.val) as u64;
let hours = seconds / 3600;
let seconds = seconds % 3600;
let minutes = seconds / 60;
let seconds = seconds % 60;
if hours > 0 {
s = hours.to_string();
s.push_str("h ");
}
if minutes > 0 || hours > 0 && seconds > 0 {
s.push_str(&minutes.to_string());
s.push_str("m ");
}
if hours == 0
&& minutes < 10
&& (seconds > 0 || minutes == 0)
{
s.push_str(&seconds.to_string());
s.push_str("s");
} else if s.ends_with(" ") {
s.pop();
}
s
} else {
"no timeout".to_string()
}
};
let h = slider.val as _;
if slider.val_changed() {
info.actions.push(GuiAction::Do(Box::new(
move |gui| gui.scroll_lines_multiplier = h,
)));
}
},
)),
],
)),
0.0,
),
],
)),
Box::new(Panel::with_background(
GuiElemCfg::default().w_mouse(),
vec![],
Color::BLACK,
)),
],
} }
} }
pub fn get_timeout_val(&self) -> Option<f64> { pub fn get_timeout_val(&self) -> Option<f64> {
let v = self.children[0] let v = self.c_scroll_box.children.idle_time.children.1.val;
.any()
.downcast_ref::<ScrollBox>()
.unwrap()
.children[4]
.0
.any()
.downcast_ref::<Panel>()
.unwrap()
.children[1]
.any()
.downcast_ref::<Slider>()
.unwrap()
.val;
if v > 0.0 { if v > 0.0 {
Some(v * v) Some(v * v)
} else { } else {
@ -246,15 +47,229 @@ impl Settings {
} }
} }
} }
impl GuiElemTrait for Settings { struct SettingsContent {
back_button: Button<[Label; 1]>,
opacity: Panel<(Label, Slider)>,
animations_toggle: Panel<(Label, Button<[Label; 1]>)>,
line_height: Panel<(Label, Slider)>,
scroll_sensitivity: Panel<(Label, Slider)>,
idle_time: Panel<(Label, Slider)>,
}
impl GuiElemChildren for SettingsContent {
fn iter(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
Box::new(
[
self.back_button.elem_mut(),
self.opacity.elem_mut(),
self.animations_toggle.elem_mut(),
self.line_height.elem_mut(),
self.scroll_sensitivity.elem_mut(),
self.idle_time.elem_mut(),
]
.into_iter(),
)
}
fn len(&self) -> usize {
6
}
}
impl SettingsContent {
pub fn new(
no_animations: bool,
line_height: f32,
_scroll_sensitivity_pixels: f64,
scroll_sensitivity_lines: f64,
_scroll_sensitivity_pages: f64,
) -> Self {
Self {
back_button: Button::new(
GuiElemCfg::at(Rectangle::from_tuples((0.75, 0.0), (1.0, 1.0))),
|_| vec![GuiAction::OpenSettings(false)],
[Label::new(
GuiElemCfg::default(),
"Back".to_string(),
Color::WHITE,
None,
Vec2::new(0.5, 0.5),
)],
),
opacity: Panel::new(
GuiElemCfg::default(),
(
Label::new(
GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (0.33, 1.0))),
"Settings panel opacity".to_string(),
Color::WHITE,
None,
Vec2::new(0.9, 0.5),
),
{
let mut s = Slider::new_labeled(
GuiElemCfg::at(Rectangle::from_tuples((0.33, 0.0), (1.0, 1.0))),
0.0,
1.0,
1.0,
|slider, label, _info| {
if slider.val_changed() {
*label.content.text() = format!("{:.0}%", slider.val * 100.0);
}
},
);
s.val_changed_subs.push(false);
s
},
),
),
animations_toggle: Panel::new(
GuiElemCfg::default(),
(
Label::new(
GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (0.33, 1.0))),
"Animations".to_string(),
Color::WHITE,
None,
Vec2::new(1.0, 0.5),
),
Button::new(
GuiElemCfg::at(Rectangle::from_tuples((0.75, 0.0), (1.0, 1.0))),
|b| {
let text = b.children[0].content.text();
let ad = if text == "On" {
*text = "Off".to_string();
true
} else {
*text = "On".to_string();
false
};
vec![GuiAction::SetAnimationsDisabled(ad)]
},
[Label::new(
GuiElemCfg::default(),
if no_animations { "Off" } else { "On" }.to_string(),
Color::WHITE,
None,
Vec2::new(0.5, 0.5),
)],
),
),
),
line_height: Panel::new(
GuiElemCfg::default(),
(
Label::new(
GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (0.33, 1.0))),
"Line Height / Text Size".to_string(),
Color::WHITE,
None,
Vec2::new(0.9, 0.5),
),
Slider::new_labeled(
GuiElemCfg::at(Rectangle::from_tuples((0.33, 0.0), (1.0, 1.0))),
16.0,
80.0,
line_height as _,
|slider, label, info| {
if slider.val_changed() {
*label.content.text() = format!("line height: {:.0}", slider.val);
let h = slider.val as _;
info.actions.push(GuiAction::SetLineHeight(h));
}
},
),
),
),
scroll_sensitivity: Panel::new(
GuiElemCfg::default(),
(
Label::new(
GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (0.33, 1.0))),
"Scroll Sensitivity".to_string(),
Color::WHITE,
None,
Vec2::new(0.9, 0.5),
),
Slider::new_labeled(
GuiElemCfg::at(Rectangle::from_tuples((0.33, 0.0), (1.0, 1.0))),
0.0,
12.0,
scroll_sensitivity_lines,
|slider, label, info| {
if slider.val_changed() {
*label.content.text() = format!("{:.1}", slider.val);
let h = slider.val as _;
info.actions.push(GuiAction::Do(Box::new(move |gui| {
gui.scroll_lines_multiplier = h
})));
}
},
),
),
),
idle_time: Panel::new(
GuiElemCfg::default(),
(
Label::new(
GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (0.33, 1.0))),
"Idle time".to_string(),
Color::WHITE,
None,
Vec2::new(0.9, 0.5),
),
Slider::new_labeled(
GuiElemCfg::at(Rectangle::from_tuples((0.33, 0.0), (1.0, 1.0))),
0.0,
(60.0f64 * 60.0).sqrt(),
60.0f64.sqrt(),
|slider, label, info| {
if slider.val_changed() {
*label.content.text() = if slider.val > 0.0 {
let mut s = String::new();
let seconds = (slider.val * slider.val) as u64;
let hours = seconds / 3600;
let seconds = seconds % 3600;
let minutes = seconds / 60;
let seconds = seconds % 60;
if hours > 0 {
s = hours.to_string();
s.push_str("h ");
}
if minutes > 0 || hours > 0 && seconds > 0 {
s.push_str(&minutes.to_string());
s.push_str("m ");
}
if hours == 0 && minutes < 10 && (seconds > 0 || minutes == 0) {
s.push_str(&seconds.to_string());
s.push_str("s");
} else if s.ends_with(" ") {
s.pop();
}
s
} else {
"no timeout".to_string()
}
};
let h = slider.val as _;
if slider.val_changed() {
info.actions.push(GuiAction::Do(Box::new(move |gui| {
gui.scroll_lines_multiplier = h
})));
}
},
),
),
),
}
}
}
impl GuiElem for Settings {
fn config(&self) -> &GuiElemCfg { fn config(&self) -> &GuiElemCfg {
&self.config &self.config
} }
fn config_mut(&mut self) -> &mut GuiElemCfg { fn config_mut(&mut self) -> &mut GuiElemCfg {
&mut self.config &mut self.config
} }
fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElemTrait> + '_> { fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
Box::new(self.children.iter_mut().map(|v| v.as_mut())) Box::new([self.c_scroll_box.elem_mut(), self.c_background.elem_mut()].into_iter())
} }
fn any(&self) -> &dyn std::any::Any { fn any(&self) -> &dyn std::any::Any {
self self
@ -262,33 +277,19 @@ impl GuiElemTrait for Settings {
fn any_mut(&mut self) -> &mut dyn std::any::Any { fn any_mut(&mut self) -> &mut dyn std::any::Any {
self self
} }
fn elem(&self) -> &dyn GuiElemTrait { fn elem(&self) -> &dyn GuiElem {
self self
} }
fn elem_mut(&mut self) -> &mut dyn GuiElemTrait { fn elem_mut(&mut self) -> &mut dyn GuiElem {
self self
} }
fn draw(&mut self, info: &mut DrawInfo, _g: &mut Graphics2D) { fn draw(&mut self, info: &mut DrawInfo, _g: &mut Graphics2D) {
let (rest, background) = self.children.split_at_mut(1); let scrollbox = &mut self.c_scroll_box;
let scrollbox = rest[0].any_mut().downcast_mut::<ScrollBox>().unwrap(); let background = &mut self.c_background;
let settings_opacity_slider = scrollbox.children[1] let settings_opacity_slider = &mut scrollbox.children.opacity.children.1;
.0
.any_mut()
.downcast_mut::<Panel>()
.unwrap()
.children[1]
.any_mut()
.downcast_mut::<Slider>()
.unwrap();
if settings_opacity_slider.val_changed_subs[0] { if settings_opacity_slider.val_changed_subs[0] {
settings_opacity_slider.val_changed_subs[0] = false; settings_opacity_slider.val_changed_subs[0] = false;
let color = background[0] let color = background.background.as_mut().unwrap();
.any_mut()
.downcast_mut::<Panel>()
.unwrap()
.background
.as_mut()
.unwrap();
*color = Color::from_rgba( *color = Color::from_rgba(
color.r(), color.r(),
color.g(), color.g(),
@ -298,12 +299,18 @@ impl GuiElemTrait for Settings {
} }
if self.config.redraw { if self.config.redraw {
self.config.redraw = false; self.config.redraw = false;
for (i, (_, h)) in scrollbox.children.iter_mut().enumerate() { scrollbox.config_mut().redraw = true;
*h = if i == 0 { if scrollbox.children_heights.len() == scrollbox.children.len() {
info.line_height * 2.0 for (i, h) in scrollbox.children_heights.iter_mut().enumerate() {
} else { *h = if i == 0 {
info.line_height info.line_height * 2.0
}; } else {
info.line_height
};
}
} else {
// try again next frame (scrollbox will autofill the children_heights vec)
self.config.redraw = true;
} }
} }
} }

View File

@ -0,0 +1,123 @@
use std::time::Instant;
use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle};
use crate::{
gui::{DrawInfo, GuiElem, GuiElemCfg},
gui_anim::AnimationController,
gui_playback::{image_display, CurrentInfo},
gui_text::AdvancedLabel,
};
pub struct StatusBar {
config: GuiElemCfg,
pub idle_mode: f32,
current_info: CurrentInfo,
cover_aspect_ratio: AnimationController<f32>,
c_song_label: AdvancedLabel,
}
impl StatusBar {
pub fn new(config: GuiElemCfg) -> Self {
Self {
config,
idle_mode: 0.0,
current_info: CurrentInfo::new(),
cover_aspect_ratio: AnimationController::new(
0.0,
0.0,
0.01,
1.0,
0.8,
0.6,
Instant::now(),
),
c_song_label: AdvancedLabel::new(GuiElemCfg::default(), Vec2::new(0.0, 0.5), vec![]),
}
}
}
impl GuiElem for StatusBar {
fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
Box::new([self.c_song_label.elem_mut()].into_iter())
}
fn draw(&mut self, info: &mut DrawInfo, g: &mut speedy2d::Graphics2D) {
self.current_info.update(info, g);
if self.current_info.new_song {
self.current_info.new_song = false;
self.c_song_label.content = if let Some(song) = self.current_info.current_song {
info.gui_config
.status_bar_text
.gen(&info.database, info.database.get_song(&song))
} else {
vec![]
};
self.c_song_label.config_mut().redraw = true;
}
if self.current_info.new_cover {
self.current_info.new_cover = false;
match self.current_info.current_cover {
None | Some((_, Some(None))) => {
self.cover_aspect_ratio.target = 0.0;
}
Some((_, None)) | Some((_, Some(Some(_)))) => {}
}
}
// move children to make space for cover
let ar_updated = self
.cover_aspect_ratio
.update(info.time.clone(), info.no_animations);
if ar_updated || info.pos.size() != self.config.pixel_pos.size() {
if let Some(h) = &info.helper {
h.request_redraw();
}
self.c_song_label.config_mut().pos = Rectangle::from_tuples(
(
self.cover_aspect_ratio.value * info.pos.height() / info.pos.width(),
0.0,
),
(1.0, 1.0),
);
}
// draw cover
if let Some(Some(cover)) = self
.current_info
.current_cover
.as_ref()
.map(|v| v.1.as_ref())
{
image_display(
g,
cover.as_ref(),
info.pos.top_left().x + info.pos.height() * 0.05,
info.pos.top_left().y + info.pos.height() * 0.05,
info.pos.top_left().y + info.pos.height() * 0.95,
&mut self.cover_aspect_ratio,
);
}
}
fn config(&self) -> &GuiElemCfg {
&self.config
}
fn config_mut(&mut self) -> &mut GuiElemCfg {
&mut self.config
}
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
}
fn updated_library(&mut self) {
self.current_info.update = true;
}
fn updated_queue(&mut self) {
self.current_info.update = true;
}
}

View File

@ -8,7 +8,7 @@ use speedy2d::{
window::{ModifiersState, MouseButton}, window::{ModifiersState, MouseButton},
}; };
use crate::gui::{GuiAction, GuiElemCfg, GuiElemTrait}; use crate::gui::{GuiAction, GuiElem, GuiElemCfg};
/* /*
@ -17,9 +17,9 @@ except they are all text-related.
*/ */
#[derive(Clone)]
pub struct Label { pub struct Label {
config: GuiElemCfg, config: GuiElemCfg,
children: Vec<Box<dyn GuiElemTrait>>,
pub content: Content, pub content: Content,
pub pos: Vec2, pub pos: Vec2,
} }
@ -30,6 +30,7 @@ pub struct Content {
background: Option<Color>, background: Option<Color>,
formatted: Option<Rc<FormattedTextBlock>>, formatted: Option<Rc<FormattedTextBlock>>,
} }
#[allow(unused)]
impl Content { impl Content {
pub fn new(text: String, color: Color) -> Self { pub fn new(text: String, color: Color) -> Self {
Self { Self {
@ -72,7 +73,6 @@ impl Label {
) -> Self { ) -> Self {
Self { Self {
config, config,
children: vec![],
content: Content { content: Content {
text, text,
color, color,
@ -83,15 +83,15 @@ impl Label {
} }
} }
} }
impl GuiElemTrait for Label { impl GuiElem for Label {
fn config(&self) -> &GuiElemCfg { fn config(&self) -> &GuiElemCfg {
&self.config &self.config
} }
fn config_mut(&mut self) -> &mut GuiElemCfg { fn config_mut(&mut self) -> &mut GuiElemCfg {
&mut self.config &mut self.config
} }
fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElemTrait> + '_> { fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
Box::new(self.children.iter_mut().map(|v| v.as_mut())) Box::new([].into_iter())
} }
fn any(&self) -> &dyn std::any::Any { fn any(&self) -> &dyn std::any::Any {
self self
@ -99,10 +99,10 @@ impl GuiElemTrait for Label {
fn any_mut(&mut self) -> &mut dyn std::any::Any { fn any_mut(&mut self) -> &mut dyn std::any::Any {
self self
} }
fn elem(&self) -> &dyn GuiElemTrait { fn elem(&self) -> &dyn GuiElem {
self self
} }
fn elem_mut(&mut self) -> &mut dyn GuiElemTrait { fn elem_mut(&mut self) -> &mut dyn GuiElem {
self self
} }
fn draw(&mut self, info: &mut crate::gui::DrawInfo, g: &mut speedy2d::Graphics2D) { fn draw(&mut self, info: &mut crate::gui::DrawInfo, g: &mut speedy2d::Graphics2D) {
@ -146,7 +146,8 @@ impl GuiElemTrait for Label {
/// a single-line text field for users to type text into. /// a single-line text field for users to type text into.
pub struct TextField { pub struct TextField {
config: GuiElemCfg, config: GuiElemCfg,
pub children: Vec<Box<dyn GuiElemTrait>>, pub c_input: Label,
pub c_hint: Label,
pub on_changed: Option<Box<dyn FnMut(&str)>>, pub on_changed: Option<Box<dyn FnMut(&str)>>,
pub on_changed_mut: Option<Box<dyn FnMut(&mut Self, String)>>, pub on_changed_mut: Option<Box<dyn FnMut(&mut Self, String)>>,
} }
@ -164,52 +165,38 @@ impl TextField {
let text_is_empty = text.is_empty(); let text_is_empty = text.is_empty();
Self { Self {
config: config.w_mouse().w_keyboard_focus(), config: config.w_mouse().w_keyboard_focus(),
children: vec![ c_input: Label::new(
Box::new(Label::new( GuiElemCfg::default(),
GuiElemCfg::default(), text,
text, color_input,
color_input, None,
None, Vec2::new(0.0, 0.5),
Vec2::new(0.0, 0.5), ),
)), c_hint: Label::new(
Box::new(Label::new( if text_is_empty {
if text_is_empty { GuiElemCfg::default()
GuiElemCfg::default() } else {
} else { GuiElemCfg::default().disabled()
GuiElemCfg::default().disabled() },
}, hint,
hint, color_hint,
color_hint, None,
None, Vec2::new(0.0, 0.5),
Vec2::new(0.0, 0.5), ),
)),
],
on_changed: None, on_changed: None,
on_changed_mut: None, on_changed_mut: None,
} }
} }
pub fn label_input(&self) -> &Label {
self.children[0].any().downcast_ref().unwrap()
}
pub fn label_input_mut(&mut self) -> &mut Label {
self.children[0].any_mut().downcast_mut().unwrap()
}
pub fn label_hint(&self) -> &Label {
self.children[1].any().downcast_ref().unwrap()
}
pub fn label_hint_mut(&mut self) -> &mut Label {
self.children[1].any_mut().downcast_mut().unwrap()
}
} }
impl GuiElemTrait for TextField { impl GuiElem for TextField {
fn config(&self) -> &GuiElemCfg { fn config(&self) -> &GuiElemCfg {
&self.config &self.config
} }
fn config_mut(&mut self) -> &mut GuiElemCfg { fn config_mut(&mut self) -> &mut GuiElemCfg {
&mut self.config &mut self.config
} }
fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElemTrait> + '_> { fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
Box::new(self.children.iter_mut().map(|v| v.as_mut())) Box::new([self.c_input.elem_mut(), self.c_hint.elem_mut()].into_iter())
} }
fn any(&self) -> &dyn std::any::Any { fn any(&self) -> &dyn std::any::Any {
self self
@ -217,10 +204,10 @@ impl GuiElemTrait for TextField {
fn any_mut(&mut self) -> &mut dyn std::any::Any { fn any_mut(&mut self) -> &mut dyn std::any::Any {
self self
} }
fn elem(&self) -> &dyn GuiElemTrait { fn elem(&self) -> &dyn GuiElem {
self self
} }
fn elem_mut(&mut self) -> &mut dyn GuiElemTrait { fn elem_mut(&mut self) -> &mut dyn GuiElem {
self self
} }
fn draw(&mut self, info: &mut crate::gui::DrawInfo, g: &mut speedy2d::Graphics2D) { fn draw(&mut self, info: &mut crate::gui::DrawInfo, g: &mut speedy2d::Graphics2D) {
@ -240,11 +227,7 @@ impl GuiElemTrait for TextField {
} }
fn char_focus(&mut self, modifiers: ModifiersState, key: char) -> Vec<GuiAction> { fn char_focus(&mut self, modifiers: ModifiersState, key: char) -> Vec<GuiAction> {
if !(modifiers.ctrl() || modifiers.alt() || modifiers.logo()) && !key.is_control() { if !(modifiers.ctrl() || modifiers.alt() || modifiers.logo()) && !key.is_control() {
let content = &mut self.children[0] let content = &mut self.c_input.content;
.any_mut()
.downcast_mut::<Label>()
.unwrap()
.content;
let was_empty = content.get_text().is_empty(); let was_empty = content.get_text().is_empty();
content.text().push(key); content.text().push(key);
if let Some(f) = &mut self.on_changed { if let Some(f) = &mut self.on_changed {
@ -256,7 +239,7 @@ impl GuiElemTrait for TextField {
self.on_changed_mut = Some(f); self.on_changed_mut = Some(f);
} }
if was_empty { if was_empty {
self.children[1].config_mut().enabled = false; self.c_hint.config_mut().enabled = false;
} }
} }
vec![] vec![]
@ -272,11 +255,7 @@ impl GuiElemTrait for TextField {
&& !(modifiers.alt() || modifiers.logo()) && !(modifiers.alt() || modifiers.logo())
&& key == Some(speedy2d::window::VirtualKeyCode::Backspace) && key == Some(speedy2d::window::VirtualKeyCode::Backspace)
{ {
let content = &mut self.children[0] let content = &mut self.c_input.content;
.any_mut()
.downcast_mut::<Label>()
.unwrap()
.content;
if !content.get_text().is_empty() { if !content.get_text().is_empty() {
if modifiers.ctrl() { if modifiers.ctrl() {
for s in [true, false, true] { for s in [true, false, true] {
@ -299,7 +278,7 @@ impl GuiElemTrait for TextField {
self.on_changed_mut = Some(f); self.on_changed_mut = Some(f);
} }
if is_now_empty { if is_now_empty {
self.children[1].config_mut().enabled = true; self.c_hint.config_mut().enabled = true;
} }
} }
} }
@ -311,7 +290,7 @@ impl GuiElemTrait for TextField {
/// Allows stringing together multiple `Content`s in one line. /// Allows stringing together multiple `Content`s in one line.
pub struct AdvancedLabel { pub struct AdvancedLabel {
config: GuiElemCfg, config: GuiElemCfg,
children: Vec<Box<dyn GuiElemTrait>>, children: Vec<Box<dyn GuiElem>>,
/// 0.0 => align to top/left /// 0.0 => align to top/left
/// 0.5 => center /// 0.5 => center
/// 1.0 => align to bottom/right /// 1.0 => align to bottom/right
@ -334,15 +313,15 @@ impl AdvancedLabel {
} }
} }
} }
impl GuiElemTrait for AdvancedLabel { impl GuiElem for AdvancedLabel {
fn config(&self) -> &GuiElemCfg { fn config(&self) -> &GuiElemCfg {
&self.config &self.config
} }
fn config_mut(&mut self) -> &mut GuiElemCfg { fn config_mut(&mut self) -> &mut GuiElemCfg {
&mut self.config &mut self.config
} }
fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElemTrait> + '_> { fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
Box::new(self.children.iter_mut().map(|v| v.as_mut())) Box::new(self.children.iter_mut().map(|v| v.elem_mut()))
} }
fn any(&self) -> &dyn std::any::Any { fn any(&self) -> &dyn std::any::Any {
self self
@ -350,10 +329,10 @@ impl GuiElemTrait for AdvancedLabel {
fn any_mut(&mut self) -> &mut dyn std::any::Any { fn any_mut(&mut self) -> &mut dyn std::any::Any {
self self
} }
fn elem(&self) -> &dyn GuiElemTrait { fn elem(&self) -> &dyn GuiElem {
self self
} }
fn elem_mut(&mut self) -> &mut dyn GuiElemTrait { fn elem_mut(&mut self) -> &mut dyn GuiElem {
self self
} }
fn draw(&mut self, info: &mut crate::gui::DrawInfo, g: &mut speedy2d::Graphics2D) { fn draw(&mut self, info: &mut crate::gui::DrawInfo, g: &mut speedy2d::Graphics2D) {

View File

@ -1,149 +1,62 @@
use speedy2d::{ use speedy2d::window::VirtualKeyCode;
window::{MouseButton, VirtualKeyCode},
Graphics2D,
};
use crate::gui::{DrawInfo, GuiAction, GuiElemCfg, GuiElemTrait}; /// requires `keyboard_events_watch = true`
pub struct Hotkey {
pub struct WithFocusHotkey<T: GuiElemTrait> { /// 4 * (ignore, pressed): 10 (or 11, but 0b11111111 -> never) -> doesn't matter, 01 -> must be pressed, 00 -> must not be pressed
pub inner: T,
/// 4 * (ignore, pressed): 10 or 11 -> doesn't matter, 01 -> must be pressed, 00 -> must not be pressed
/// logo alt shift ctrl /// logo alt shift ctrl
pub modifiers: u8, pub modifiers: u8,
pub key: VirtualKeyCode, pub key: VirtualKeyCode,
} }
impl<T: GuiElemTrait> WithFocusHotkey<T> { #[allow(unused)]
/// unlike noshift, this ignores the shift modifier impl Hotkey {
pub fn new_key(key: VirtualKeyCode, inner: T) -> WithFocusHotkey<T> { pub fn triggered(
Self::new(0b1000, key, inner) &self,
}
/// requires the key to be pressed without any modifiers
pub fn new_noshift(key: VirtualKeyCode, inner: T) -> WithFocusHotkey<T> {
Self::new(0, key, inner)
}
pub fn new_shift(key: VirtualKeyCode, inner: T) -> WithFocusHotkey<T> {
Self::new(0b0100, key, inner)
}
pub fn new_ctrl(key: VirtualKeyCode, inner: T) -> WithFocusHotkey<T> {
Self::new(0b01, key, inner)
}
pub fn new_ctrl_shift(key: VirtualKeyCode, inner: T) -> WithFocusHotkey<T> {
Self::new(0b0101, key, inner)
}
pub fn new_alt(key: VirtualKeyCode, inner: T) -> WithFocusHotkey<T> {
Self::new(0b010000, key, inner)
}
pub fn new_alt_shift(key: VirtualKeyCode, inner: T) -> WithFocusHotkey<T> {
Self::new(0b010100, key, inner)
}
pub fn new_ctrl_alt(key: VirtualKeyCode, inner: T) -> WithFocusHotkey<T> {
Self::new(0b010001, key, inner)
}
pub fn new_ctrl_alt_shift(key: VirtualKeyCode, inner: T) -> WithFocusHotkey<T> {
Self::new(0b010101, key, inner)
}
pub fn new(modifiers: u8, key: VirtualKeyCode, mut inner: T) -> WithFocusHotkey<T> {
inner.config_mut().keyboard_events_watch = true;
WithFocusHotkey {
inner,
modifiers,
key,
}
}
}
impl<T: GuiElemTrait> GuiElemTrait for WithFocusHotkey<T>
where
T: GuiElemTrait,
{
fn config(&self) -> &GuiElemCfg {
self.inner.config()
}
fn config_mut(&mut self) -> &mut GuiElemCfg {
self.inner.config_mut()
}
fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElemTrait> + '_> {
self.inner.children()
}
fn any(&self) -> &dyn std::any::Any {
self
}
fn any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn elem(&self) -> &dyn GuiElemTrait {
self
}
fn elem_mut(&mut self) -> &mut dyn GuiElemTrait {
self
}
fn draw(&mut self, info: &mut DrawInfo, g: &mut Graphics2D) {
self.inner.draw(info, g)
}
fn mouse_down(&mut self, button: MouseButton) -> Vec<GuiAction> {
self.inner.mouse_down(button)
}
fn mouse_up(&mut self, button: MouseButton) -> Vec<GuiAction> {
self.inner.mouse_up(button)
}
fn mouse_pressed(&mut self, button: MouseButton) -> Vec<GuiAction> {
self.inner.mouse_pressed(button)
}
fn mouse_wheel(&mut self, diff: f32) -> Vec<GuiAction> {
self.inner.mouse_wheel(diff)
}
fn char_watch(
&mut self,
modifiers: speedy2d::window::ModifiersState,
key: char,
) -> Vec<GuiAction> {
self.inner.char_watch(modifiers, key)
}
fn char_focus(
&mut self,
modifiers: speedy2d::window::ModifiersState,
key: char,
) -> Vec<GuiAction> {
self.inner.char_focus(modifiers, key)
}
fn key_watch(
&mut self,
modifiers: speedy2d::window::ModifiersState, modifiers: speedy2d::window::ModifiersState,
down: bool, down: bool,
key: Option<speedy2d::window::VirtualKeyCode>, key: Option<speedy2d::window::VirtualKeyCode>,
scan: speedy2d::window::KeyScancode, ) -> bool {
) -> Vec<GuiAction> { if self.modifiers == u8::MAX {
let hotkey = down == false return false;
}
down == false
&& key.is_some_and(|v| v == self.key) && key.is_some_and(|v| v == self.key)
&& (self.modifiers & 0b10 == 1 || (self.modifiers & 0b01 == 1) == modifiers.ctrl()) && (self.modifiers & 0b10 == 1 || (self.modifiers & 0b01 == 1) == modifiers.ctrl())
&& (self.modifiers & 0b1000 == 1 && (self.modifiers & 0b1000 == 1 || (self.modifiers & 0b0100 == 1) == modifiers.shift())
|| (self.modifiers & 0b0100 == 1) == modifiers.shift())
&& (self.modifiers & 0b100000 == 1 && (self.modifiers & 0b100000 == 1
|| (self.modifiers & 0b010000 == 1) == modifiers.alt()) || (self.modifiers & 0b010000 == 1) == modifiers.alt())
&& (self.modifiers & 0b10000000 == 1 && (self.modifiers & 0b10000000 == 1
|| (self.modifiers & 0b01000000 == 1) == modifiers.logo()); || (self.modifiers & 0b01000000 == 1) == modifiers.logo())
let mut o = self.inner.key_watch(modifiers, down, key, scan);
if hotkey {
self.config_mut().request_keyboard_focus = true;
o.push(GuiAction::ResetKeyboardFocus);
}
o
} }
fn key_focus( /// unlike noshift, this ignores the shift modifier
&mut self, pub fn new_key(key: VirtualKeyCode) -> Self {
modifiers: speedy2d::window::ModifiersState, Self::new(0b1000, key)
down: bool,
key: Option<speedy2d::window::VirtualKeyCode>,
scan: speedy2d::window::KeyScancode,
) -> Vec<GuiAction> {
self.inner.key_focus(modifiers, down, key, scan)
} }
fn dragged(&mut self, dragged: crate::gui::Dragging) -> Vec<GuiAction> { /// requires the key to be pressed without any modifiers
self.inner.dragged(dragged) pub fn new_noshift(key: VirtualKeyCode) -> Self {
Self::new(0, key)
} }
fn updated_library(&mut self) { pub fn new_shift(key: VirtualKeyCode) -> Self {
self.inner.updated_library() Self::new(0b0100, key)
} }
fn updated_queue(&mut self) { pub fn new_ctrl(key: VirtualKeyCode) -> Self {
self.inner.updated_queue() Self::new(0b01, key)
}
pub fn new_ctrl_shift(key: VirtualKeyCode) -> Self {
Self::new(0b0101, key)
}
pub fn new_alt(key: VirtualKeyCode) -> Self {
Self::new(0b010000, key)
}
pub fn new_alt_shift(key: VirtualKeyCode) -> Self {
Self::new(0b010100, key)
}
pub fn new_ctrl_alt(key: VirtualKeyCode) -> Self {
Self::new(0b010001, key)
}
pub fn new_ctrl_alt_shift(key: VirtualKeyCode) -> Self {
Self::new(0b010101, key)
}
pub fn new(modifiers: u8, key: VirtualKeyCode) -> Self {
Hotkey { modifiers, key }
} }
} }

View File

@ -22,9 +22,11 @@ use musicdb_lib::{
#[cfg(feature = "speedy2d")] #[cfg(feature = "speedy2d")]
mod gui; mod gui;
#[cfg(feature = "speedy2d")] #[cfg(feature = "speedy2d")]
mod gui_anim;
#[cfg(feature = "speedy2d")]
mod gui_base; mod gui_base;
#[cfg(feature = "speedy2d")] #[cfg(feature = "speedy2d")]
mod gui_edit; mod gui_idle_display;
#[cfg(feature = "speedy2d")] #[cfg(feature = "speedy2d")]
mod gui_library; mod gui_library;
#[cfg(feature = "speedy2d")] #[cfg(feature = "speedy2d")]
@ -38,6 +40,8 @@ mod gui_screen;
#[cfg(feature = "speedy2d")] #[cfg(feature = "speedy2d")]
mod gui_settings; mod gui_settings;
#[cfg(feature = "speedy2d")] #[cfg(feature = "speedy2d")]
mod gui_statusbar;
#[cfg(feature = "speedy2d")]
mod gui_text; mod gui_text;
#[cfg(feature = "speedy2d")] #[cfg(feature = "speedy2d")]
mod gui_wrappers; mod gui_wrappers;

View File

@ -39,6 +39,7 @@ impl TextBuilder {
pub fn gen(&self, db: &Database, current_song: Option<&Song>) -> Vec<Vec<(Content, f32, f32)>> { pub fn gen(&self, db: &Database, current_song: Option<&Song>) -> Vec<Vec<(Content, f32, f32)>> {
let mut out = vec![]; let mut out = vec![];
let mut line = vec![]; let mut line = vec![];
let mut c = Color::WHITE;
self.gen_to( self.gen_to(
db, db,
current_song, current_song,
@ -46,7 +47,7 @@ impl TextBuilder {
&mut line, &mut line,
&mut 1.0, &mut 1.0,
&mut 1.0, &mut 1.0,
&mut Color::WHITE, &mut c,
); );
if !line.is_empty() { if !line.is_empty() {
out.push(line) out.push(line)

View File

@ -1,5 +1,5 @@
use std::{ use std::{
collections::HashMap, collections::{HashMap, HashSet},
fs, fs,
io::Write, io::Write,
path::{Path, PathBuf}, path::{Path, PathBuf},
@ -21,21 +21,44 @@ fn main() {
let lib_dir = if let Some(arg) = args.next() { let lib_dir = if let Some(arg) = args.next() {
arg arg
} else { } else {
eprintln!("usage: musicdb-filldb <library root> [--skip-duration]"); eprintln!("usage: musicdb-filldb <library root> [--help] [--skip-duration] [--custom-files <path>] [... (see --help)]");
std::process::exit(1); std::process::exit(1);
}; };
let mut unknown_arg = false; let mut bad_arg = false;
let mut skip_duration = false; let mut skip_duration = false;
for arg in args { let mut custom_files = None;
match arg.as_str() { let mut artist_txt = false;
"--skip-duration" => skip_duration = true, let mut artist_img = false;
_ => { loop {
unknown_arg = true; match args.next() {
eprintln!("Unknown argument: {arg}"); None => break,
} Some(arg) => match arg.as_str() {
"--help" => {
eprintln!("--skip-duration: Don't try to figure out the songs duration from file contents. This means mp3 files with the Duration field unset will have a duration of 0.");
eprintln!("--custom-files <path>: server will use <path> as its custom-files directory.");
eprintln!("--cf-artist-txt: For each artist, check for an <artist>.txt file. If it exists, add each line as a tag to that artist.");
eprintln!("--cf-artist-img: For each artist, check for an <artist>.{{jpg,png,...}} file. If it exists, add ImageExt=<extension> tag to the artist, so the image can be loaded by clients later.");
return;
}
"--skip-duration" => skip_duration = true,
"--custom-files" => {
if let Some(path) = args.next() {
custom_files = Some(PathBuf::from(path));
} else {
bad_arg = true;
eprintln!("--custom-files <path> :: missing <path>!");
}
}
"--cf-artist-txt" => artist_txt = true,
"--cf-artist-img" => artist_img = true,
arg => {
bad_arg = true;
eprintln!("Unknown argument: {arg}");
}
},
} }
} }
if unknown_arg { if bad_arg {
return; return;
} }
eprintln!("Library: {lib_dir}. press enter to start. result will be saved in 'dbfile'."); eprintln!("Library: {lib_dir}. press enter to start. result will be saved in 'dbfile'.");
@ -254,6 +277,61 @@ fn main() {
eprintln!("Added the <unknown> artist as a fallback!"); eprintln!("Added the <unknown> artist as a fallback!");
} }
} }
if let Some(custom_files) = custom_files {
if artist_txt {
eprintln!("[info] Searching for <artist>.txt files in custom-files dir...");
let l = database.artists().len();
let mut c = 0;
for (i, artist) in database.artists_mut().values_mut().enumerate() {
if let Ok(info) =
fs::read_to_string(custom_files.join(format!("{}.txt", artist.name)))
{
c += 1;
for line in info.lines() {
artist.general.tags.push(line.to_owned());
}
}
eprint!(" {}/{l} ({c})\r", i + 1);
}
eprintln!();
}
if artist_img {
eprintln!("[info] Searching for <artist>.{{png,jpg,...}} files in custom-files dir...");
match fs::read_dir(&custom_files) {
Err(e) => {
eprintln!("Can't read custom-files dir {custom_files:?}: {e}");
}
Ok(ls) => {
let mut files = HashMap::new();
for entry in ls {
if let Ok(entry) = entry {
let p = entry.path();
if let Some(base) = p.file_stem().and_then(|v| v.to_str()) {
if let Some(ext) = entry
.path()
.extension()
.and_then(|v| v.to_str())
.filter(|v| {
matches!(v.to_lowercase().as_str(), "png" | "jpg" | "jpeg")
})
{
if let Some(old) = files.insert(base.to_owned(), ext.to_owned())
{
eprintln!("[warn] Not using file {base}.{old}, because {base}.{ext} was found.");
}
}
}
}
}
for artist in database.artists_mut().values_mut() {
if let Some(ext) = files.get(&artist.name) {
artist.general.tags.push(format!("ImageExt={ext}"));
}
}
}
}
}
}
eprintln!("saving dbfile..."); eprintln!("saving dbfile...");
database.save_database(None).unwrap(); database.save_database(None).unwrap();
eprintln!("done!"); eprintln!("done!");

View File

@ -156,7 +156,7 @@ impl Song {
match dlcon match dlcon
.lock() .lock()
.unwrap() .unwrap()
.song_file(id, true) .song_file(id)
.expect("problem with downloader connection...") .expect("problem with downloader connection...")
{ {
Ok(data) => Some(data), Ok(data) => Some(data),

View File

@ -35,18 +35,32 @@ impl<T: Write + Read> Client<T> {
Ok(Err(response)) Ok(Err(response))
} }
} }
pub fn song_file( pub fn song_file(&mut self, id: SongId) -> Result<Result<Vec<u8>, String>, std::io::Error> {
&mut self,
id: SongId,
blocking: bool,
) -> Result<Result<Vec<u8>, String>, std::io::Error> {
writeln!( writeln!(
self.0.get_mut(), self.0.get_mut(),
"{}", "{}",
con_get_encode_string(&format!( con_get_encode_string(&format!("song-file\n{id}",))
"song-file{}\n{id}", )?;
if blocking { "-blocking" } else { "" } let mut response = String::new();
)) self.0.read_line(&mut response)?;
let response = con_get_decode_line(&response);
if response.starts_with("len: ") {
if let Ok(len) = response[4..].trim().parse() {
let mut bytes = vec![0; len];
self.0.read_exact(&mut bytes)?;
Ok(Ok(bytes))
} else {
Ok(Err(response))
}
} else {
Ok(Err(response))
}
}
pub fn custom_file(&mut self, path: &str) -> Result<Result<Vec<u8>, String>, std::io::Error> {
writeln!(
self.0.get_mut(),
"{}",
con_get_encode_string(&format!("custom-file\n{path}",))
)?; )?;
let mut response = String::new(); let mut response = String::new();
self.0.read_line(&mut response)?; self.0.read_line(&mut response)?;
@ -128,7 +142,7 @@ pub fn handle_one_connection_as_get(
Some(None) => Some(db.lib_directory.clone()), Some(None) => Some(db.lib_directory.clone()),
Some(Some(p)) => Some(p.clone()), Some(Some(p)) => Some(p.clone()),
}; };
// check for malicious paths // check for malicious paths [TODO: Improve]
if Path::new(path).is_absolute() { if Path::new(path).is_absolute() {
parent = None; parent = None;
} }