diff --git a/musicdb-client/src/gui.rs b/musicdb-client/src/gui.rs index d275768..c4d940c 100755 --- a/musicdb-client/src/gui.rs +++ b/musicdb-client/src/gui.rs @@ -204,27 +204,36 @@ pub trait GuiElemTrait { } #[derive(Debug, Clone)] +/// The config for any gui element. pub struct GuiElemCfg { pub enabled: bool, /// if true, indicates that something (text size, screen size, ...) has changed /// and you should probably relayout and redraw from scratch. pub redraw: bool, + /// Position relative to the parent where this element should be drawn. + /// ((0, 0), (1, 1)) is the default and fills all available space. + /// ((0, 0.5), (0.5, 1)) fills the bottom left quarter. pub pos: Rectangle, /// the pixel position after the last call to draw(). /// in draw, use info.pos instead, as pixel_pos is only updated *after* draw(). /// this can act like a "previous pos" field within draw. pub pixel_pos: Rectangle, + /// which mouse buttons were pressed down while the mouse was on this element and haven't been released since? (Left/Middle/Right) pub mouse_down: (bool, bool, bool), + /// Set this to true to receive mouse click events when the mouse is within this element's bounds pub mouse_events: bool, + /// Set this to true to receive scroll events when the mouse is within this element's bounds pub scroll_events: bool, /// allows elements to watch all keyboard events, regardless of keyboard focus. pub keyboard_events_watch: bool, /// indicates that this element can have the keyboard focus pub keyboard_events_focus: bool, /// index of the child that has keyboard focus. if usize::MAX, `self` has focus. - /// will automatically be changed when Tab is pressed. [TODO] + /// will automatically be changed when Tab is pressed (Tab skips elements with keyboard_events_focus == false) pub keyboard_focus_index: usize, + /// if this is true and ResetKeyboardFocus is returned, this element may get the keyboard focus (guaranteed if no other element has this set to true) pub request_keyboard_focus: bool, + /// if this is true, things can be dragged into this element via drag-n-drop pub drag_target: bool, } impl GuiElemCfg { @@ -281,9 +290,10 @@ pub enum GuiAction { OpenMain, SetIdle(bool), OpenSettings(bool), + /// Build the GuiAction(s) later, when we have access to the Database (can turn an AlbumId into a QueueContent::Folder, etc) Build(Box Vec>), SendToServer(Command), - /// unfocuses all gui elements, then assigns keyboard focus to one with config().request_keyboard_focus == true. + /// unfocuses all gui elements, then assigns keyboard focus to one with config().request_keyboard_focus == true if there is one. ResetKeyboardFocus, SetDragging( Option<( @@ -292,6 +302,7 @@ pub enum GuiAction { )>, ), SetLineHeight(f32), + /// Run a custom closure with mutable access to the Gui struct Do(Box), Exit, } @@ -301,6 +312,9 @@ pub enum Dragging { Song(SongId), Queue(Queue), } + +/// GuiElems have access to this within draw. +/// Except for `actions`, they should not change any of these values - GuiElem::draw will handle everything automatically. pub struct DrawInfo<'a> { pub actions: Vec, pub pos: Rectangle, @@ -316,6 +330,7 @@ pub struct DrawInfo<'a> { pub line_height: f32, } +/// Generic wrapper over anything that implements GuiElemTrait pub struct GuiElem { pub inner: Box, } diff --git a/musicdb-client/src/gui_base.rs b/musicdb-client/src/gui_base.rs index 7933c53..0511729 100755 --- a/musicdb-client/src/gui_base.rs +++ b/musicdb-client/src/gui_base.rs @@ -7,6 +7,13 @@ use crate::{ gui_text::Label, }; +/* + +Some basic structs to use everywhere. +Mostly containers for other GuiElems. + +*/ + /// A simple container for zero, one, or multiple child GuiElems. Can optionally fill the background with a color. #[derive(Clone)] pub struct Panel { diff --git a/musicdb-client/src/gui_library.rs b/musicdb-client/src/gui_library.rs index 5db9ea2..0595d0e 100755 --- a/musicdb-client/src/gui_library.rs +++ b/musicdb-client/src/gui_library.rs @@ -14,6 +14,13 @@ use crate::{ gui_wrappers::WithFocusHotkey, }; +/* + +This is responsible for showing the library, +with Regex search and drag-n-drop. + +*/ + #[derive(Clone)] pub struct LibraryBrowser { config: GuiElemCfg, diff --git a/musicdb-client/src/gui_playback.rs b/musicdb-client/src/gui_playback.rs index 695cc69..2729937 100755 --- a/musicdb-client/src/gui_playback.rs +++ b/musicdb-client/src/gui_playback.rs @@ -9,6 +9,13 @@ use crate::{ gui_text::Label, }; +/* + +Components for the StatusBar. +This file could probably have a better name. + +*/ + #[derive(Clone)] pub struct CurrentSong { config: GuiElemCfg, diff --git a/musicdb-client/src/gui_queue.rs b/musicdb-client/src/gui_queue.rs index 58fb76c..95b8be0 100755 --- a/musicdb-client/src/gui_queue.rs +++ b/musicdb-client/src/gui_queue.rs @@ -20,6 +20,15 @@ use crate::{ gui_text::Label, }; +/* + + +This is responsible for showing the current queue, +with drag-n-drop only if the mouse leaves the element before it is released, +because simple clicks have to be GoTo events. + +*/ + #[derive(Clone)] pub struct QueueViewer { config: GuiElemCfg, diff --git a/musicdb-client/src/gui_screen.rs b/musicdb-client/src/gui_screen.rs index 2a42e64..22fa15f 100755 --- a/musicdb-client/src/gui_screen.rs +++ b/musicdb-client/src/gui_screen.rs @@ -12,7 +12,15 @@ use crate::{ gui_text::Label, }; -/// calculates f(p) (f(x) = 3x^2 - 2x^3)): +/* + +The root gui element. +Contains the Library, Queue, StatusBar, and sometimes Settings elements. +Resizes these elements to show/hide the settings menu and to smoothly switch to/from idle mode. + +*/ + +/// calculates f(p), where f(x) = 3x^2 - 2x^3, because /// f(0) = 0 /// f(0.5) = 0.5 /// f(1) = 1 @@ -36,9 +44,6 @@ pub struct GuiScreen { pub prev_mouse_pos: Vec2, } impl GuiScreen { - fn i_statusbar() -> usize { - 0 - } pub fn new( config: GuiElemCfg, line_height: f32, diff --git a/musicdb-client/src/gui_text.rs b/musicdb-client/src/gui_text.rs index 2cd6369..43fa92a 100755 --- a/musicdb-client/src/gui_text.rs +++ b/musicdb-client/src/gui_text.rs @@ -10,6 +10,13 @@ use speedy2d::{ use crate::gui::{GuiAction, GuiElem, GuiElemCfg, GuiElemTrait}; +/* + +Some basic structs to use everywhere, +except they are all text-related. + +*/ + #[derive(Clone)] pub struct Label { config: GuiElemCfg, diff --git a/musicdb-lib/src/data/database.rs b/musicdb-lib/src/data/database.rs index 3b63c76..384640b 100755 --- a/musicdb-lib/src/data/database.rs +++ b/musicdb-lib/src/data/database.rs @@ -18,7 +18,9 @@ use super::{ }; pub struct Database { + /// the path to the file used to save/load the data db_file: PathBuf, + /// the path to the directory containing the actual music and cover image files pub lib_directory: PathBuf, artists: HashMap, albums: HashMap, @@ -26,13 +28,18 @@ pub struct Database { covers: HashMap, // TODO! make sure this works out for the server AND clients // cover_cache: HashMap>, + // These will be used for autosave once that gets implemented db_data_file_change_first: Option, db_data_file_change_last: Option, pub queue: Queue, + /// if the database receives an update, it will inform all of its clients so they can stay in sync. + /// this is a list containing all the clients. pub update_endpoints: Vec, + /// true if a song is/should be playing pub playing: bool, pub command_sender: Option>, } +// for custom server implementations, this enum should allow you to deal with updates from any context (writers such as tcp streams, sync/async mpsc senders, or via closure as a fallback) pub enum UpdateEndpoint { Bytes(Box), CmdChannel(mpsc::Sender>), @@ -41,6 +48,7 @@ pub enum UpdateEndpoint { } impl Database { + /// TODO! fn panic(&self, msg: &str) -> ! { // custom panic handler // make a backup @@ -50,6 +58,7 @@ impl Database { pub fn get_path(&self, location: &DatabaseLocation) -> PathBuf { self.lib_directory.join(&location.rel_path) } + // NOTE: just use `songs` directly? not sure yet... pub fn get_song(&self, song: &SongId) -> Option<&Song> { self.songs.get(song) } @@ -72,6 +81,7 @@ impl Database { } id } + /// used internally pub fn add_song_new_nomagic(&mut self, mut song: Song) -> SongId { for key in 0.. { if !self.songs.contains_key(&key) { @@ -89,6 +99,7 @@ impl Database { let id = self.add_artist_new_nomagic(artist); id } + /// used internally fn add_artist_new_nomagic(&mut self, mut artist: Artist) -> ArtistId { for key in 0.. { if !self.artists.contains_key(&key) { @@ -110,6 +121,7 @@ impl Database { } id } + /// used internally fn add_album_new_nomagic(&mut self, mut album: Album) -> AlbumId { for key in 0.. { if !self.albums.contains_key(&key) { diff --git a/musicdb-lib/src/data/mod.rs b/musicdb-lib/src/data/mod.rs index 053af29..16451c8 100755 --- a/musicdb-lib/src/data/mod.rs +++ b/musicdb-lib/src/data/mod.rs @@ -17,11 +17,13 @@ pub type ArtistId = u64; pub type CoverId = u64; #[derive(Clone, Default, Debug)] +/// general data for songs, albums and artists pub struct GeneralData { pub tags: Vec, } #[derive(Clone, Debug)] +/// the location of a file relative to the lib directory, often Artist/Album/Song.ext or similar pub struct DatabaseLocation { pub rel_path: PathBuf, } diff --git a/musicdb-lib/src/server/mod.rs b/musicdb-lib/src/server/mod.rs index 1afa904..c940098 100755 --- a/musicdb-lib/src/server/mod.rs +++ b/musicdb-lib/src/server/mod.rs @@ -81,6 +81,8 @@ pub fn run_server( sender_sender: Option>>, ) { let mut player = Player::new().unwrap(); + // commands sent to this will be handeled later in this function in an infinite loop. + // these commands are sent to the database asap. let (command_sender, command_receiver) = mpsc::channel(); if let Some(s) = sender_sender { s.blocking_send(command_sender.clone()).unwrap(); @@ -91,6 +93,7 @@ pub fn run_server( Ok(v) => { let command_sender = command_sender.clone(); let db = Arc::clone(&database); + // each connection gets its own thread, but they will be idle most of the time (waiting for data on the tcp stream) thread::spawn(move || loop { if let Ok((mut connection, con_addr)) = v.accept() { eprintln!("[info] TCP connection accepted from {con_addr}."); @@ -100,10 +103,15 @@ pub fn run_server( // sync database let mut db = db.lock().unwrap(); db.init_connection(&mut connection)?; + // keep the client in sync: + // the db will send all updates to the client once it is added to update_endpoints db.update_endpoints.push(UpdateEndpoint::Bytes(Box::new( + // try_clone is used here to split a TcpStream into Writer and Reader connection.try_clone().unwrap(), ))); + // drop the mutex lock drop(db); + // read updates from the tcp stream and send them to the database, exit on EOF or Err loop { if let Ok(command) = Command::from_bytes(&mut connection) { command_sender.send(command).unwrap(); @@ -121,6 +129,8 @@ pub fn run_server( } } } + // for now, update the player 10 times a second so it can detect when a song has finished and start a new one. + // TODO: player should send a NextSong update to the mpsc::Sender to wake up this thread let dur = Duration::from_secs_f32(0.1); loop { player.update(&mut database.lock().unwrap()); diff --git a/musicdb-server/src/main.rs b/musicdb-server/src/main.rs index de3dc0f..6aa0331 100755 --- a/musicdb-server/src/main.rs +++ b/musicdb-server/src/main.rs @@ -25,6 +25,7 @@ use musicdb_lib::data::database::Database; #[tokio::main] async fn main() { + // parse args let mut args = std::env::args().skip(1); let mut tcp_addr = None; let mut web_addr = None; @@ -148,20 +149,7 @@ this help was shown because no arguments were provided." ); exit(1); }; - // database.add_song_new(Song::new( - // "Amaranthe/Manifest/02 Make It Better.mp3".into(), - // "Make It Better".to_owned(), - // None, - // None, - // vec![], - // None, - // )); - // let mut player = Player::new(); - // eprintln!("[info] database.songs: {:?}", database.songs()); - // database.save_database(Some("/tmp/dbfile".into())).unwrap(); - // eprintln!("{}", database.get_song(&0).unwrap()); - // database.queue.add_to_end(QueueContent::Song(1).into()); - // player.update_and_restart_playing_song(&database); + // database can be shared by multiple threads using Arc> let database = Arc::new(Mutex::new(database)); if tcp_addr.is_some() || web_addr.is_some() { if let Some(addr) = web_addr { @@ -177,8 +165,4 @@ this help was shown because no arguments were provided." } else { eprintln!("nothing to do, not starting the server."); } - // std::io::stdin().read_line(&mut String::new()).unwrap(); - // dbg!(Update::from_bytes(&mut BufReader::new( - // TcpStream::connect("127.0.0.1:26314".parse::().unwrap()).unwrap() - // ))); }