added some comments

This commit is contained in:
Mark 2023-08-14 00:58:31 +02:00
parent 5add9c477e
commit 0ae0126f04
11 changed files with 89 additions and 24 deletions

View File

@ -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<dyn FnOnce(&mut Database) -> Vec<Self>>),
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<dyn FnMut(&mut Gui)>),
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<GuiAction>,
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<dyn GuiElemTrait>,
}

View File

@ -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 {

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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<ArtistId, Artist>,
albums: HashMap<AlbumId, Album>,
@ -26,13 +28,18 @@ pub struct Database {
covers: HashMap<CoverId, DatabaseLocation>,
// TODO! make sure this works out for the server AND clients
// cover_cache: HashMap<CoverId, Vec<u8>>,
// These will be used for autosave once that gets implemented
db_data_file_change_first: Option<Instant>,
db_data_file_change_last: Option<Instant>,
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<UpdateEndpoint>,
/// true if a song is/should be playing
pub playing: bool,
pub command_sender: Option<mpsc::Sender<Command>>,
}
// 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<dyn Write + Sync + Send>),
CmdChannel(mpsc::Sender<Arc<Command>>),
@ -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) {

View File

@ -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<String>,
}
#[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,
}

View File

@ -81,6 +81,8 @@ pub fn run_server(
sender_sender: Option<tokio::sync::mpsc::Sender<mpsc::Sender<Command>>>,
) {
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());

View File

@ -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<Mutex<_>>
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::<SocketAddr>().unwrap()).unwrap()
// )));
}