From 9fbe67012eac38b1fac42ff26cbabf8c18eb5cc1 Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 24 Aug 2023 16:15:01 +0200 Subject: [PATCH] small improvements idk i forgot i had a git repo for this project --- musicdb-client/Cargo.toml | 1 + musicdb-client/src/gui.rs | 116 +++- musicdb-client/src/gui_base.rs | 49 ++ musicdb-client/src/gui_library.rs | 22 + musicdb-client/src/gui_playback.rs | 291 ++++++--- musicdb-client/src/gui_queue.rs | 923 ++++++++++++++++++++++------- musicdb-client/src/gui_screen.rs | 35 +- musicdb-client/src/gui_settings.rs | 2 +- musicdb-client/src/main.rs | 80 ++- musicdb-lib/Cargo.toml | 1 + musicdb-lib/src/data/database.rs | 133 ++++- musicdb-lib/src/data/queue.rs | 353 ++++++++++- musicdb-lib/src/load/mod.rs | 28 +- musicdb-lib/src/player/mod.rs | 60 +- musicdb-lib/src/server/mod.rs | 122 ++-- musicdb-server/src/main.rs | 4 +- musicdb-server/src/web.rs | 129 +++- 17 files changed, 1894 insertions(+), 455 deletions(-) diff --git a/musicdb-client/Cargo.toml b/musicdb-client/Cargo.toml index 7d9d4a4..ff03499 100755 --- a/musicdb-client/Cargo.toml +++ b/musicdb-client/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" musicdb-lib = { version = "0.1.0", path = "../musicdb-lib" } regex = "1.9.3" speedy2d = { version = "1.12.0", optional = true } +toml = "0.7.6" [features] default = ["speedy2d"] diff --git a/musicdb-client/src/gui.rs b/musicdb-client/src/gui.rs index c4d940c..a2302ec 100755 --- a/musicdb-client/src/gui.rs +++ b/musicdb-client/src/gui.rs @@ -1,6 +1,7 @@ use std::{ any::Any, eprintln, + io::{Read, Write}, net::TcpStream, sync::{Arc, Mutex}, time::Instant, @@ -10,7 +11,7 @@ use std::{ use musicdb_lib::{ data::{database::Database, queue::Queue, AlbumId, ArtistId, SongId}, load::ToFromBytes, - server::Command, + server::{get, Command}, }; use speedy2d::{ color::Color, @@ -33,11 +34,74 @@ pub enum GuiEvent { Exit, } -pub fn main( +pub fn main( database: Arc>, connection: TcpStream, + get_con: get::Client, event_sender_arc: Arc>>>, ) { + let mut config_file = super::get_config_file_path(); + config_file.push("config_gui.toml"); + let mut font = None; + let mut line_height = 32.0; + let mut scroll_pixels_multiplier = 1.0; + let mut scroll_lines_multiplier = 3.0; + let mut scroll_pages_multiplier = 0.75; + match std::fs::read_to_string(&config_file) { + Ok(cfg) => { + if let Ok(table) = cfg.parse::() { + if let Some(path) = table["font"].as_str() { + if let Ok(bytes) = std::fs::read(path) { + if let Ok(f) = Font::new(&bytes) { + font = Some(f); + } else { + eprintln!("[toml] couldn't load font") + } + } else { + eprintln!("[toml] couldn't read font file") + } + } + if let Some(v) = table.get("line_height").and_then(|v| v.as_float()) { + line_height = v as _; + } + if let Some(v) = table + .get("scroll_pixels_multiplier") + .and_then(|v| v.as_float()) + { + scroll_pixels_multiplier = v; + } + if let Some(v) = table + .get("scroll_lines_multiplier") + .and_then(|v| v.as_float()) + { + scroll_lines_multiplier = v; + } + if let Some(v) = table + .get("scroll_pages_multiplier") + .and_then(|v| v.as_float()) + { + scroll_pages_multiplier = v; + } + } else { + eprintln!("Couldn't parse config file {config_file:?} as toml!"); + } + } + Err(e) => { + eprintln!("[exit] no config file found at {config_file:?}: {e}"); + if let Some(p) = config_file.parent() { + _ = std::fs::create_dir_all(p); + } + _ = std::fs::write(&config_file, "font = \"\""); + std::process::exit(25); + } + } + let font = if let Some(v) = font { + v + } else { + eprintln!("[toml] required: font = "); + std::process::exit(30); + }; + let window = speedy2d::Window::::new_with_user_events( "MusicDB Client", WindowCreationOptions::new_fullscreen_borderless(), @@ -45,7 +109,18 @@ pub fn main( .expect("couldn't open window"); *event_sender_arc.lock().unwrap() = Some(window.create_user_event_sender()); let sender = window.create_user_event_sender(); - window.run_loop(Gui::new(database, connection, event_sender_arc, sender)); + window.run_loop(Gui::new( + font, + database, + connection, + get_con, + event_sender_arc, + sender, + line_height, + scroll_pixels_multiplier, + scroll_lines_multiplier, + scroll_pages_multiplier, + )); } pub struct Gui { @@ -69,11 +144,17 @@ pub struct Gui { pub scroll_pages_multiplier: f64, } impl Gui { - fn new( + fn new( + font: Font, database: Arc>, connection: TcpStream, + get_con: get::Client, event_sender_arc: Arc>>>, event_sender: UserEventSender, + line_height: f32, + scroll_pixels_multiplier: f64, + scroll_lines_multiplier: f64, + scroll_pages_multiplier: f64, ) -> Self { database.lock().unwrap().update_endpoints.push( musicdb_lib::data::database::UpdateEndpoint::Custom(Box::new(move |cmd| match cmd { @@ -87,7 +168,8 @@ impl Gui { | Command::QueueAdd(..) | Command::QueueInsert(..) | Command::QueueRemove(..) - | Command::QueueGoto(..) => { + | Command::QueueGoto(..) + | Command::QueueSetShuffle(..) => { if let Some(s) = &*event_sender_arc.lock().unwrap() { _ = s.send_event(GuiEvent::UpdatedQueue); } @@ -96,6 +178,7 @@ impl Gui { | Command::AddSong(_) | Command::AddAlbum(_) | Command::AddArtist(_) + | Command::AddCover(_) | Command::ModifySong(_) | Command::ModifyAlbum(_) | Command::ModifyArtist(_) => { @@ -105,10 +188,6 @@ impl Gui { } })), ); - let line_height = 32.0; - let scroll_pixels_multiplier = 1.0; - let scroll_lines_multiplier = 3.0; - let scroll_pages_multiplier = 0.75; Gui { event_sender, database, @@ -117,6 +196,7 @@ impl Gui { VirtualKeyCode::Escape, GuiScreen::new( GuiElemCfg::default(), + get_con, line_height, scroll_pixels_multiplier, scroll_lines_multiplier, @@ -125,10 +205,7 @@ impl Gui { )), size: UVec2::ZERO, mouse_pos: Vec2::ZERO, - font: Font::new(include_bytes!( - "/usr/share/fonts/mozilla-fira/FiraSans-Regular.otf" - )) - .unwrap(), + font, // font: Font::new(include_bytes!("/usr/share/fonts/TTF/FiraSans-Regular.ttf")).unwrap(), last_draw: Instant::now(), modifiers: ModifiersState::default(), @@ -328,6 +405,10 @@ pub struct DrawInfo<'a> { pub child_has_keyboard_focus: bool, /// the height of one line of text (in pixels) pub line_height: f32, + pub dragging: Option<( + Dragging, + Option>, + )>, } /// Generic wrapper over anything that implements GuiElemTrait @@ -679,9 +760,11 @@ impl WindowHandler for Gui { has_keyboard_focus: false, child_has_keyboard_focus: true, line_height: self.line_height, + dragging: self.dragging.take(), }; self.gui.draw(&mut info, graphics); let actions = std::mem::replace(&mut info.actions, Vec::with_capacity(0)); + self.dragging = info.dragging.take(); if let Some((d, f)) = &mut self.dragging { if let Some(f) = f { f(&mut info, graphics); @@ -753,12 +836,15 @@ impl WindowHandler for Gui { distance: speedy2d::window::MouseScrollDistance, ) { let dist = match distance { - MouseScrollDistance::Pixels { y, .. } => (self.scroll_pixels_multiplier * y) as f32, + MouseScrollDistance::Pixels { y, .. } => { + (self.scroll_pixels_multiplier * y * self.scroll_lines_multiplier) as f32 + } MouseScrollDistance::Lines { y, .. } => { (self.scroll_lines_multiplier * y) as f32 * self.line_height } MouseScrollDistance::Pages { y, .. } => { - (self.scroll_pages_multiplier * y) as f32 * self.last_height + (self.scroll_pages_multiplier * y * self.scroll_lines_multiplier) as f32 + * self.last_height } }; if let Some(a) = self.gui.mouse_wheel(dist, self.mouse_pos.clone()) { diff --git a/musicdb-client/src/gui_base.rs b/musicdb-client/src/gui_base.rs index 0511729..dcc18e6 100755 --- a/musicdb-client/src/gui_base.rs +++ b/musicdb-client/src/gui_base.rs @@ -63,6 +63,55 @@ impl GuiElemTrait for Panel { } } +#[derive(Clone)] +pub struct Square { + config: GuiElemCfg, + pub inner: GuiElem, +} +impl Square { + pub fn new(mut config: GuiElemCfg, inner: GuiElem) -> Self { + config.redraw = true; + Self { config, inner } + } +} +impl GuiElemTrait for Square { + fn config(&self) -> &GuiElemCfg { + &self.config + } + fn config_mut(&mut self) -> &mut GuiElemCfg { + &mut self.config + } + fn children(&mut self) -> Box + '_> { + Box::new([&mut self.inner].into_iter()) + } + fn any(&self) -> &dyn std::any::Any { + self + } + fn any_mut(&mut self) -> &mut dyn std::any::Any { + self + } + fn clone_gui(&self) -> Box { + Box::new(self.clone()) + } + fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) { + if info.pos.size() != self.config.pixel_pos.size() { + self.config.redraw = true; + } + if self.config.redraw { + self.config.redraw = false; + if info.pos.width() > info.pos.height() { + let w = 0.5 * info.pos.height() / info.pos.width(); + self.inner.inner.config_mut().pos = + Rectangle::from_tuples((0.5 - w, 0.0), (0.5 + w, 1.0)); + } else { + let h = 0.5 * info.pos.width() / info.pos.height(); + self.inner.inner.config_mut().pos = + Rectangle::from_tuples((0.0, 0.5 - h), (1.0, 0.5 + h)); + } + } + } +} + #[derive(Clone)] pub struct ScrollBox { config: GuiElemCfg, diff --git a/musicdb-client/src/gui_library.rs b/musicdb-client/src/gui_library.rs index 0595d0e..ac18819 100755 --- a/musicdb-client/src/gui_library.rs +++ b/musicdb-client/src/gui_library.rs @@ -189,6 +189,28 @@ impl LibraryBrowser { )), artist_height, )); + for song_id in &artist.singles { + if let Some(song) = db.songs().get(song_id) { + if self.search_song.is_empty() + || self + .search_song_regex + .as_ref() + .is_some_and(|regex| regex.is_match(&song.title)) + { + if let Some(g) = artist_gui.take() { + gui_elements.push(g); + } + gui_elements.push(( + GuiElem::new(ListSong::new( + GuiElemCfg::default(), + *song_id, + song.title.clone(), + )), + song_height, + )); + } + } + } for album_id in &artist.albums { if let Some(album) = db.albums().get(album_id) { if self.search_album.is_empty() diff --git a/musicdb-client/src/gui_playback.rs b/musicdb-client/src/gui_playback.rs index 2729937..12a6aa5 100755 --- a/musicdb-client/src/gui_playback.rs +++ b/musicdb-client/src/gui_playback.rs @@ -1,8 +1,15 @@ -use musicdb_lib::{ - data::{queue::QueueContent, SongId}, - server::Command, +use std::{ + io::{Cursor, Read, Write}, + thread::{self, JoinHandle}, +}; + +use musicdb_lib::{ + data::{queue::QueueContent, CoverId, SongId}, + server::{get, Command}, +}; +use speedy2d::{ + color::Color, dimen::Vec2, image::ImageHandle, shape::Rectangle, window::MouseButton, }; -use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle, window::MouseButton}; use crate::{ gui::{adjust_area, adjust_pos, GuiAction, GuiElem, GuiElemCfg, GuiElemTrait}, @@ -16,38 +23,60 @@ This file could probably have a better name. */ -#[derive(Clone)] -pub struct CurrentSong { +pub struct CurrentSong { config: GuiElemCfg, children: Vec, + get_con: Option>, prev_song: Option, + cover_pos: Rectangle, + cover_id: Option, + cover: Option, + new_cover: Option, Option>)>>, } -impl CurrentSong { - pub fn new(config: GuiElemCfg) -> Self { +impl Clone for CurrentSong { + fn clone(&self) -> Self { + Self { + config: self.config.clone(), + children: self.children.clone(), + get_con: None, + prev_song: None, + cover_pos: self.cover_pos.clone(), + cover_id: None, + cover: None, + new_cover: None, + } + } +} +impl CurrentSong { + pub fn new(config: GuiElemCfg, get_con: get::Client) -> Self { Self { config, children: vec![ GuiElem::new(Label::new( - GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (1.0, 0.5))), + GuiElemCfg::at(Rectangle::from_tuples((0.4, 0.0), (1.0, 0.5))), "".to_owned(), Color::from_int_rgb(180, 180, 210), None, - Vec2::new(0.1, 1.0), + Vec2::new(0.0, 1.0), )), GuiElem::new(Label::new( - GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.5), (0.5, 1.0))), + GuiElemCfg::at(Rectangle::from_tuples((0.4, 0.5), (1.0, 1.0))), "".to_owned(), Color::from_int_rgb(120, 120, 120), None, - Vec2::new(0.3, 0.0), + Vec2::new(0.0, 0.0), )), ], - + get_con: Some(get_con), + cover_pos: Rectangle::new(Vec2::ZERO, Vec2::ZERO), + cover_id: None, prev_song: None, + cover: None, + new_cover: None, } } } -impl GuiElemTrait for CurrentSong { +impl GuiElemTrait for CurrentSong { fn config(&self) -> &GuiElemCfg { &self.config } @@ -67,79 +96,155 @@ impl GuiElemTrait for CurrentSong { Box::new(self.clone()) } fn draw(&mut self, info: &mut crate::gui::DrawInfo, g: &mut speedy2d::Graphics2D) { - let song = if let Some(v) = info.database.queue.get_current() { - if let QueueContent::Song(song) = v.content() { - if Some(*song) == self.prev_song { - // same song as before - return; - } else { - Some(*song) - } - } else if self.prev_song.is_none() { - // no song, nothing in queue - return; - } else { + // 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() { // no song, nothing in queue - return; - } else { None + } else { + self.cover = None; + Some(None) }; - if self.prev_song != song { - self.config.redraw = true; - self.prev_song = song; - } - if self.config.redraw { - self.config.redraw = false; - let (name, subtext) = if let Some(song) = song { - if let Some(song) = info.database.get_song(&song) { - let sub = match ( - song.artist - .as_ref() - .and_then(|id| info.database.artists().get(id)), - song.album - .as_ref() - .and_then(|id| info.database.albums().get(id)), - ) { - (None, None) => String::new(), - (Some(artist), None) => format!("by {}", artist.name), - (None, Some(album)) => { - if let Some(artist) = album - .artist - .as_ref() - .and_then(|id| info.database.artists().get(id)) - { - format!("on {} by {}", album.name, artist.name) - } else { - format!("on {}", album.name) - } - } - (Some(artist), Some(album)) => { - format!("by {} on {}", artist.name, album.name) - } - }; - (song.title.clone(), sub) - } else { - ( - "< song not in db >".to_owned(), - "maybe restart the client to resync the database?".to_owned(), - ) - } + // drawing stuff + if self.config.pixel_pos.size() != info.pos.size() { + let leftright = 0.05; + let topbottom = 0.05; + let mut width = 0.3; + let mut height = 1.0 - topbottom * 2.0; + if width * info.pos.width() < height * info.pos.height() { + height = width * info.pos.width() / info.pos.height(); } else { - (String::new(), String::new()) - }; - *self.children[0] - .try_as_mut::