mirror of
				https://github.com/Dummi26/musicdb.git
				synced 2025-10-29 19:19:20 +01:00 
			
		
		
		
	s
This commit is contained in:
		
							parent
							
								
									168f51a5fc
								
							
						
					
					
						commit
						8c434743f8
					
				| @ -12,6 +12,9 @@ musicdb-lib = { version = "0.1.0", path = "../musicdb-lib" } | |||||||
| regex = "1.9.3" | regex = "1.9.3" | ||||||
| speedy2d = { version = "1.12.0", optional = true } | speedy2d = { version = "1.12.0", optional = true } | ||||||
| toml = "0.7.6" | toml = "0.7.6" | ||||||
|  | mers_lib = { path = "../../mers/mers_lib", optional = true } | ||||||
| 
 | 
 | ||||||
| [features] | [features] | ||||||
| default = ["speedy2d"] | default = ["speedy2d"] | ||||||
|  | merscfg = ["mers_lib"] | ||||||
|  | playback = ["musicdb-lib/playback"] | ||||||
|  | |||||||
| @ -9,7 +9,7 @@ use std::{ | |||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| use musicdb_lib::{ | use musicdb_lib::{ | ||||||
|     data::{database::Database, queue::Queue, AlbumId, ArtistId, CoverId, SongId}, |     data::{database::Database, queue::Queue, song::Song, AlbumId, ArtistId, CoverId, SongId}, | ||||||
|     load::ToFromBytes, |     load::ToFromBytes, | ||||||
|     server::{get, Command}, |     server::{get, Command}, | ||||||
| }; | }; | ||||||
| @ -26,8 +26,11 @@ use speedy2d::{ | |||||||
|     Graphics2D, |     Graphics2D, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | #[cfg(feature = "merscfg")] | ||||||
|  | use crate::merscfg::MersCfg; | ||||||
| use crate::{ | use crate::{ | ||||||
|     gui_base::Panel, |     gui_base::{Panel, ScrollBox}, | ||||||
|  |     gui_edit_song::EditorForSongs, | ||||||
|     gui_notif::{NotifInfo, NotifOverlay}, |     gui_notif::{NotifInfo, NotifOverlay}, | ||||||
|     gui_screen::GuiScreen, |     gui_screen::GuiScreen, | ||||||
|     gui_text::Label, |     gui_text::Label, | ||||||
| @ -76,8 +79,8 @@ pub fn main( | |||||||
|     get_con: get::Client<TcpStream>, |     get_con: get::Client<TcpStream>, | ||||||
|     event_sender_arc: Arc<Mutex<Option<UserEventSender<GuiEvent>>>>, |     event_sender_arc: Arc<Mutex<Option<UserEventSender<GuiEvent>>>>, | ||||||
| ) { | ) { | ||||||
|     let mut config_file = super::get_config_file_path(); |     let config_dir = super::get_config_file_path(); | ||||||
|     config_file.push("config_gui.toml"); |     let config_file = config_dir.join("config_gui.toml"); | ||||||
|     let mut font = None; |     let mut font = None; | ||||||
|     let mut line_height = 32.0; |     let mut line_height = 32.0; | ||||||
|     let mut scroll_pixels_multiplier = 1.0; |     let mut scroll_pixels_multiplier = 1.0; | ||||||
| @ -214,7 +217,7 @@ pub fn main( | |||||||
|         connection, |         connection, | ||||||
|         Arc::new(Mutex::new(get_con)), |         Arc::new(Mutex::new(get_con)), | ||||||
|         event_sender_arc, |         event_sender_arc, | ||||||
|         sender, |         Arc::new(sender), | ||||||
|         line_height, |         line_height, | ||||||
|         scroll_pixels_multiplier, |         scroll_pixels_multiplier, | ||||||
|         scroll_lines_multiplier, |         scroll_lines_multiplier, | ||||||
| @ -254,6 +257,8 @@ pub fn main( | |||||||
|                     crate::gui_library::FilterType::TagWithValueInt("Year".to_owned(), 1990, 2000), |                     crate::gui_library::FilterType::TagWithValueInt("Year".to_owned(), 1990, 2000), | ||||||
|                 ), |                 ), | ||||||
|             ], |             ], | ||||||
|  |             #[cfg(feature = "merscfg")] | ||||||
|  |             merscfg: crate::merscfg::MersCfg::new(config_dir.join("dynamic_config.mers")), | ||||||
|         }, |         }, | ||||||
|     )); |     )); | ||||||
| } | } | ||||||
| @ -266,10 +271,12 @@ pub struct GuiConfig { | |||||||
|     pub filter_presets_song: Vec<(String, crate::gui_library::FilterType)>, |     pub filter_presets_song: Vec<(String, crate::gui_library::FilterType)>, | ||||||
|     pub filter_presets_album: 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 filter_presets_artist: Vec<(String, crate::gui_library::FilterType)>, | ||||||
|  |     #[cfg(feature = "merscfg")] | ||||||
|  |     pub merscfg: crate::merscfg::MersCfg, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub struct Gui { | pub struct Gui { | ||||||
|     pub event_sender: UserEventSender<GuiEvent>, |     pub event_sender: Arc<UserEventSender<GuiEvent>>, | ||||||
|     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>>>, | ||||||
| @ -304,7 +311,7 @@ impl Gui { | |||||||
|         connection: TcpStream, |         connection: TcpStream, | ||||||
|         get_con: Arc<Mutex<get::Client<TcpStream>>>, |         get_con: Arc<Mutex<get::Client<TcpStream>>>, | ||||||
|         event_sender_arc: Arc<Mutex<Option<UserEventSender<GuiEvent>>>>, |         event_sender_arc: Arc<Mutex<Option<UserEventSender<GuiEvent>>>>, | ||||||
|         event_sender: UserEventSender<GuiEvent>, |         event_sender: Arc<UserEventSender<GuiEvent>>, | ||||||
|         line_height: f32, |         line_height: f32, | ||||||
|         scroll_pixels_multiplier: f64, |         scroll_pixels_multiplier: f64, | ||||||
|         scroll_lines_multiplier: f64, |         scroll_lines_multiplier: f64, | ||||||
| @ -313,6 +320,28 @@ impl Gui { | |||||||
|     ) -> Self { |     ) -> Self { | ||||||
|         let (notif_overlay, notif_sender) = NotifOverlay::new(); |         let (notif_overlay, notif_sender) = NotifOverlay::new(); | ||||||
|         let notif_sender_two = notif_sender.clone(); |         let notif_sender_two = notif_sender.clone(); | ||||||
|  |         #[cfg(feature = "merscfg")] | ||||||
|  |         match gui_config | ||||||
|  |             .merscfg | ||||||
|  |             .load(Arc::clone(&event_sender), notif_sender.clone()) | ||||||
|  |         { | ||||||
|  |             Err(e) => { | ||||||
|  |                 if !matches!(e.kind(), std::io::ErrorKind::NotFound) { | ||||||
|  |                     eprintln!("Couldn't load merscfg: {e}") | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             Ok(Err(e)) => { | ||||||
|  |                 eprintln!("Error loading merscfg:\n{e}"); | ||||||
|  |             } | ||||||
|  |             Ok(Ok(Err((m, e)))) => { | ||||||
|  |                 if let Some(e) = e { | ||||||
|  |                     eprintln!("Error loading merscfg:\n{m}\n{e}"); | ||||||
|  |                 } else { | ||||||
|  |                     eprintln!("Error loading merscfg:\n{m}"); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             Ok(Ok(Ok(()))) => eprintln!("Info: using merscfg"), | ||||||
|  |         } | ||||||
|         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 | ||||||
| @ -662,6 +691,9 @@ pub(crate) trait GuiElemInternal: GuiElem { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     fn _keyboard_move_focus(&mut self, decrement: bool, refocus: bool) -> bool { |     fn _keyboard_move_focus(&mut self, decrement: bool, refocus: bool) -> bool { | ||||||
|  |         if self.config().enabled == false { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|         let mut focus_index = if refocus { |         let mut focus_index = if refocus { | ||||||
|             usize::MAX |             usize::MAX | ||||||
|         } else { |         } else { | ||||||
| @ -986,7 +1018,7 @@ pub enum GuiAction { | |||||||
|     /// 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>>), |     ContextMenu(Option<(Vec<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( | ||||||
| @ -998,8 +1030,11 @@ pub enum GuiAction { | |||||||
|     SetLineHeight(f32), |     SetLineHeight(f32), | ||||||
|     LoadCover(CoverId), |     LoadCover(CoverId), | ||||||
|     /// Run a custom closure with mutable access to the Gui struct
 |     /// Run a custom closure with mutable access to the Gui struct
 | ||||||
|     Do(Box<dyn FnMut(&mut Gui)>), |     Do(Box<dyn FnOnce(&mut Gui)>), | ||||||
|     Exit, |     Exit, | ||||||
|  |     EditSongs(Vec<Song>), | ||||||
|  |     // EditAlbums(Vec<Album>),
 | ||||||
|  |     // EditArtists(Vec<Artist>),
 | ||||||
| } | } | ||||||
| pub enum Dragging { | pub enum Dragging { | ||||||
|     Artist(ArtistId), |     Artist(ArtistId), | ||||||
| @ -1032,7 +1067,6 @@ pub struct DrawInfo<'a> { | |||||||
|         Dragging, |         Dragging, | ||||||
|         Option<Box<dyn FnMut(&mut DrawInfo, &mut Graphics2D)>>, |         Option<Box<dyn FnMut(&mut DrawInfo, &mut Graphics2D)>>, | ||||||
|     )>, |     )>, | ||||||
|     pub context_menu: Option<Box<dyn GuiElem>>, |  | ||||||
|     pub gui_config: &'a mut GuiConfig, |     pub gui_config: &'a mut GuiConfig, | ||||||
|     pub high_performance: bool, |     pub high_performance: bool, | ||||||
| } | } | ||||||
| @ -1068,7 +1102,40 @@ impl Gui { | |||||||
|             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::SetHighPerformance(d) => self.high_performance = d, |             GuiAction::SetHighPerformance(d) => self.high_performance = d, | ||||||
|             GuiAction::ContextMenu(m) => self.gui.c_context_menu = m, |             GuiAction::ContextMenu(elems) => { | ||||||
|  |                 self.gui.c_context_menu = if let Some(elems) = elems { | ||||||
|  |                     let elem_height = 32.0; | ||||||
|  |                     let w = elem_height * 6.0; | ||||||
|  |                     let h = elem_height * elems.len() as f32; | ||||||
|  |                     let mut ax = self.mouse_pos.x / self.size.x.max(1) as f32; | ||||||
|  |                     let mut ay = self.mouse_pos.y / self.size.y.max(1) as f32; | ||||||
|  |                     let mut bx = (self.mouse_pos.x + w) / self.size.x.max(1) as f32; | ||||||
|  |                     let mut by = (self.mouse_pos.y + h) / self.size.y.max(1) as f32; | ||||||
|  |                     if bx > 1.0 { | ||||||
|  |                         ax -= bx - 1.0; | ||||||
|  |                         bx = 1.0; | ||||||
|  |                     } | ||||||
|  |                     if by > 1.0 { | ||||||
|  |                         ay -= by - 1.0; | ||||||
|  |                         by = 1.0; | ||||||
|  |                     } | ||||||
|  |                     if ax < 0.0 { | ||||||
|  |                         ax = 0.0; | ||||||
|  |                     } | ||||||
|  |                     if ay < 0.0 { | ||||||
|  |                         ay = 0.0; | ||||||
|  |                     } | ||||||
|  |                     Some(Box::new(ScrollBox::new( | ||||||
|  |                         GuiElemCfg::at(Rectangle::from_tuples((ax, ay), (bx, by))), | ||||||
|  |                         crate::gui_base::ScrollBoxSizeUnit::Pixels, | ||||||
|  |                         elems, | ||||||
|  |                         vec![], | ||||||
|  |                         elem_height, | ||||||
|  |                     ))) | ||||||
|  |                 } else { | ||||||
|  |                     None | ||||||
|  |                 }; | ||||||
|  |             } | ||||||
|             GuiAction::SetLineHeight(h) => { |             GuiAction::SetLineHeight(h) => { | ||||||
|                 self.line_height = h; |                 self.line_height = h; | ||||||
|                 self.gui |                 self.gui | ||||||
| @ -1080,7 +1147,7 @@ impl Gui { | |||||||
|                     .unwrap() |                     .unwrap() | ||||||
|                     .insert(id, GuiServerImage::new_cover(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(f) => f(self), | ||||||
|             GuiAction::Exit => _ = self.event_sender.send_event(GuiEvent::Exit), |             GuiAction::Exit => _ = self.event_sender.send_event(GuiEvent::Exit), | ||||||
|             GuiAction::EndIdle(v) => { |             GuiAction::EndIdle(v) => { | ||||||
|                 if v { |                 if v { | ||||||
| @ -1090,19 +1157,20 @@ impl Gui { | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             GuiAction::OpenSettings(v) => { |             GuiAction::OpenSettings(v) => { | ||||||
|                 self.gui.idle.target = 0.0; |                 self.gui.unidle(); | ||||||
|                 self.gui.last_interaction = Instant::now(); |  | ||||||
|                 if self.gui.settings.0 != v { |                 if self.gui.settings.0 != v { | ||||||
|                     self.gui.settings = (v, Some(Instant::now())); |                     self.gui.settings = (v, Some(Instant::now())); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             GuiAction::OpenMain => { |             GuiAction::OpenMain => { | ||||||
|                 self.gui.idle.target = 0.0; |                 self.gui.unidle(); | ||||||
|                 self.gui.last_interaction = Instant::now(); |  | ||||||
|                 if self.gui.settings.0 { |                 if self.gui.settings.0 { | ||||||
|                     self.gui.settings = (false, Some(Instant::now())); |                     self.gui.settings = (false, Some(Instant::now())); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |             GuiAction::EditSongs(songs) => { | ||||||
|  |                 self.gui.c_editing_songs = Some(EditorForSongs::new(songs)); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -1113,10 +1181,13 @@ impl WindowHandler<GuiEvent> for Gui { | |||||||
|             Rectangle::new(Vec2::ZERO, self.size.into_f32()), |             Rectangle::new(Vec2::ZERO, self.size.into_f32()), | ||||||
|             Color::BLACK, |             Color::BLACK, | ||||||
|         ); |         ); | ||||||
|         let mut dblock = self.database.lock().unwrap(); |         let dblock = Arc::clone(&self.database); | ||||||
|  |         let mut dblock = dblock.lock().unwrap(); | ||||||
|         let mut covers = self.covers.take().unwrap(); |         let mut covers = self.covers.take().unwrap(); | ||||||
|         let mut custom_images = self.custom_images.take().unwrap(); |         let mut custom_images = self.custom_images.take().unwrap(); | ||||||
|         let mut cfg = self.gui_config.take().unwrap(); |         let mut cfg = self.gui_config.take().unwrap(); | ||||||
|  |         #[cfg(feature = "merscfg")] | ||||||
|  |         MersCfg::run(&mut cfg, self, Some(&mut dblock), |m| &m.func_before_draw); | ||||||
|         let mut info = DrawInfo { |         let mut info = DrawInfo { | ||||||
|             time: draw_start_time, |             time: draw_start_time, | ||||||
|             actions: Vec::with_capacity(0), |             actions: Vec::with_capacity(0), | ||||||
| @ -1133,12 +1204,10 @@ impl WindowHandler<GuiEvent> for Gui { | |||||||
|             line_height: self.line_height, |             line_height: self.line_height, | ||||||
|             high_performance: self.high_performance, |             high_performance: self.high_performance, | ||||||
|             dragging: self.dragging.take(), |             dragging: self.dragging.take(), | ||||||
|             context_menu: self.gui.c_context_menu.take(), |  | ||||||
|             gui_config: &mut cfg, |             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 { | ||||||
| @ -1242,6 +1311,9 @@ impl WindowHandler<GuiEvent> for Gui { | |||||||
|                 self.exec_gui_action(a) |                 self.exec_gui_action(a) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |         if button != MouseButton::Right { | ||||||
|  |             self.gui.c_context_menu = None; | ||||||
|  |         } | ||||||
|         helper.request_redraw(); |         helper.request_redraw(); | ||||||
|     } |     } | ||||||
|     fn on_mouse_wheel_scroll( |     fn on_mouse_wheel_scroll( | ||||||
| @ -1365,10 +1437,34 @@ impl WindowHandler<GuiEvent> for Gui { | |||||||
|         match user_event { |         match user_event { | ||||||
|             GuiEvent::Refresh => helper.request_redraw(), |             GuiEvent::Refresh => helper.request_redraw(), | ||||||
|             GuiEvent::UpdatedLibrary => { |             GuiEvent::UpdatedLibrary => { | ||||||
|  |                 #[cfg(feature = "merscfg")] | ||||||
|  |                 if let Some(mut gc) = self.gui_config.take() { | ||||||
|  |                     MersCfg::run( | ||||||
|  |                         &mut gc, | ||||||
|  |                         self, | ||||||
|  |                         self.database.clone().lock().ok().as_mut().map(|v| &mut **v), | ||||||
|  |                         |m| &m.func_library_updated, | ||||||
|  |                     ); | ||||||
|  |                     self.gui_config = Some(gc); | ||||||
|  |                 } else { | ||||||
|  |                     eprintln!("WARN: Skipping call to merscfg's library_updated because gui_config is not available"); | ||||||
|  |                 } | ||||||
|                 self.gui._recursive_all(&mut |e| e.updated_library()); |                 self.gui._recursive_all(&mut |e| e.updated_library()); | ||||||
|                 helper.request_redraw(); |                 helper.request_redraw(); | ||||||
|             } |             } | ||||||
|             GuiEvent::UpdatedQueue => { |             GuiEvent::UpdatedQueue => { | ||||||
|  |                 #[cfg(feature = "merscfg")] | ||||||
|  |                 if let Some(mut gc) = self.gui_config.take() { | ||||||
|  |                     MersCfg::run( | ||||||
|  |                         &mut gc, | ||||||
|  |                         self, | ||||||
|  |                         self.database.clone().lock().ok().as_mut().map(|v| &mut **v), | ||||||
|  |                         |m| &m.func_queue_updated, | ||||||
|  |                     ); | ||||||
|  |                     self.gui_config = Some(gc); | ||||||
|  |                 } else { | ||||||
|  |                     eprintln!("WARN: Skipping call to merscfg's queue_updated because gui_config is not available"); | ||||||
|  |                 } | ||||||
|                 self.gui._recursive_all(&mut |e| e.updated_queue()); |                 self.gui._recursive_all(&mut |e| e.updated_queue()); | ||||||
|                 helper.request_redraw(); |                 helper.request_redraw(); | ||||||
|             } |             } | ||||||
| @ -1472,3 +1568,15 @@ pub fn morph_rect(a: &Rectangle, b: &Rectangle, p: f32) -> Rectangle { | |||||||
|         ), |         ), | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
|  | pub fn rect_from_rel(v: &Rectangle, outer: &Rectangle) -> Rectangle { | ||||||
|  |     Rectangle::from_tuples( | ||||||
|  |         ( | ||||||
|  |             outer.top_left().x + v.top_left().x * outer.width(), | ||||||
|  |             outer.top_left().y + v.top_left().y * outer.height(), | ||||||
|  |         ), | ||||||
|  |         ( | ||||||
|  |             outer.top_left().x + v.bottom_right().x * outer.width(), | ||||||
|  |             outer.top_left().y + v.bottom_right().y * outer.height(), | ||||||
|  |         ), | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | |||||||
| @ -122,9 +122,11 @@ pub struct ScrollBox<C: GuiElemChildren> { | |||||||
|     config: GuiElemCfg, |     config: GuiElemCfg, | ||||||
|     pub children: C, |     pub children: C, | ||||||
|     pub children_heights: Vec<f32>, |     pub children_heights: Vec<f32>, | ||||||
|  |     pub default_size: 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, | ||||||
|  |     /// the y-position of the bottom edge of the last element (i.e. the total height)
 | ||||||
|     height_bottom: f32, |     height_bottom: f32, | ||||||
|     /// 0.max(height_bottom - 1)
 |     /// 0.max(height_bottom - 1)
 | ||||||
|     max_scroll: f32, |     max_scroll: f32, | ||||||
| @ -145,15 +147,16 @@ impl<C: GuiElemChildren> ScrollBox<C> { | |||||||
|         size_unit: ScrollBoxSizeUnit, |         size_unit: ScrollBoxSizeUnit, | ||||||
|         children: C, |         children: C, | ||||||
|         children_heights: Vec<f32>, |         children_heights: Vec<f32>, | ||||||
|  |         default_size: f32, | ||||||
|     ) -> Self { |     ) -> Self { | ||||||
|         Self { |         Self { | ||||||
|             config: config.w_scroll().w_mouse(), |             config: config.w_scroll().w_mouse(), | ||||||
|             children, |             children, | ||||||
|             children_heights, |             children_heights, | ||||||
|  |             default_size, | ||||||
|             size_unit, |             size_unit, | ||||||
|             scroll_target: 0.0, |             scroll_target: 0.0, | ||||||
|             scroll_display: 0.0, |             scroll_display: 0.0, | ||||||
|             /// the y-position of the bottom edge of the last element (i.e. the total height)
 |  | ||||||
|             height_bottom: 0.0, |             height_bottom: 0.0, | ||||||
|             max_scroll: 0.0, |             max_scroll: 0.0, | ||||||
|             last_height_px: 0.0, |             last_height_px: 0.0, | ||||||
| @ -217,7 +220,7 @@ impl<C: GuiElemChildren + 'static> GuiElem for ScrollBox<C> { | |||||||
|             if self.children_heights.len() != self.children.len() { |             if self.children_heights.len() != self.children.len() { | ||||||
|                 let target = self.children.len(); |                 let target = self.children.len(); | ||||||
|                 while self.children_heights.len() < target { |                 while self.children_heights.len() < target { | ||||||
|                     self.children_heights.push(0.0); |                     self.children_heights.push(self.default_size); | ||||||
|                 } |                 } | ||||||
|                 while self.children_heights.len() > target { |                 while self.children_heights.len() > target { | ||||||
|                     self.children_heights.pop(); |                     self.children_heights.pop(); | ||||||
| @ -341,7 +344,7 @@ impl<C: GuiElemChildren> Button<C> { | |||||||
|         children: C, |         children: C, | ||||||
|     ) -> Self { |     ) -> Self { | ||||||
|         Self { |         Self { | ||||||
|             config: config.w_mouse(), |             config: config.w_mouse().w_keyboard_focus(), | ||||||
|             children, |             children, | ||||||
|             action: Arc::new(action), |             action: Arc::new(action), | ||||||
|         } |         } | ||||||
| @ -376,6 +379,27 @@ impl<C: GuiElemChildren + 'static> GuiElem for Button<C> { | |||||||
|             vec![] |             vec![] | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |     fn key_focus( | ||||||
|  |         &mut self, | ||||||
|  |         _modifiers: speedy2d::window::ModifiersState, | ||||||
|  |         down: bool, | ||||||
|  |         key: Option<speedy2d::window::VirtualKeyCode>, | ||||||
|  |         _scan: speedy2d::window::KeyScancode, | ||||||
|  |     ) -> Vec<GuiAction> { | ||||||
|  |         if !down | ||||||
|  |             && matches!( | ||||||
|  |                 key, | ||||||
|  |                 Some( | ||||||
|  |                     speedy2d::window::VirtualKeyCode::Return | ||||||
|  |                         | speedy2d::window::VirtualKeyCode::NumpadEnter, | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
|  |         { | ||||||
|  |             (self.action.clone())(self) | ||||||
|  |         } else { | ||||||
|  |             vec![] | ||||||
|  |         } | ||||||
|  |     } | ||||||
|     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) { | ||||||
|         let mouse_down = self.config.mouse_down.0; |         let mouse_down = self.config.mouse_down.0; | ||||||
|         let contains = info.pos.contains(info.mouse_pos); |         let contains = info.pos.contains(info.mouse_pos); | ||||||
| @ -389,6 +413,32 @@ impl<C: GuiElemChildren + 'static> GuiElem for Button<C> { | |||||||
|                 Color::from_rgb(0.1, 0.1, 0.1) |                 Color::from_rgb(0.1, 0.1, 0.1) | ||||||
|             }, |             }, | ||||||
|         ); |         ); | ||||||
|  |         if info.has_keyboard_focus { | ||||||
|  |             g.draw_line( | ||||||
|  |                 *info.pos.top_left(), | ||||||
|  |                 info.pos.top_right(), | ||||||
|  |                 2.0, | ||||||
|  |                 Color::WHITE, | ||||||
|  |             ); | ||||||
|  |             g.draw_line( | ||||||
|  |                 *info.pos.top_left(), | ||||||
|  |                 info.pos.bottom_left(), | ||||||
|  |                 2.0, | ||||||
|  |                 Color::WHITE, | ||||||
|  |             ); | ||||||
|  |             g.draw_line( | ||||||
|  |                 info.pos.top_right(), | ||||||
|  |                 *info.pos.bottom_right(), | ||||||
|  |                 2.0, | ||||||
|  |                 Color::WHITE, | ||||||
|  |             ); | ||||||
|  |             g.draw_line( | ||||||
|  |                 info.pos.bottom_left(), | ||||||
|  |                 *info.pos.bottom_right(), | ||||||
|  |                 2.0, | ||||||
|  |                 Color::WHITE, | ||||||
|  |             ); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										371
									
								
								musicdb-client/src/gui_edit_song.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										371
									
								
								musicdb-client/src/gui_edit_song.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,371 @@ | |||||||
|  | use std::{ | ||||||
|  |     sync::{atomic::AtomicU8, Arc}, | ||||||
|  |     time::Instant, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | use musicdb_lib::data::{song::Song, ArtistId}; | ||||||
|  | use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle}; | ||||||
|  | 
 | ||||||
|  | use crate::{ | ||||||
|  |     color_scale, | ||||||
|  |     gui::{GuiAction, GuiElem, GuiElemCfg, GuiElemChildren}, | ||||||
|  |     gui_anim::AnimationController, | ||||||
|  |     gui_base::{Button, Panel, ScrollBox}, | ||||||
|  |     gui_text::{Label, TextField}, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | // TODO: Fix bug where after selecting an artist you can't mouse-click the buttons anymore (to change it)
 | ||||||
|  | 
 | ||||||
|  | const ELEM_HEIGHT: f32 = 32.0; | ||||||
|  | 
 | ||||||
|  | pub struct EditorForSongs { | ||||||
|  |     config: GuiElemCfg, | ||||||
|  |     songs: Vec<Song>, | ||||||
|  |     c_title: Label, | ||||||
|  |     c_scrollbox: ScrollBox<EditorForSongElems>, | ||||||
|  |     c_buttons: Panel<[Button<[Label; 1]>; 2]>, | ||||||
|  |     c_background: Panel<()>, | ||||||
|  |     created: Option<Instant>, | ||||||
|  |     event_sender: std::sync::mpsc::Sender<Event>, | ||||||
|  |     event_recv: std::sync::mpsc::Receiver<Event>, | ||||||
|  | } | ||||||
|  | pub enum Event { | ||||||
|  |     Close, | ||||||
|  |     Apply, | ||||||
|  |     SetArtist(String, Option<ArtistId>), | ||||||
|  | } | ||||||
|  | pub struct EditorForSongElems { | ||||||
|  |     c_title: TextField, | ||||||
|  |     c_artist: EditorForSongArtistChooser, | ||||||
|  |     c_album: Label, | ||||||
|  | } | ||||||
|  | impl GuiElemChildren for EditorForSongElems { | ||||||
|  |     fn iter(&mut self) -> Box<dyn Iterator<Item = &mut dyn crate::gui::GuiElem> + '_> { | ||||||
|  |         Box::new( | ||||||
|  |             [ | ||||||
|  |                 self.c_title.elem_mut(), | ||||||
|  |                 self.c_artist.elem_mut(), | ||||||
|  |                 self.c_album.elem_mut(), | ||||||
|  |             ] | ||||||
|  |             .into_iter(), | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  |     fn len(&self) -> usize { | ||||||
|  |         3 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl EditorForSongs { | ||||||
|  |     pub fn new(songs: Vec<Song>) -> Self { | ||||||
|  |         let (sender, recv) = std::sync::mpsc::channel(); | ||||||
|  |         Self { | ||||||
|  |             config: GuiElemCfg::at(Rectangle::from_tuples((0.0, 1.0), (1.0, 2.0))), | ||||||
|  |             c_title: Label::new( | ||||||
|  |                 GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (1.0, 0.05))), | ||||||
|  |                 format!("Editing {} songs", songs.len()), | ||||||
|  |                 Color::LIGHT_GRAY, | ||||||
|  |                 None, | ||||||
|  |                 Vec2::new(0.5, 0.5), | ||||||
|  |             ), | ||||||
|  |             c_scrollbox: ScrollBox::new( | ||||||
|  |                 GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.05), (1.0, 0.95))), | ||||||
|  |                 crate::gui_base::ScrollBoxSizeUnit::Pixels, | ||||||
|  |                 EditorForSongElems { | ||||||
|  |                     c_title: TextField::new( | ||||||
|  |                         GuiElemCfg::default(), | ||||||
|  |                         format!( | ||||||
|  |                             "Title ({})", | ||||||
|  |                             songs | ||||||
|  |                                 .iter() | ||||||
|  |                                 .enumerate() | ||||||
|  |                                 .map(|(i, s)| format!( | ||||||
|  |                                     "{}{}", | ||||||
|  |                                     if i == 0 { "" } else { ", " }, | ||||||
|  |                                     s.title | ||||||
|  |                                 )) | ||||||
|  |                                 .collect::<String>() | ||||||
|  |                         ), | ||||||
|  |                         color_scale(Color::MAGENTA, 0.6, 0.6, 0.6, Some(0.75)), | ||||||
|  |                         Color::MAGENTA, | ||||||
|  |                     ), | ||||||
|  |                     c_artist: EditorForSongArtistChooser::new(sender.clone()), | ||||||
|  |                     c_album: Label::new( | ||||||
|  |                         GuiElemCfg::default(), | ||||||
|  |                         format!("(todo...)"), | ||||||
|  |                         Color::GRAY, | ||||||
|  |                         None, | ||||||
|  |                         Vec2::new(0.0, 0.5), | ||||||
|  |                     ), | ||||||
|  |                 }, | ||||||
|  |                 vec![], | ||||||
|  |                 ELEM_HEIGHT, | ||||||
|  |             ), | ||||||
|  |             c_buttons: Panel::new( | ||||||
|  |                 GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.95), (1.0, 1.0))), | ||||||
|  |                 [ | ||||||
|  |                     { | ||||||
|  |                         let sender = sender.clone(); | ||||||
|  |                         Button::new( | ||||||
|  |                             GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (0.5, 1.0))), | ||||||
|  |                             move |_| { | ||||||
|  |                                 sender.send(Event::Close).unwrap(); | ||||||
|  |                                 vec![] | ||||||
|  |                             }, | ||||||
|  |                             [Label::new( | ||||||
|  |                                 GuiElemCfg::default(), | ||||||
|  |                                 "Close".to_owned(), | ||||||
|  |                                 Color::WHITE, | ||||||
|  |                                 None, | ||||||
|  |                                 Vec2::new(0.5, 0.5), | ||||||
|  |                             )], | ||||||
|  |                         ) | ||||||
|  |                     }, | ||||||
|  |                     { | ||||||
|  |                         let sender = sender.clone(); | ||||||
|  |                         Button::new( | ||||||
|  |                             GuiElemCfg::at(Rectangle::from_tuples((0.5, 0.0), (1.0, 1.0))), | ||||||
|  |                             move |_| { | ||||||
|  |                                 sender.send(Event::Apply).unwrap(); | ||||||
|  |                                 vec![] | ||||||
|  |                             }, | ||||||
|  |                             [Label::new( | ||||||
|  |                                 GuiElemCfg::default(), | ||||||
|  |                                 "Apply".to_owned(), | ||||||
|  |                                 Color::WHITE, | ||||||
|  |                                 None, | ||||||
|  |                                 Vec2::new(0.5, 0.5), | ||||||
|  |                             )], | ||||||
|  |                         ) | ||||||
|  |                     }, | ||||||
|  |                 ], | ||||||
|  |             ), | ||||||
|  |             c_background: Panel::with_background(GuiElemCfg::default(), (), Color::BLACK), | ||||||
|  |             created: Some(Instant::now()), | ||||||
|  |             songs, | ||||||
|  |             event_sender: sender, | ||||||
|  |             event_recv: recv, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl GuiElem for EditorForSongs { | ||||||
|  |     fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> { | ||||||
|  |         Box::new( | ||||||
|  |             [ | ||||||
|  |                 self.c_title.elem_mut(), | ||||||
|  |                 self.c_scrollbox.elem_mut(), | ||||||
|  |                 self.c_buttons.elem_mut(), | ||||||
|  |                 self.c_background.elem_mut(), | ||||||
|  |             ] | ||||||
|  |             .into_iter(), | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  |     fn draw(&mut self, info: &mut crate::gui::DrawInfo, g: &mut speedy2d::Graphics2D) { | ||||||
|  |         loop { | ||||||
|  |             match self.event_recv.try_recv() { | ||||||
|  |                 Ok(e) => match e { | ||||||
|  |                     Event::Close => info.actions.push(GuiAction::Do(Box::new(|gui| { | ||||||
|  |                         gui.gui.c_editing_songs = None; | ||||||
|  |                         gui.gui.set_normal_ui_enabled(true); | ||||||
|  |                     }))), | ||||||
|  |                     Event::Apply => eprintln!("TODO: Apply"), | ||||||
|  |                     Event::SetArtist(name, id) => { | ||||||
|  |                         self.c_scrollbox.children.c_artist.chosen_id = id; | ||||||
|  |                         self.c_scrollbox.children.c_artist.last_search = name.to_lowercase(); | ||||||
|  |                         self.c_scrollbox.children.c_artist.open_prog.target = 1.0; | ||||||
|  |                         *self | ||||||
|  |                             .c_scrollbox | ||||||
|  |                             .children | ||||||
|  |                             .c_artist | ||||||
|  |                             .c_name | ||||||
|  |                             .c_input | ||||||
|  |                             .content | ||||||
|  |                             .text() = name; | ||||||
|  |                         self.c_scrollbox.children.c_artist.config_mut().redraw = true; | ||||||
|  |                     } | ||||||
|  |                 }, | ||||||
|  |                 Err(_) => break, | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         // animation
 | ||||||
|  |         if let Some(created) = &self.created { | ||||||
|  |             if let Some(h) = &info.helper { | ||||||
|  |                 h.request_redraw(); | ||||||
|  |             } | ||||||
|  |             let open_prog = created.elapsed().as_secs_f32() / 0.5; | ||||||
|  |             if open_prog >= 1.0 { | ||||||
|  |                 self.created = None; | ||||||
|  |                 self.config.pos = Rectangle::from_tuples((0.0, 0.0), (1.0, 1.0)); | ||||||
|  |                 info.actions.push(GuiAction::Do(Box::new(|gui| { | ||||||
|  |                     gui.gui.set_normal_ui_enabled(false); | ||||||
|  |                 }))); | ||||||
|  |             } else { | ||||||
|  |                 let offset = 1.0 - open_prog; | ||||||
|  |                 let offset = offset * offset; | ||||||
|  |                 self.config.pos = Rectangle::from_tuples((0.0, offset), (1.0, 1.0 + offset)); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         // artist sel
 | ||||||
|  |         if self | ||||||
|  |             .c_scrollbox | ||||||
|  |             .children | ||||||
|  |             .c_artist | ||||||
|  |             .open_prog | ||||||
|  |             .update(Instant::now(), false) | ||||||
|  |         { | ||||||
|  |             if let Some(v) = self.c_scrollbox.children_heights.get_mut(1) { | ||||||
|  |                 *v = ELEM_HEIGHT * self.c_scrollbox.children.c_artist.open_prog.value; | ||||||
|  |                 self.c_scrollbox.config_mut().redraw = true; | ||||||
|  |             } | ||||||
|  |             if let Some(h) = &info.helper { | ||||||
|  |                 h.request_redraw(); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     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 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub struct EditorForSongArtistChooser { | ||||||
|  |     config: GuiElemCfg, | ||||||
|  |     event_sender: std::sync::mpsc::Sender<Event>, | ||||||
|  |     /// `1.0` = collapsed, `self.expand_to` = expanded (shows `c_picker` of height 7-1=6)
 | ||||||
|  |     open_prog: AnimationController<f32>, | ||||||
|  |     expand_to: f32, | ||||||
|  |     chosen_id: Option<ArtistId>, | ||||||
|  |     c_name: TextField, | ||||||
|  |     c_picker: ScrollBox<Vec<Button<[Label; 1]>>>, | ||||||
|  |     last_search: String, | ||||||
|  | } | ||||||
|  | impl EditorForSongArtistChooser { | ||||||
|  |     pub fn new(event_sender: std::sync::mpsc::Sender<Event>) -> Self { | ||||||
|  |         let expand_to = 7.0; | ||||||
|  |         Self { | ||||||
|  |             config: GuiElemCfg::default(), | ||||||
|  |             event_sender, | ||||||
|  |             open_prog: AnimationController::new(1.0, 1.0, 0.3, 8.0, 0.5, 0.6, Instant::now()), | ||||||
|  |             expand_to, | ||||||
|  |             chosen_id: None, | ||||||
|  |             c_name: TextField::new( | ||||||
|  |                 GuiElemCfg::default(), | ||||||
|  |                 "artist".to_owned(), | ||||||
|  |                 Color::DARK_GRAY, | ||||||
|  |                 Color::WHITE, | ||||||
|  |             ), | ||||||
|  |             c_picker: ScrollBox::new( | ||||||
|  |                 GuiElemCfg::default().disabled(), | ||||||
|  |                 crate::gui_base::ScrollBoxSizeUnit::Pixels, | ||||||
|  |                 vec![], | ||||||
|  |                 vec![], | ||||||
|  |                 ELEM_HEIGHT, | ||||||
|  |             ), | ||||||
|  |             last_search: String::from("\n"), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | impl GuiElem for EditorForSongArtistChooser { | ||||||
|  |     fn draw(&mut self, info: &mut crate::gui::DrawInfo, _g: &mut speedy2d::Graphics2D) { | ||||||
|  |         let picker_enabled = self.open_prog.value > 1.0; | ||||||
|  |         self.c_picker.config_mut().enabled = picker_enabled; | ||||||
|  |         if picker_enabled { | ||||||
|  |             let split = 1.0 / self.open_prog.value; | ||||||
|  |             self.c_name.config_mut().pos = Rectangle::from_tuples((0.0, 0.0), (1.0, split)); | ||||||
|  |             self.c_picker.config_mut().pos = Rectangle::from_tuples((0.0, split), (1.0, 1.0)); | ||||||
|  |         } else { | ||||||
|  |             self.c_name.config_mut().pos = Rectangle::from_tuples((0.0, 0.0), (1.0, 1.0)); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let search = self.c_name.c_input.content.get_text().to_lowercase(); | ||||||
|  |         let search_changed = &self.last_search != &search; | ||||||
|  |         if self.config.redraw || search_changed { | ||||||
|  |             *self.c_name.c_input.content.color() = if self.chosen_id.is_some() { | ||||||
|  |                 Color::GREEN | ||||||
|  |             } else { | ||||||
|  |                 Color::WHITE | ||||||
|  |             }; | ||||||
|  |             if search_changed { | ||||||
|  |                 self.chosen_id = None; | ||||||
|  |                 self.open_prog.target = self.expand_to; | ||||||
|  |                 if search.is_empty() { | ||||||
|  |                     self.open_prog.target = 1.0; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             let artists = info | ||||||
|  |                 .database | ||||||
|  |                 .artists() | ||||||
|  |                 .values() | ||||||
|  |                 .filter(|artist| artist.name.to_lowercase().contains(&search)) | ||||||
|  |                 // .take(self.open_prog.value as _)
 | ||||||
|  |                 .map(|artist| (artist.name.clone(), artist.id)) | ||||||
|  |                 .collect::<Vec<_>>(); | ||||||
|  |             let chosen_id = self.chosen_id; | ||||||
|  |             self.c_picker.children = artists | ||||||
|  |                 .iter() | ||||||
|  |                 .map(|a| { | ||||||
|  |                     let sender = self.event_sender.clone(); | ||||||
|  |                     let name = a.0.clone(); | ||||||
|  |                     let id = a.1; | ||||||
|  |                     Button::new( | ||||||
|  |                         GuiElemCfg::default(), | ||||||
|  |                         move |_| { | ||||||
|  |                             sender | ||||||
|  |                                 .send(Event::SetArtist(name.clone(), Some(id))) | ||||||
|  |                                 .unwrap(); | ||||||
|  |                             vec![] | ||||||
|  |                         }, | ||||||
|  |                         [Label::new( | ||||||
|  |                             GuiElemCfg::default(), | ||||||
|  |                             a.0.clone(), | ||||||
|  |                             if chosen_id.is_some_and(|c| c == a.1) { | ||||||
|  |                                 Color::WHITE | ||||||
|  |                             } else { | ||||||
|  |                                 Color::LIGHT_GRAY | ||||||
|  |                             }, | ||||||
|  |                             None, | ||||||
|  |                             Vec2::new(0.0, 0.5), | ||||||
|  |                         )], | ||||||
|  |                     ) | ||||||
|  |                 }) | ||||||
|  |                 .collect(); | ||||||
|  |             self.c_picker.config_mut().redraw = true; | ||||||
|  |             self.last_search = search; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     fn config(&self) -> &GuiElemCfg { | ||||||
|  |         &self.config | ||||||
|  |     } | ||||||
|  |     fn config_mut(&mut self) -> &mut GuiElemCfg { | ||||||
|  |         &mut self.config | ||||||
|  |     } | ||||||
|  |     fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> { | ||||||
|  |         Box::new([self.c_name.elem_mut(), self.c_picker.elem_mut()].into_iter()) | ||||||
|  |     } | ||||||
|  |     fn any(&self) -> &dyn std::any::Any { | ||||||
|  |         self | ||||||
|  |     } | ||||||
|  |     fn any_mut(&mut self) -> &mut dyn std::any::Any { | ||||||
|  |         self | ||||||
|  |     } | ||||||
|  |     fn elem(&self) -> &dyn GuiElem { | ||||||
|  |         self | ||||||
|  |     } | ||||||
|  |     fn elem_mut(&mut self) -> &mut dyn GuiElem { | ||||||
|  |         self | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -4,7 +4,7 @@ use musicdb_lib::data::ArtistId; | |||||||
| use speedy2d::{color::Color, dimen::Vec2, image::ImageHandle, shape::Rectangle}; | use speedy2d::{color::Color, dimen::Vec2, image::ImageHandle, shape::Rectangle}; | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::{ | ||||||
|     gui::{DrawInfo, GuiAction, GuiElem, GuiElemCfg, GuiServerImage}, |     gui::{rect_from_rel, DrawInfo, GuiAction, GuiElem, GuiElemCfg, GuiServerImage}, | ||||||
|     gui_anim::AnimationController, |     gui_anim::AnimationController, | ||||||
|     gui_base::Button, |     gui_base::Button, | ||||||
|     gui_playback::{get_right_x, image_display, CurrentInfo}, |     gui_playback::{get_right_x, image_display, CurrentInfo}, | ||||||
| @ -13,24 +13,32 @@ use crate::{ | |||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| pub struct IdleDisplay { | pub struct IdleDisplay { | ||||||
|     config: GuiElemCfg, |     pub config: GuiElemCfg, | ||||||
|     pub idle_mode: f32, |     pub idle_mode: f32, | ||||||
|     current_info: CurrentInfo, |     pub current_info: CurrentInfo, | ||||||
|     current_artist_image: Option<(ArtistId, Option<(String, Option<Option<ImageHandle>>)>)>, |     pub current_artist_image: Option<(ArtistId, Option<(String, Option<Option<ImageHandle>>)>)>, | ||||||
|     pub c_idle_exit_hint: Button<[Label; 1]>, |     pub c_idle_exit_hint: Button<[Label; 1]>, | ||||||
|     c_top_label: AdvancedLabel, |     pub c_top_label: AdvancedLabel, | ||||||
|     c_side1_label: AdvancedLabel, |     pub c_side1_label: AdvancedLabel, | ||||||
|     c_side2_label: AdvancedLabel, |     pub c_side2_label: AdvancedLabel, | ||||||
|     c_buttons: PlayPause, |     pub c_buttons: PlayPause, | ||||||
|     cover_aspect_ratio: AnimationController<f32>, |     pub c_buttons_custom_pos: bool, | ||||||
|     artist_image_aspect_ratio: AnimationController<f32>, | 
 | ||||||
|     cover_left: f32, |     pub cover_aspect_ratio: AnimationController<f32>, | ||||||
|     cover_top: f32, |     pub artist_image_aspect_ratio: AnimationController<f32>, | ||||||
|     cover_bottom: f32, | 
 | ||||||
|  |     pub cover_pos: Option<Rectangle>, | ||||||
|  |     pub cover_left: f32, | ||||||
|  |     pub cover_top: f32, | ||||||
|  |     pub cover_bottom: f32, | ||||||
|  | 
 | ||||||
|  |     pub artist_image_pos: Option<Rectangle>, | ||||||
|     /// 0.0 -> same height as cover,
 |     /// 0.0 -> same height as cover,
 | ||||||
|     /// 0.5 -> lower half of cover
 |     /// 0.5 -> lower half of cover
 | ||||||
|     artist_image_top: f32, |     pub artist_image_top: f32, | ||||||
|     artist_image_to_cover_margin: f32, |     pub artist_image_to_cover_margin: f32, | ||||||
|  | 
 | ||||||
|  |     pub force_reset_texts: bool, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl IdleDisplay { | impl IdleDisplay { | ||||||
| @ -60,6 +68,7 @@ impl IdleDisplay { | |||||||
|             c_side1_label: AdvancedLabel::new(GuiElemCfg::default(), Vec2::new(0.0, 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![]), |             c_side2_label: AdvancedLabel::new(GuiElemCfg::default(), Vec2::new(0.0, 0.5), vec![]), | ||||||
|             c_buttons: PlayPause::new(GuiElemCfg::default()), |             c_buttons: PlayPause::new(GuiElemCfg::default()), | ||||||
|  |             c_buttons_custom_pos: false, | ||||||
|             cover_aspect_ratio: AnimationController::new( |             cover_aspect_ratio: AnimationController::new( | ||||||
|                 1.0, |                 1.0, | ||||||
|                 1.0, |                 1.0, | ||||||
| @ -78,11 +87,14 @@ impl IdleDisplay { | |||||||
|                 0.6, |                 0.6, | ||||||
|                 Instant::now(), |                 Instant::now(), | ||||||
|             ), |             ), | ||||||
|  |             cover_pos: None, | ||||||
|             cover_left: 0.01, |             cover_left: 0.01, | ||||||
|             cover_top: 0.21, |             cover_top: 0.21, | ||||||
|             cover_bottom, |             cover_bottom, | ||||||
|  |             artist_image_pos: None, | ||||||
|             artist_image_top: 0.5, |             artist_image_top: 0.5, | ||||||
|             artist_image_to_cover_margin: 0.01, |             artist_image_to_cover_margin: 0.01, | ||||||
|  |             force_reset_texts: false, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -108,7 +120,7 @@ impl GuiElem for IdleDisplay { | |||||||
|         ); |         ); | ||||||
|         // update current_info
 |         // update current_info
 | ||||||
|         self.current_info.update(info, g); |         self.current_info.update(info, g); | ||||||
|         if self.current_info.new_song { |         if self.current_info.new_song || self.force_reset_texts { | ||||||
|             self.current_info.new_song = false; |             self.current_info.new_song = false; | ||||||
|             self.c_top_label.content = if let Some(song) = self.current_info.current_song { |             self.c_top_label.content = if let Some(song) = self.current_info.current_song { | ||||||
|                 info.gui_config |                 info.gui_config | ||||||
| @ -207,6 +219,7 @@ impl GuiElem for IdleDisplay { | |||||||
|             image_display( |             image_display( | ||||||
|                 g, |                 g, | ||||||
|                 cover.as_ref(), |                 cover.as_ref(), | ||||||
|  |                 self.cover_pos.as_ref().map(|v| rect_from_rel(v, &info.pos)), | ||||||
|                 info.pos.top_left().x + info.pos.height() * self.cover_left, |                 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_top, | ||||||
|                 info.pos.top_left().y + info.pos.height() * self.cover_bottom, |                 info.pos.top_left().y + info.pos.height() * self.cover_bottom, | ||||||
| @ -220,6 +233,9 @@ impl GuiElem for IdleDisplay { | |||||||
|             image_display( |             image_display( | ||||||
|                 g, |                 g, | ||||||
|                 img.as_ref(), |                 img.as_ref(), | ||||||
|  |                 self.artist_image_pos | ||||||
|  |                     .as_ref() | ||||||
|  |                     .map(|v| rect_from_rel(v, &info.pos)), | ||||||
|                 get_right_x( |                 get_right_x( | ||||||
|                     info.pos.top_left().x + info.pos.height() * self.cover_left, |                     info.pos.top_left().x + info.pos.height() * self.cover_left, | ||||||
|                     top, |                     top, | ||||||
| @ -265,12 +281,14 @@ impl GuiElem for IdleDisplay { | |||||||
|             let buttons_right_pos = 0.99; |             let buttons_right_pos = 0.99; | ||||||
|             let buttons_width_max = info.pos.height() * 0.08 / 0.3 / info.pos.width(); |             let buttons_width_max = info.pos.height() * 0.08 / 0.3 / info.pos.width(); | ||||||
|             let buttons_width = buttons_width_max.min(0.2); |             let buttons_width = buttons_width_max.min(0.2); | ||||||
|  |             if !self.c_buttons_custom_pos { | ||||||
|                 self.c_buttons.config_mut().pos = Rectangle::from_tuples( |                 self.c_buttons.config_mut().pos = Rectangle::from_tuples( | ||||||
|                     (buttons_right_pos - buttons_width, 0.86), |                     (buttons_right_pos - buttons_width, 0.86), | ||||||
|                     (buttons_right_pos, 0.94), |                     (buttons_right_pos, 0.94), | ||||||
|                 ); |                 ); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |     } | ||||||
|     fn config(&self) -> &GuiElemCfg { |     fn config(&self) -> &GuiElemCfg { | ||||||
|         &self.config |         &self.config | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -123,6 +123,7 @@ impl LibraryBrowser { | |||||||
|             crate::gui_base::ScrollBoxSizeUnit::Pixels, |             crate::gui_base::ScrollBoxSizeUnit::Pixels, | ||||||
|             vec![], |             vec![], | ||||||
|             vec![], |             vec![], | ||||||
|  |             0.0, | ||||||
|         ); |         ); | ||||||
|         let (do_something_sender, do_something_receiver) = mpsc::channel(); |         let (do_something_sender, do_something_receiver) = mpsc::channel(); | ||||||
|         let search_settings_changed = Arc::new(AtomicBool::new(false)); |         let search_settings_changed = Arc::new(AtomicBool::new(false)); | ||||||
| @ -936,7 +937,7 @@ impl GuiElem for ListArtist { | |||||||
|                     let selected = self.selected.clone(); |                     let selected = self.selected.clone(); | ||||||
|                     info.actions.push(GuiAction::Do(Box::new(move |gui| { |                     info.actions.push(GuiAction::Do(Box::new(move |gui| { | ||||||
|                         let q = selected.as_queue( |                         let q = selected.as_queue( | ||||||
|                             &gui.gui.c_main_view.children.2, |                             &gui.gui.c_main_view.children.library_browser, | ||||||
|                             &gui.database.lock().unwrap(), |                             &gui.database.lock().unwrap(), | ||||||
|                         ); |                         ); | ||||||
|                         gui.exec_gui_action(GuiAction::SetDragging(Some(( |                         gui.exec_gui_action(GuiAction::SetDragging(Some(( | ||||||
| @ -1074,7 +1075,7 @@ impl GuiElem for ListAlbum { | |||||||
|                     let selected = self.selected.clone(); |                     let selected = self.selected.clone(); | ||||||
|                     info.actions.push(GuiAction::Do(Box::new(move |gui| { |                     info.actions.push(GuiAction::Do(Box::new(move |gui| { | ||||||
|                         let q = selected.as_queue( |                         let q = selected.as_queue( | ||||||
|                             &gui.gui.c_main_view.children.2, |                             &gui.gui.c_main_view.children.library_browser, | ||||||
|                             &gui.database.lock().unwrap(), |                             &gui.database.lock().unwrap(), | ||||||
|                         ); |                         ); | ||||||
|                         gui.exec_gui_action(GuiAction::SetDragging(Some(( |                         gui.exec_gui_action(GuiAction::SetDragging(Some(( | ||||||
| @ -1208,7 +1209,7 @@ impl GuiElem for ListSong { | |||||||
|                     let selected = self.selected.clone(); |                     let selected = self.selected.clone(); | ||||||
|                     info.actions.push(GuiAction::Do(Box::new(move |gui| { |                     info.actions.push(GuiAction::Do(Box::new(move |gui| { | ||||||
|                         let q = selected.as_queue( |                         let q = selected.as_queue( | ||||||
|                             &gui.gui.c_main_view.children.2, |                             &gui.gui.c_main_view.children.library_browser, | ||||||
|                             &gui.database.lock().unwrap(), |                             &gui.database.lock().unwrap(), | ||||||
|                         ); |                         ); | ||||||
|                         gui.exec_gui_action(GuiAction::SetDragging(Some(( |                         gui.exec_gui_action(GuiAction::SetDragging(Some(( | ||||||
| @ -1251,6 +1252,31 @@ impl GuiElem for ListSong { | |||||||
|         } |         } | ||||||
|         vec![] |         vec![] | ||||||
|     } |     } | ||||||
|  |     fn mouse_pressed(&mut self, button: MouseButton) -> Vec<GuiAction> { | ||||||
|  |         if button == MouseButton::Right { | ||||||
|  |             let id = self.id; | ||||||
|  |             vec![GuiAction::Build(Box::new(move |db| { | ||||||
|  |                 if let Some(me) = db.songs().get(&id) { | ||||||
|  |                     let me = me.clone(); | ||||||
|  |                     vec![GuiAction::ContextMenu(Some(vec![Box::new(Button::new( | ||||||
|  |                         GuiElemCfg::default(), | ||||||
|  |                         move |_| vec![GuiAction::EditSongs(vec![me.clone()])], | ||||||
|  |                         [Label::new( | ||||||
|  |                             GuiElemCfg::default(), | ||||||
|  |                             format!("Edit"), | ||||||
|  |                             Color::WHITE, | ||||||
|  |                             None, | ||||||
|  |                             Vec2::new_y(0.5), | ||||||
|  |                         )], | ||||||
|  |                     ))]))] | ||||||
|  |                 } else { | ||||||
|  |                     vec![] | ||||||
|  |                 } | ||||||
|  |             }))] | ||||||
|  |         } else { | ||||||
|  |             vec![] | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| struct FilterPanel { | struct FilterPanel { | ||||||
| @ -1484,24 +1510,28 @@ impl FilterPanel { | |||||||
|                 ), |                 ), | ||||||
|             ), |             ), | ||||||
|             vec![0.0; 10], |             vec![0.0; 10], | ||||||
|  |             0.0, | ||||||
|         ); |         ); | ||||||
|         let c_tab_filters_songs = ScrollBox::new( |         let c_tab_filters_songs = ScrollBox::new( | ||||||
|             GuiElemCfg::at(Rectangle::from_tuples((VSPLIT, HEIGHT), (1.0, 1.0))), |             GuiElemCfg::at(Rectangle::from_tuples((VSPLIT, HEIGHT), (1.0, 1.0))), | ||||||
|             crate::gui_base::ScrollBoxSizeUnit::Pixels, |             crate::gui_base::ScrollBoxSizeUnit::Pixels, | ||||||
|             FilterTab::default(), |             FilterTab::default(), | ||||||
|             vec![], |             vec![], | ||||||
|  |             0.0, | ||||||
|         ); |         ); | ||||||
|         let c_tab_filters_albums = ScrollBox::new( |         let c_tab_filters_albums = ScrollBox::new( | ||||||
|             GuiElemCfg::at(Rectangle::from_tuples((VSPLIT, HEIGHT), (1.0, 1.0))).disabled(), |             GuiElemCfg::at(Rectangle::from_tuples((VSPLIT, HEIGHT), (1.0, 1.0))).disabled(), | ||||||
|             crate::gui_base::ScrollBoxSizeUnit::Pixels, |             crate::gui_base::ScrollBoxSizeUnit::Pixels, | ||||||
|             FilterTab::default(), |             FilterTab::default(), | ||||||
|             vec![], |             vec![], | ||||||
|  |             0.0, | ||||||
|         ); |         ); | ||||||
|         let c_tab_filters_artists = ScrollBox::new( |         let c_tab_filters_artists = ScrollBox::new( | ||||||
|             GuiElemCfg::at(Rectangle::from_tuples((VSPLIT, HEIGHT), (1.0, 1.0))).disabled(), |             GuiElemCfg::at(Rectangle::from_tuples((VSPLIT, HEIGHT), (1.0, 1.0))).disabled(), | ||||||
|             crate::gui_base::ScrollBoxSizeUnit::Pixels, |             crate::gui_base::ScrollBoxSizeUnit::Pixels, | ||||||
|             FilterTab::default(), |             FilterTab::default(), | ||||||
|             vec![], |             vec![], | ||||||
|  |             0.0, | ||||||
|         ); |         ); | ||||||
|         let new_tab = Arc::new(AtomicUsize::new(0)); |         let new_tab = Arc::new(AtomicUsize::new(0)); | ||||||
|         let set_tab_1 = Arc::clone(&new_tab); |         let set_tab_1 = Arc::clone(&new_tab); | ||||||
|  | |||||||
| @ -147,6 +147,7 @@ impl CurrentInfo { | |||||||
| pub fn image_display( | pub fn image_display( | ||||||
|     g: &mut speedy2d::Graphics2D, |     g: &mut speedy2d::Graphics2D, | ||||||
|     img: Option<&ImageHandle>, |     img: Option<&ImageHandle>, | ||||||
|  |     pos: Option<Rectangle>, | ||||||
|     left: f32, |     left: f32, | ||||||
|     top: f32, |     top: f32, | ||||||
|     bottom: f32, |     bottom: f32, | ||||||
| @ -155,8 +156,12 @@ pub fn image_display( | |||||||
|     if let Some(cover) = &img { |     if let Some(cover) = &img { | ||||||
|         let cover_size = cover.size(); |         let cover_size = cover.size(); | ||||||
|         aspect_ratio.target = if cover_size.x > 0 && cover_size.y > 0 { |         aspect_ratio.target = if cover_size.x > 0 && cover_size.y > 0 { | ||||||
|  |             let pos = if let Some(pos) = pos { | ||||||
|  |                 pos | ||||||
|  |             } else { | ||||||
|                 let right_x = get_right_x(left, top, bottom, aspect_ratio.value); |                 let right_x = get_right_x(left, top, bottom, aspect_ratio.value); | ||||||
|             let pos = Rectangle::from_tuples((left, top), (right_x, bottom)); |                 Rectangle::from_tuples((left, top), (right_x, bottom)) | ||||||
|  |             }; | ||||||
|             let aspect_ratio = cover_size.x as f32 / cover_size.y as f32; |             let aspect_ratio = cover_size.x as f32 / cover_size.y as f32; | ||||||
|             g.draw_rectangle_image(pos, cover); |             g.draw_rectangle_image(pos, cover); | ||||||
|             aspect_ratio |             aspect_ratio | ||||||
|  | |||||||
| @ -96,6 +96,7 @@ impl QueueViewer { | |||||||
|                 crate::gui_base::ScrollBoxSizeUnit::Pixels, |                 crate::gui_base::ScrollBoxSizeUnit::Pixels, | ||||||
|                 vec![], |                 vec![], | ||||||
|                 vec![], |                 vec![], | ||||||
|  |                 0.0, | ||||||
|             ), |             ), | ||||||
|             c_empty_space_drag_handler: QueueEmptySpaceDragHandler::new(GuiElemCfg::at( |             c_empty_space_drag_handler: QueueEmptySpaceDragHandler::new(GuiElemCfg::at( | ||||||
|                 Rectangle::from_tuples((0.0, QP_QUEUE1), (1.0, QP_QUEUE2)), |                 Rectangle::from_tuples((0.0, QP_QUEUE1), (1.0, QP_QUEUE2)), | ||||||
|  | |||||||
| @ -4,9 +4,10 @@ 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::{DrawInfo, GuiAction, GuiElem, GuiElemCfg}, |     gui::{DrawInfo, GuiAction, GuiElem, GuiElemCfg, GuiElemChildren}, | ||||||
|     gui_anim::AnimationController, |     gui_anim::AnimationController, | ||||||
|     gui_base::{Button, Panel}, |     gui_base::{Button, Panel}, | ||||||
|  |     gui_edit_song::EditorForSongs, | ||||||
|     gui_idle_display::IdleDisplay, |     gui_idle_display::IdleDisplay, | ||||||
|     gui_library::LibraryBrowser, |     gui_library::LibraryBrowser, | ||||||
|     gui_notif::NotifOverlay, |     gui_notif::NotifOverlay, | ||||||
| @ -37,17 +38,12 @@ pub fn transition(p: f32) -> f32 { | |||||||
| 
 | 
 | ||||||
| pub struct GuiScreen { | pub struct GuiScreen { | ||||||
|     config: GuiElemCfg, |     config: GuiElemCfg, | ||||||
|     c_notif_overlay: NotifOverlay, |     pub c_notif_overlay: NotifOverlay, | ||||||
|     c_idle_display: IdleDisplay, |     pub c_idle_display: IdleDisplay, | ||||||
|     c_status_bar: StatusBar, |     pub c_editing_songs: Option<EditorForSongs>, | ||||||
|  |     pub c_status_bar: StatusBar, | ||||||
|     pub c_settings: Settings, |     pub c_settings: Settings, | ||||||
|     pub c_main_view: Panel<( |     pub c_main_view: Panel<MainView>, | ||||||
|         Button<[Label; 1]>, |  | ||||||
|         Button<[Label; 1]>, |  | ||||||
|         LibraryBrowser, |  | ||||||
|         QueueViewer, |  | ||||||
|         Button<[Label; 1]>, |  | ||||||
|     )>, |  | ||||||
|     pub c_context_menu: Option<Box<dyn GuiElem>>, |     pub c_context_menu: Option<Box<dyn GuiElem>>, | ||||||
|     pub idle: AnimationController<f32>, |     pub idle: AnimationController<f32>, | ||||||
|     // pub settings: (bool, Option<Instant>),
 |     // pub settings: (bool, Option<Instant>),
 | ||||||
| @ -57,6 +53,30 @@ pub struct GuiScreen { | |||||||
|     pub prev_mouse_pos: Vec2, |     pub prev_mouse_pos: Vec2, | ||||||
|     pub hotkey: Hotkey, |     pub hotkey: Hotkey, | ||||||
| } | } | ||||||
|  | pub struct MainView { | ||||||
|  |     pub button_clear_queue: Button<[Label; 1]>, | ||||||
|  |     pub button_settings: Button<[Label; 1]>, | ||||||
|  |     pub button_exit: Button<[Label; 1]>, | ||||||
|  |     pub library_browser: LibraryBrowser, | ||||||
|  |     pub queue_viewer: QueueViewer, | ||||||
|  | } | ||||||
|  | impl GuiElemChildren for MainView { | ||||||
|  |     fn iter(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> { | ||||||
|  |         Box::new( | ||||||
|  |             [ | ||||||
|  |                 self.button_clear_queue.elem_mut(), | ||||||
|  |                 self.button_settings.elem_mut(), | ||||||
|  |                 self.button_exit.elem_mut(), | ||||||
|  |                 self.library_browser.elem_mut(), | ||||||
|  |                 self.queue_viewer.elem_mut(), | ||||||
|  |             ] | ||||||
|  |             .into_iter(), | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  |     fn len(&self) -> usize { | ||||||
|  |         5 | ||||||
|  |     } | ||||||
|  | } | ||||||
| impl GuiScreen { | impl GuiScreen { | ||||||
|     pub fn new( |     pub fn new( | ||||||
|         config: GuiElemCfg, |         config: GuiElemCfg, | ||||||
| @ -74,6 +94,7 @@ impl GuiScreen { | |||||||
|                 (0.0, 0.9), |                 (0.0, 0.9), | ||||||
|                 (1.0, 1.0), |                 (1.0, 1.0), | ||||||
|             ))), |             ))), | ||||||
|  |             c_editing_songs: None, | ||||||
|             c_idle_display: IdleDisplay::new(GuiElemCfg::default().disabled()), |             c_idle_display: IdleDisplay::new(GuiElemCfg::default().disabled()), | ||||||
|             c_settings: Settings::new( |             c_settings: Settings::new( | ||||||
|                 GuiElemCfg::default().disabled(), |                 GuiElemCfg::default().disabled(), | ||||||
| @ -85,38 +106,8 @@ impl GuiScreen { | |||||||
|             ), |             ), | ||||||
|             c_main_view: Panel::new( |             c_main_view: Panel::new( | ||||||
|                 GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (1.0, 0.9))), |                 GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (1.0, 0.9))), | ||||||
|                 ( |                 MainView { | ||||||
|                     Button::new( |                     button_clear_queue: Button::new( | ||||||
|                         GuiElemCfg::at(Rectangle::from_tuples((0.75, 0.0), (0.875, 0.03))), |  | ||||||
|                         |_| vec![GuiAction::OpenSettings(true)], |  | ||||||
|                         [Label::new( |  | ||||||
|                             GuiElemCfg::default(), |  | ||||||
|                             "Settings".to_string(), |  | ||||||
|                             Color::WHITE, |  | ||||||
|                             None, |  | ||||||
|                             Vec2::new(0.5, 0.5), |  | ||||||
|                         )], |  | ||||||
|                     ), |  | ||||||
|                     Button::new( |  | ||||||
|                         GuiElemCfg::at(Rectangle::from_tuples((0.875, 0.0), (1.0, 0.03))), |  | ||||||
|                         |_| vec![GuiAction::Exit], |  | ||||||
|                         [Label::new( |  | ||||||
|                             GuiElemCfg::default(), |  | ||||||
|                             "Exit".to_string(), |  | ||||||
|                             Color::WHITE, |  | ||||||
|                             None, |  | ||||||
|                             Vec2::new(0.5, 0.5), |  | ||||||
|                         )], |  | ||||||
|                     ), |  | ||||||
|                     LibraryBrowser::new(GuiElemCfg::at(Rectangle::from_tuples( |  | ||||||
|                         (0.0, 0.0), |  | ||||||
|                         (0.5, 1.0), |  | ||||||
|                     ))), |  | ||||||
|                     QueueViewer::new(GuiElemCfg::at(Rectangle::from_tuples( |  | ||||||
|                         (0.5, 0.03), |  | ||||||
|                         (1.0, 1.0), |  | ||||||
|                     ))), |  | ||||||
|                     Button::new( |  | ||||||
|                         GuiElemCfg::at(Rectangle::from_tuples((0.5, 0.0), (0.75, 0.03))), |                         GuiElemCfg::at(Rectangle::from_tuples((0.5, 0.0), (0.75, 0.03))), | ||||||
|                         |_| { |                         |_| { | ||||||
|                             vec![GuiAction::SendToServer( |                             vec![GuiAction::SendToServer( | ||||||
| @ -139,7 +130,37 @@ impl GuiScreen { | |||||||
|                             Vec2::new(0.5, 0.5), |                             Vec2::new(0.5, 0.5), | ||||||
|                         )], |                         )], | ||||||
|                     ), |                     ), | ||||||
|  |                     button_settings: Button::new( | ||||||
|  |                         GuiElemCfg::at(Rectangle::from_tuples((0.75, 0.0), (0.875, 0.03))), | ||||||
|  |                         |_| vec![GuiAction::OpenSettings(true)], | ||||||
|  |                         [Label::new( | ||||||
|  |                             GuiElemCfg::default(), | ||||||
|  |                             "Settings".to_string(), | ||||||
|  |                             Color::WHITE, | ||||||
|  |                             None, | ||||||
|  |                             Vec2::new(0.5, 0.5), | ||||||
|  |                         )], | ||||||
|                     ), |                     ), | ||||||
|  |                     button_exit: Button::new( | ||||||
|  |                         GuiElemCfg::at(Rectangle::from_tuples((0.875, 0.0), (1.0, 0.03))), | ||||||
|  |                         |_| vec![GuiAction::Exit], | ||||||
|  |                         [Label::new( | ||||||
|  |                             GuiElemCfg::default(), | ||||||
|  |                             "Exit".to_string(), | ||||||
|  |                             Color::WHITE, | ||||||
|  |                             None, | ||||||
|  |                             Vec2::new(0.5, 0.5), | ||||||
|  |                         )], | ||||||
|  |                     ), | ||||||
|  |                     library_browser: LibraryBrowser::new(GuiElemCfg::at(Rectangle::from_tuples( | ||||||
|  |                         (0.0, 0.0), | ||||||
|  |                         (0.5, 1.0), | ||||||
|  |                     ))), | ||||||
|  |                     queue_viewer: QueueViewer::new(GuiElemCfg::at(Rectangle::from_tuples( | ||||||
|  |                         (0.5, 0.03), | ||||||
|  |                         (1.0, 1.0), | ||||||
|  |                     ))), | ||||||
|  |                 }, | ||||||
|             ), |             ), | ||||||
|             c_context_menu: None, |             c_context_menu: None, | ||||||
|             hotkey: Hotkey::new_noshift(VirtualKeyCode::Escape), |             hotkey: Hotkey::new_noshift(VirtualKeyCode::Escape), | ||||||
| @ -173,6 +194,9 @@ impl GuiScreen { | |||||||
|             0.0 |             0.0 | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |     pub fn force_idle(&mut self) { | ||||||
|  |         self.idle.target = 1.0; | ||||||
|  |     } | ||||||
|     pub fn not_idle(&mut self) { |     pub fn not_idle(&mut self) { | ||||||
|         self.last_interaction = Instant::now(); |         self.last_interaction = Instant::now(); | ||||||
|         if self.idle.target > 0.0 { |         if self.idle.target > 0.0 { | ||||||
| @ -197,6 +221,12 @@ impl GuiScreen { | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     pub fn set_normal_ui_enabled(&mut self, enabled: bool) { | ||||||
|  |         self.c_status_bar.config_mut().enabled = enabled; | ||||||
|  |         // self.c_settings.config_mut().enabled = enabled;
 | ||||||
|  |         self.c_main_view.config_mut().enabled = enabled; | ||||||
|  |     } | ||||||
| } | } | ||||||
| impl GuiElem for GuiScreen { | impl GuiElem for GuiScreen { | ||||||
|     fn config(&self) -> &GuiElemCfg { |     fn config(&self) -> &GuiElemCfg { | ||||||
| @ -207,15 +237,19 @@ impl GuiElem for GuiScreen { | |||||||
|     } |     } | ||||||
|     fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> { |     fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> { | ||||||
|         Box::new( |         Box::new( | ||||||
|  |             self.c_context_menu.iter_mut().map(|v| v.elem_mut()).chain( | ||||||
|                 [ |                 [ | ||||||
|                     self.c_notif_overlay.elem_mut(), |                     self.c_notif_overlay.elem_mut(), | ||||||
|                     self.c_idle_display.elem_mut(), |                     self.c_idle_display.elem_mut(), | ||||||
|  |                 ] | ||||||
|  |                 .into_iter() | ||||||
|  |                 .chain(self.c_editing_songs.as_mut().map(|v| v.elem_mut())) | ||||||
|  |                 .chain([ | ||||||
|                     self.c_status_bar.elem_mut(), |                     self.c_status_bar.elem_mut(), | ||||||
|                     self.c_settings.elem_mut(), |                     self.c_settings.elem_mut(), | ||||||
|                     self.c_main_view.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 { | ||||||
| @ -301,9 +335,7 @@ impl GuiElem for GuiScreen { | |||||||
|         // animations: idle
 |         // animations: idle
 | ||||||
|         if idle_changed { |         if idle_changed { | ||||||
|             let enable_normal_ui = self.idle.value < 1.0; |             let enable_normal_ui = self.idle.value < 1.0; | ||||||
|             self.c_main_view.config_mut().enabled = enable_normal_ui; |             self.set_normal_ui_enabled(enable_normal_ui); | ||||||
|             // self.c_settings.config_mut().enabled = enable_normal_ui;
 |  | ||||||
|             self.c_status_bar.config_mut().enabled = enable_normal_ui; |  | ||||||
|             if let Some(h) = &info.helper { |             if let Some(h) = &info.helper { | ||||||
|                 h.set_cursor_visible(enable_normal_ui); |                 h.set_cursor_visible(enable_normal_ui); | ||||||
|             } |             } | ||||||
|  | |||||||
| @ -34,6 +34,7 @@ impl Settings { | |||||||
|                     scroll_sensitivity_pages, |                     scroll_sensitivity_pages, | ||||||
|                 ), |                 ), | ||||||
|                 vec![], |                 vec![], | ||||||
|  |                 0.0, | ||||||
|             ), |             ), | ||||||
|             c_background: Panel::with_background(GuiElemCfg::default().w_mouse(), (), Color::BLACK), |             c_background: Panel::with_background(GuiElemCfg::default().w_mouse(), (), Color::BLACK), | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -1,11 +1,10 @@ | |||||||
| use std::time::Instant; | use std::time::Instant; | ||||||
| 
 | 
 | ||||||
| use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle}; | use speedy2d::{dimen::Vec2, shape::Rectangle}; | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::{ | ||||||
|     gui::{DrawInfo, GuiElem, GuiElemCfg}, |     gui::{DrawInfo, GuiElem, GuiElemCfg}, | ||||||
|     gui_anim::AnimationController, |     gui_anim::AnimationController, | ||||||
|     gui_base::Panel, |  | ||||||
|     gui_playback::{image_display, CurrentInfo}, |     gui_playback::{image_display, CurrentInfo}, | ||||||
|     gui_playpause::PlayPause, |     gui_playpause::PlayPause, | ||||||
|     gui_text::AdvancedLabel, |     gui_text::AdvancedLabel, | ||||||
| @ -17,6 +16,7 @@ pub struct StatusBar { | |||||||
|     current_info: CurrentInfo, |     current_info: CurrentInfo, | ||||||
|     cover_aspect_ratio: AnimationController<f32>, |     cover_aspect_ratio: AnimationController<f32>, | ||||||
|     c_song_label: AdvancedLabel, |     c_song_label: AdvancedLabel, | ||||||
|  |     pub force_reset_texts: bool, | ||||||
|     c_buttons: PlayPause, |     c_buttons: PlayPause, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -36,6 +36,7 @@ impl StatusBar { | |||||||
|                 Instant::now(), |                 Instant::now(), | ||||||
|             ), |             ), | ||||||
|             c_song_label: AdvancedLabel::new(GuiElemCfg::default(), Vec2::new(0.0, 0.5), vec![]), |             c_song_label: AdvancedLabel::new(GuiElemCfg::default(), Vec2::new(0.0, 0.5), vec![]), | ||||||
|  |             force_reset_texts: false, | ||||||
|             c_buttons: PlayPause::new(GuiElemCfg::default()), |             c_buttons: PlayPause::new(GuiElemCfg::default()), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @ -47,7 +48,7 @@ impl GuiElem for StatusBar { | |||||||
|     } |     } | ||||||
|     fn draw(&mut self, info: &mut DrawInfo, g: &mut speedy2d::Graphics2D) { |     fn draw(&mut self, info: &mut DrawInfo, g: &mut speedy2d::Graphics2D) { | ||||||
|         self.current_info.update(info, g); |         self.current_info.update(info, g); | ||||||
|         if self.current_info.new_song { |         if self.current_info.new_song || self.force_reset_texts { | ||||||
|             self.current_info.new_song = false; |             self.current_info.new_song = false; | ||||||
|             self.c_song_label.content = if let Some(song) = self.current_info.current_song { |             self.c_song_label.content = if let Some(song) = self.current_info.current_song { | ||||||
|                 info.gui_config |                 info.gui_config | ||||||
| @ -101,6 +102,7 @@ impl GuiElem for StatusBar { | |||||||
|             image_display( |             image_display( | ||||||
|                 g, |                 g, | ||||||
|                 cover.as_ref(), |                 cover.as_ref(), | ||||||
|  |                 None, | ||||||
|                 info.pos.top_left().x + info.pos.height() * 0.05, |                 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.05, | ||||||
|                 info.pos.top_left().y + info.pos.height() * 0.95, |                 info.pos.top_left().y + info.pos.height() * 0.95, | ||||||
|  | |||||||
| @ -10,21 +10,24 @@ use std::{ | |||||||
| use clap::{Parser, Subcommand}; | use clap::{Parser, Subcommand}; | ||||||
| #[cfg(feature = "speedy2d")] | #[cfg(feature = "speedy2d")] | ||||||
| use gui::GuiEvent; | use gui::GuiEvent; | ||||||
|  | #[cfg(feature = "playback")] | ||||||
|  | use musicdb_lib::player::Player; | ||||||
| use musicdb_lib::{ | use musicdb_lib::{ | ||||||
|     data::{ |     data::{ | ||||||
|         database::{ClientIo, Database}, |         database::{ClientIo, Database}, | ||||||
|         CoverId, SongId, |         CoverId, SongId, | ||||||
|     }, |     }, | ||||||
|     load::ToFromBytes, |     load::ToFromBytes, | ||||||
|     player::Player, |  | ||||||
|     server::{get, Command}, |     server::{get, Command}, | ||||||
| }; | }; | ||||||
|  | use speedy2d::color::Color; | ||||||
| #[cfg(feature = "speedy2d")] | #[cfg(feature = "speedy2d")] | ||||||
| mod gui; | mod gui; | ||||||
| #[cfg(feature = "speedy2d")] | #[cfg(feature = "speedy2d")] | ||||||
| mod gui_anim; | mod gui_anim; | ||||||
| #[cfg(feature = "speedy2d")] | #[cfg(feature = "speedy2d")] | ||||||
| mod gui_base; | mod gui_base; | ||||||
|  | mod gui_edit_song; | ||||||
| #[cfg(feature = "speedy2d")] | #[cfg(feature = "speedy2d")] | ||||||
| mod gui_idle_display; | mod gui_idle_display; | ||||||
| #[cfg(feature = "speedy2d")] | #[cfg(feature = "speedy2d")] | ||||||
| @ -47,6 +50,8 @@ mod gui_statusbar; | |||||||
| mod gui_text; | mod gui_text; | ||||||
| #[cfg(feature = "speedy2d")] | #[cfg(feature = "speedy2d")] | ||||||
| mod gui_wrappers; | mod gui_wrappers; | ||||||
|  | #[cfg(feature = "merscfg")] | ||||||
|  | mod merscfg; | ||||||
| #[cfg(feature = "speedy2d")] | #[cfg(feature = "speedy2d")] | ||||||
| mod textcfg; | mod textcfg; | ||||||
| 
 | 
 | ||||||
| @ -65,8 +70,10 @@ enum Mode { | |||||||
|     /// graphical user interface
 |     /// graphical user interface
 | ||||||
|     Gui, |     Gui, | ||||||
|     /// play in sync with the server, but load the songs from a local copy of the lib-dir
 |     /// play in sync with the server, but load the songs from a local copy of the lib-dir
 | ||||||
|  |     #[cfg(feature = "playback")] | ||||||
|     SyncplayerLocal { lib_dir: PathBuf }, |     SyncplayerLocal { lib_dir: PathBuf }, | ||||||
|     /// play in sync with the server, and fetch the songs from it too. slower than the local variant for obvious reasons
 |     /// play in sync with the server, and fetch the songs from it too. slower than the local variant for obvious reasons
 | ||||||
|  |     #[cfg(feature = "playback")] | ||||||
|     SyncplayerNetwork, |     SyncplayerNetwork, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -97,23 +104,31 @@ fn main() { | |||||||
|         let mut con = con.try_clone().unwrap(); |         let mut con = con.try_clone().unwrap(); | ||||||
|         // this is all you need to keep the db in sync
 |         // this is all you need to keep the db in sync
 | ||||||
|         thread::spawn(move || { |         thread::spawn(move || { | ||||||
|  |             #[cfg(feature = "playback")] | ||||||
|             let mut player = |             let mut player = | ||||||
|                 if matches!(mode, Mode::SyncplayerLocal { .. } | Mode::SyncplayerNetwork) { |                 if matches!(mode, Mode::SyncplayerLocal { .. } | Mode::SyncplayerNetwork) { | ||||||
|                     Some(Player::new().unwrap().without_sending_commands()) |                     Some(Player::new().unwrap().without_sending_commands()) | ||||||
|                 } else { |                 } else { | ||||||
|                     None |                     None | ||||||
|                 }; |                 }; | ||||||
|  |             #[allow(unused_labels)] | ||||||
|  |             'ifstatementworkaround: { | ||||||
|  |                 // use if+break instead of if-else because we can't #[cfg(feature)] the if statement,
 | ||||||
|  |                 // since we want the else part to run if the feature is disabled
 | ||||||
|  |                 #[cfg(feature = "playback")] | ||||||
|                 if let Mode::SyncplayerLocal { lib_dir } = mode { |                 if let Mode::SyncplayerLocal { lib_dir } = mode { | ||||||
|                     let mut db = database.lock().unwrap(); |                     let mut db = database.lock().unwrap(); | ||||||
|                     db.lib_directory = lib_dir; |                     db.lib_directory = lib_dir; | ||||||
|             } else { |                     break 'ifstatementworkaround; | ||||||
|  |                 } | ||||||
|                 let mut db = database.lock().unwrap(); |                 let mut db = database.lock().unwrap(); | ||||||
|                 let client_con: Box<dyn ClientIo> = Box::new(TcpStream::connect(addr).unwrap()); |                 let client_con: Box<dyn ClientIo> = Box::new(TcpStream::connect(addr).unwrap()); | ||||||
|                 db.remote_server_as_song_file_source = Some(Arc::new(Mutex::new( |                 db.remote_server_as_song_file_source = Some(Arc::new(Mutex::new( | ||||||
|                     musicdb_lib::server::get::Client::new(BufReader::new(client_con)).unwrap(), |                     musicdb_lib::server::get::Client::new(BufReader::new(client_con)).unwrap(), | ||||||
|                 ))); |                 ))); | ||||||
|             }; |             } | ||||||
|             loop { |             loop { | ||||||
|  |                 #[cfg(feature = "playback")] | ||||||
|                 if let Some(player) = &mut player { |                 if let Some(player) = &mut player { | ||||||
|                     let mut db = database.lock().unwrap(); |                     let mut db = database.lock().unwrap(); | ||||||
|                     if db.is_client_init() { |                     if db.is_client_init() { | ||||||
| @ -121,6 +136,7 @@ fn main() { | |||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|                 let update = Command::from_bytes(&mut con).unwrap(); |                 let update = Command::from_bytes(&mut con).unwrap(); | ||||||
|  |                 #[cfg(feature = "playback")] | ||||||
|                 if let Some(player) = &mut player { |                 if let Some(player) = &mut player { | ||||||
|                     player.handle_command(&update); |                     player.handle_command(&update); | ||||||
|                 } |                 } | ||||||
| @ -154,6 +170,7 @@ fn main() { | |||||||
|                 ) |                 ) | ||||||
|             }; |             }; | ||||||
|         } |         } | ||||||
|  |         #[cfg(feature = "playback")] | ||||||
|         Mode::SyncplayerLocal { .. } | Mode::SyncplayerNetwork => { |         Mode::SyncplayerLocal { .. } | Mode::SyncplayerNetwork => { | ||||||
|             con_thread.join().unwrap(); |             con_thread.join().unwrap(); | ||||||
|         } |         } | ||||||
| @ -180,3 +197,7 @@ fn get_cover(song: SongId, database: &Database) -> Option<CoverId> { | |||||||
|         database.albums().get(song.album.as_ref()?)?.cover |         database.albums().get(song.album.as_ref()?)?.cover | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | pub(crate) fn color_scale(c: Color, r: f32, g: f32, b: f32, new_alpha: Option<f32>) -> Color { | ||||||
|  |     Color::from_rgba(c.r() * r, c.g() * g, c.b() * b, new_alpha.unwrap_or(c.a())) | ||||||
|  | } | ||||||
|  | |||||||
							
								
								
									
										849
									
								
								musicdb-client/src/merscfg.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										849
									
								
								musicdb-client/src/merscfg.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,849 @@ | |||||||
|  | use std::{ | ||||||
|  |     path::PathBuf, | ||||||
|  |     sync::{ | ||||||
|  |         atomic::{AtomicBool, AtomicU8}, | ||||||
|  |         mpsc::Sender, | ||||||
|  |         Arc, Mutex, RwLock, | ||||||
|  |     }, | ||||||
|  |     time::Duration, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | use mers_lib::{ | ||||||
|  |     data::{Data, MersType, Type}, | ||||||
|  |     errors::CheckError, | ||||||
|  |     prelude_compile::CompInfo, | ||||||
|  | }; | ||||||
|  | use musicdb_lib::{data::database::Database, server::Command}; | ||||||
|  | use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle, window::UserEventSender}; | ||||||
|  | 
 | ||||||
|  | use crate::{ | ||||||
|  |     gui::{Gui, GuiAction, GuiConfig, GuiElem, GuiElemCfg, GuiEvent}, | ||||||
|  |     gui_base::Panel, | ||||||
|  |     gui_notif::{NotifInfo, NotifOverlay}, | ||||||
|  |     gui_text::Label, | ||||||
|  |     textcfg::TextBuilder, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | pub struct OptFunc(Option<mers_lib::data::function::Function>); | ||||||
|  | impl OptFunc { | ||||||
|  |     pub fn none() -> Self { | ||||||
|  |         Self(None) | ||||||
|  |     } | ||||||
|  |     pub fn some(func: mers_lib::data::function::Function) -> Self { | ||||||
|  |         Self(Some(func)) | ||||||
|  |     } | ||||||
|  |     fn run(&self) { | ||||||
|  |         if let Some(func) = &self.0 { | ||||||
|  |             func.run(Data::empty_tuple()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// mers code must return an object `{}` with hook functions.
 | ||||||
|  | /// All hook functions will be called with `()` as their argument,
 | ||||||
|  | /// and their return value will be ignored.
 | ||||||
|  | ///
 | ||||||
|  | /// Values:
 | ||||||
|  | /// - `is_playing`
 | ||||||
|  | /// - `is_idle`
 | ||||||
|  | /// - `window_size_in_pixels`
 | ||||||
|  | /// - `idle_screen_cover_aspect_ratio`
 | ||||||
|  | ///
 | ||||||
|  | /// Functions:
 | ||||||
|  | /// - `idle_start`
 | ||||||
|  | /// - `idle_stop`
 | ||||||
|  | /// - `idle_prevent`
 | ||||||
|  | /// - `send_notification`
 | ||||||
|  | /// - `set_idle_screen_cover_pos`
 | ||||||
|  | /// - `set_idle_screen_artist_image_pos`
 | ||||||
|  | /// - `set_idle_screen_top_text_pos`
 | ||||||
|  | /// - `set_idle_screen_side_text_1_pos`
 | ||||||
|  | /// - `set_idle_screen_side_text_2_pos`
 | ||||||
|  | /// - `set_statusbar_text_format`
 | ||||||
|  | /// - `set_idle_screen_top_text_format`
 | ||||||
|  | /// - `set_idle_screen_side_text_1_format`
 | ||||||
|  | /// - `set_idle_screen_side_text_2_format`
 | ||||||
|  | pub struct MersCfg { | ||||||
|  |     pub source_file: PathBuf, | ||||||
|  |     // - - handler functions - -
 | ||||||
|  |     pub func_before_draw: OptFunc, | ||||||
|  |     pub func_library_updated: OptFunc, | ||||||
|  |     pub func_queue_updated: OptFunc, | ||||||
|  |     // - - globals that aren't functions - -
 | ||||||
|  |     pub var_is_playing: Arc<RwLock<Data>>, | ||||||
|  |     pub var_is_idle: Arc<RwLock<Data>>, | ||||||
|  |     pub var_window_size_in_pixels: Arc<RwLock<Data>>, | ||||||
|  |     pub var_idle_screen_cover_aspect_ratio: Arc<RwLock<Data>>, | ||||||
|  |     // - - results from running functions - -
 | ||||||
|  |     pub updated_playing_status: Arc<AtomicU8>, | ||||||
|  |     pub updated_idle_status: Arc<AtomicU8>, | ||||||
|  |     pub updated_idle_screen_cover_pos: Arc<Updatable<Option<Rectangle>>>, | ||||||
|  |     pub updated_idle_screen_artist_image_pos: Arc<Updatable<Option<Rectangle>>>, | ||||||
|  |     pub updated_idle_screen_top_text_pos: Arc<Updatable<Rectangle>>, | ||||||
|  |     pub updated_idle_screen_side_text_1_pos: Arc<Updatable<Rectangle>>, | ||||||
|  |     pub updated_idle_screen_side_text_2_pos: Arc<Updatable<Rectangle>>, | ||||||
|  |     pub updated_idle_screen_playback_buttons_pos: Arc<Updatable<Rectangle>>, | ||||||
|  |     pub updated_statusbar_text_format: Arc<Updatable<TextBuilder>>, | ||||||
|  |     pub updated_idle_screen_top_text_format: Arc<Updatable<TextBuilder>>, | ||||||
|  |     pub updated_idle_screen_side_text_1_format: Arc<Updatable<TextBuilder>>, | ||||||
|  |     pub updated_idle_screen_side_text_2_format: Arc<Updatable<TextBuilder>>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl MersCfg { | ||||||
|  |     pub fn new(path: PathBuf) -> Self { | ||||||
|  |         Self { | ||||||
|  |             source_file: path, | ||||||
|  | 
 | ||||||
|  |             func_before_draw: OptFunc::none(), | ||||||
|  |             func_library_updated: OptFunc::none(), | ||||||
|  |             func_queue_updated: OptFunc::none(), | ||||||
|  | 
 | ||||||
|  |             var_is_playing: Arc::new(RwLock::new(Data::new(mers_lib::data::bool::Bool(false)))), | ||||||
|  |             var_is_idle: Arc::new(RwLock::new(Data::new(mers_lib::data::bool::Bool(false)))), | ||||||
|  |             var_window_size_in_pixels: Arc::new(RwLock::new(Data::new( | ||||||
|  |                 mers_lib::data::tuple::Tuple(vec![ | ||||||
|  |                     Data::new(mers_lib::data::int::Int(0)), | ||||||
|  |                     Data::new(mers_lib::data::int::Int(0)), | ||||||
|  |                 ]), | ||||||
|  |             ))), | ||||||
|  |             var_idle_screen_cover_aspect_ratio: Arc::new(RwLock::new(Data::new( | ||||||
|  |                 mers_lib::data::float::Float(0.0), | ||||||
|  |             ))), | ||||||
|  | 
 | ||||||
|  |             updated_playing_status: Arc::new(AtomicU8::new(0)), | ||||||
|  |             updated_idle_status: Arc::new(AtomicU8::new(0)), | ||||||
|  |             updated_idle_screen_cover_pos: Arc::new(Updatable::new()), | ||||||
|  |             updated_idle_screen_artist_image_pos: Arc::new(Updatable::new()), | ||||||
|  |             updated_idle_screen_top_text_pos: Arc::new(Updatable::new()), | ||||||
|  |             updated_idle_screen_side_text_1_pos: Arc::new(Updatable::new()), | ||||||
|  |             updated_idle_screen_side_text_2_pos: Arc::new(Updatable::new()), | ||||||
|  |             updated_idle_screen_playback_buttons_pos: Arc::new(Updatable::new()), | ||||||
|  |             updated_statusbar_text_format: Arc::new(Updatable::new()), | ||||||
|  |             updated_idle_screen_top_text_format: Arc::new(Updatable::new()), | ||||||
|  |             updated_idle_screen_side_text_1_format: Arc::new(Updatable::new()), | ||||||
|  |             updated_idle_screen_side_text_2_format: Arc::new(Updatable::new()), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     fn custom_globals( | ||||||
|  |         &self, | ||||||
|  |         cfg: mers_lib::prelude_extend_config::Config, | ||||||
|  |         event_sender: Arc<UserEventSender<GuiEvent>>, | ||||||
|  |         notif_sender: Sender< | ||||||
|  |             Box<dyn FnOnce(&NotifOverlay) -> (Box<dyn GuiElem>, NotifInfo) + Send>, | ||||||
|  |         >, | ||||||
|  |     ) -> mers_lib::prelude_extend_config::Config { | ||||||
|  |         cfg.add_var_arc( | ||||||
|  |             "is_playing".to_owned(), | ||||||
|  |             Arc::clone(&self.var_is_playing), | ||||||
|  |             self.var_is_playing.read().unwrap().get().as_type(), | ||||||
|  |         ) | ||||||
|  |         .add_var_arc( | ||||||
|  |             "is_idle".to_owned(), | ||||||
|  |             Arc::clone(&self.var_is_idle), | ||||||
|  |             self.var_is_idle.read().unwrap().get().as_type(), | ||||||
|  |         ) | ||||||
|  |         .add_var_arc( | ||||||
|  |             "window_size_in_pixels".to_owned(), | ||||||
|  |             Arc::clone(&self.var_window_size_in_pixels), | ||||||
|  |             self.var_window_size_in_pixels.read().unwrap().get().as_type(), | ||||||
|  |         ) | ||||||
|  |         .add_var_arc( | ||||||
|  |             "idle_screen_cover_aspect_ratio".to_owned(), | ||||||
|  |             Arc::clone(&self.var_idle_screen_cover_aspect_ratio), | ||||||
|  |             self.var_idle_screen_cover_aspect_ratio.read().unwrap().get().as_type(), | ||||||
|  |         ) | ||||||
|  |         .add_var("playback_resume".to_owned(),{ | ||||||
|  |             let es = event_sender.clone(); | ||||||
|  |             let v = Arc::clone(&self.updated_playing_status); | ||||||
|  |             Data::new(mers_lib::data::function::Function { | ||||||
|  |                 info: Arc::new(mers_lib::info::Info::neverused()), | ||||||
|  |                 info_check: Arc::new(Mutex::new(mers_lib::info::Info::neverused())), | ||||||
|  |                 out: Arc::new(|a, _| { | ||||||
|  |                     if a.is_zero_tuple() { | ||||||
|  |                         Ok(Type::empty_tuple()) | ||||||
|  |                     } else { | ||||||
|  |                         Err(format!("Can't call `playback_resume` with argument of type `{a}` (must be `()`).").into()) | ||||||
|  |                     } | ||||||
|  |                 }), | ||||||
|  |                 run: Arc::new(move |_, _| { | ||||||
|  |                     v.store(1, std::sync::atomic::Ordering::Relaxed); | ||||||
|  |                     es.send_event(GuiEvent::Refresh).unwrap(); | ||||||
|  |                     Data::empty_tuple() | ||||||
|  |                 }), | ||||||
|  |                 inner_statements: None, | ||||||
|  |             }) | ||||||
|  |         }) | ||||||
|  |         .add_var("playback_pause".to_owned(),{ | ||||||
|  |             let es = event_sender.clone(); | ||||||
|  |             let v = Arc::clone(&self.updated_playing_status); | ||||||
|  |             Data::new(mers_lib::data::function::Function { | ||||||
|  |                 info: Arc::new(mers_lib::info::Info::neverused()), | ||||||
|  |                 info_check: Arc::new(Mutex::new(mers_lib::info::Info::neverused())), | ||||||
|  |                 out: Arc::new(|a, _| { | ||||||
|  |                     if a.is_zero_tuple() { | ||||||
|  |                         Ok(Type::empty_tuple()) | ||||||
|  |                     } else { | ||||||
|  |                         Err(format!("Can't call `playback_pause` with argument of type `{a}` (must be `()`).").into()) | ||||||
|  |                     } | ||||||
|  |                 }), | ||||||
|  |                 run: Arc::new(move |_, _| { | ||||||
|  |                     v.store(2, std::sync::atomic::Ordering::Relaxed); | ||||||
|  |                     es.send_event(GuiEvent::Refresh).unwrap(); | ||||||
|  |                     Data::empty_tuple() | ||||||
|  |                 }), | ||||||
|  |                 inner_statements: None, | ||||||
|  |             }) | ||||||
|  |         }) | ||||||
|  |         .add_var("playback_stop".to_owned(),{ | ||||||
|  |             let es = event_sender.clone(); | ||||||
|  |             let v = Arc::clone(&self.updated_playing_status); | ||||||
|  |             Data::new(mers_lib::data::function::Function { | ||||||
|  |                 info: Arc::new(mers_lib::info::Info::neverused()), | ||||||
|  |                 info_check: Arc::new(Mutex::new(mers_lib::info::Info::neverused())), | ||||||
|  |                 out: Arc::new(|a, _| { | ||||||
|  |                     if a.is_zero_tuple() { | ||||||
|  |                         Ok(Type::empty_tuple()) | ||||||
|  |                     } else { | ||||||
|  |                         Err(format!("Can't call `playback_stop` with argument of type `{a}` (must be `()`).").into()) | ||||||
|  |                     } | ||||||
|  |                 }), | ||||||
|  |                 run: Arc::new(move |_, _| { | ||||||
|  |                     v.store(3, std::sync::atomic::Ordering::Relaxed); | ||||||
|  |                     es.send_event(GuiEvent::Refresh).unwrap(); | ||||||
|  |                     Data::empty_tuple() | ||||||
|  |                 }), | ||||||
|  |                 inner_statements: None, | ||||||
|  |             }) | ||||||
|  |         }) | ||||||
|  |         .add_var("idle_start".to_owned(),{ | ||||||
|  |             let es = event_sender.clone(); | ||||||
|  |             let v = Arc::clone(&self.updated_idle_status); | ||||||
|  |             Data::new(mers_lib::data::function::Function { | ||||||
|  |                 info: Arc::new(mers_lib::info::Info::neverused()), | ||||||
|  |                 info_check: Arc::new(Mutex::new(mers_lib::info::Info::neverused())), | ||||||
|  |                 out: Arc::new(|a, _| { | ||||||
|  |                     if a.is_zero_tuple() { | ||||||
|  |                         Ok(Type::empty_tuple()) | ||||||
|  |                     } else { | ||||||
|  |                         Err(format!("Can't call `idle_start` with argument of type `{a}` (must be `()`).").into()) | ||||||
|  |                     } | ||||||
|  |                 }), | ||||||
|  |                 run: Arc::new(move |_, _| { | ||||||
|  |                     v.store(1, std::sync::atomic::Ordering::Relaxed); | ||||||
|  |                     es.send_event(GuiEvent::Refresh).unwrap(); | ||||||
|  |                     Data::empty_tuple() | ||||||
|  |                 }), | ||||||
|  |                 inner_statements: None, | ||||||
|  |             }) | ||||||
|  |         }) | ||||||
|  |         .add_var("idle_stop".to_owned(),{ | ||||||
|  |             let es = event_sender.clone(); | ||||||
|  |             let v = Arc::clone(&self.updated_idle_status); | ||||||
|  |             Data::new(mers_lib::data::function::Function { | ||||||
|  |                 info: Arc::new(mers_lib::info::Info::neverused()), | ||||||
|  |                 info_check: Arc::new(Mutex::new(mers_lib::info::Info::neverused())), | ||||||
|  |                 out: Arc::new(|a, _| { | ||||||
|  |                     if a.is_zero_tuple() { | ||||||
|  |                         Ok(Type::empty_tuple()) | ||||||
|  |                     } else { | ||||||
|  |                         Err(format!("Can't call `idle_stop` with argument of type `{a}` (must be `()`).").into()) | ||||||
|  |                     } | ||||||
|  |                 }), | ||||||
|  |                 run: Arc::new(move |_, _| { | ||||||
|  |                     v.store(2, std::sync::atomic::Ordering::Relaxed); | ||||||
|  |                     es.send_event(GuiEvent::Refresh).unwrap(); | ||||||
|  |                     Data::empty_tuple() | ||||||
|  |                 }), | ||||||
|  |                 inner_statements: None, | ||||||
|  |             }) | ||||||
|  |         }) | ||||||
|  |         .add_var("idle_prevent".to_owned(),{ | ||||||
|  |             let es = event_sender.clone(); | ||||||
|  |             let v = Arc::clone(&self.updated_idle_status); | ||||||
|  |             Data::new(mers_lib::data::function::Function { | ||||||
|  |                 info: Arc::new(mers_lib::info::Info::neverused()), | ||||||
|  |                 info_check: Arc::new(Mutex::new(mers_lib::info::Info::neverused())), | ||||||
|  |                 out: Arc::new(|a, _| { | ||||||
|  |                     if a.is_zero_tuple() { | ||||||
|  |                         Ok(Type::empty_tuple()) | ||||||
|  |                     } else { | ||||||
|  |                         Err(format!("Can't call `idle_prevent` with argument of type `{a}` (must be `()`).").into()) | ||||||
|  |                     } | ||||||
|  |                 }), | ||||||
|  |                 run: Arc::new(move |_, _| { | ||||||
|  |                     v.store(3, std::sync::atomic::Ordering::Relaxed); | ||||||
|  |                     es.send_event(GuiEvent::Refresh).unwrap(); | ||||||
|  |                     Data::empty_tuple() | ||||||
|  |                 }), | ||||||
|  |                 inner_statements: None, | ||||||
|  |             }) | ||||||
|  |         }) | ||||||
|  |         .add_var("send_notification".to_owned(),{ | ||||||
|  |             let es = event_sender.clone(); | ||||||
|  |             Data::new(mers_lib::data::function::Function { | ||||||
|  |                 info: Arc::new(mers_lib::info::Info::neverused()), | ||||||
|  |                 info_check: Arc::new(Mutex::new(mers_lib::info::Info::neverused())), | ||||||
|  |                 out: Arc::new(|a, _| { | ||||||
|  |                     if a.is_included_in(&mers_lib::data::tuple::TupleT(vec![ | ||||||
|  |                         mers_lib::data::Type::new(mers_lib::data::string::StringT), | ||||||
|  |                         mers_lib::data::Type::new(mers_lib::data::string::StringT), | ||||||
|  |                         mers_lib::data::Type::newm(vec![ | ||||||
|  |                             Arc::new(mers_lib::data::int::IntT), | ||||||
|  |                             Arc::new(mers_lib::data::float::FloatT) | ||||||
|  |                         ]), | ||||||
|  |                     ])) { | ||||||
|  |                         Ok(Type::empty_tuple()) | ||||||
|  |                     } else { | ||||||
|  |                         Err(format!("Can't call `send_notification` with argument of type `{a}` (must be `String`).").into()) | ||||||
|  |                     } | ||||||
|  |                 }), | ||||||
|  |                 run: Arc::new(move |a, _| { | ||||||
|  |                     let a = a.get(); | ||||||
|  |                     let t = &a.as_any().downcast_ref::<mers_lib::data::tuple::Tuple>().unwrap().0; | ||||||
|  |                     let title = t[0].get().as_any().downcast_ref::<mers_lib::data::string::String>().unwrap().0.clone(); | ||||||
|  |                     let text = t[1].get().as_any().downcast_ref::<mers_lib::data::string::String>().unwrap().0.clone(); | ||||||
|  |                     let t = t[2].get(); | ||||||
|  |                     let duration = t.as_any().downcast_ref::<mers_lib::data::int::Int>().map(|s| Duration::from_secs(s.0.max(0) as _)).unwrap_or_else(|| Duration::from_secs_f64(t.as_any().downcast_ref::<mers_lib::data::float::Float>().unwrap().0)); | ||||||
|  |                     notif_sender | ||||||
|  |                         .send(Box::new(move |_| { | ||||||
|  |                             ( | ||||||
|  |                                 Box::new(Panel::with_background( | ||||||
|  |                                     GuiElemCfg::default(), | ||||||
|  |                                     ( | ||||||
|  |                                         Label::new( | ||||||
|  |                                             GuiElemCfg::at(Rectangle::from_tuples( | ||||||
|  |                                                 (0.25, 0.0), | ||||||
|  |                                                 (0.75, 0.5), | ||||||
|  |                                             )), | ||||||
|  |                                             title, | ||||||
|  |                                             Color::WHITE, | ||||||
|  |                                             None, | ||||||
|  |                                             Vec2::new(0.5, 0.0), | ||||||
|  |                                         ), | ||||||
|  |                                         Label::new( | ||||||
|  |                                             GuiElemCfg::at(Rectangle::from_tuples( | ||||||
|  |                                                 (0.0, 0.5), | ||||||
|  |                                                 (1.0, 1.0), | ||||||
|  |                                             )), | ||||||
|  |                                             text, | ||||||
|  |                                             Color::WHITE, | ||||||
|  |                                             None, | ||||||
|  |                                             Vec2::new(0.5, 1.0), | ||||||
|  |                                         ), | ||||||
|  |                                     ), | ||||||
|  |                                     Color::from_rgba(0.0, 0.0, 0.0, 0.8), | ||||||
|  |                                 )), | ||||||
|  |                                 NotifInfo::new(duration), | ||||||
|  |                             ) | ||||||
|  |                         })) | ||||||
|  |                         .unwrap(); | ||||||
|  |                     es.send_event(GuiEvent::Refresh).unwrap(); | ||||||
|  |                     Data::empty_tuple() | ||||||
|  |                 }), | ||||||
|  |                 inner_statements: None, | ||||||
|  |             }) | ||||||
|  |         }) | ||||||
|  |         .add_var("set_idle_screen_cover_pos".to_owned(),{ | ||||||
|  |             let es = event_sender.clone(); | ||||||
|  |             let update = Arc::clone(&self.updated_idle_screen_cover_pos); | ||||||
|  |             Data::new(mers_lib::data::function::Function { | ||||||
|  |                 info: Arc::new(mers_lib::info::Info::neverused()), | ||||||
|  |                 info_check: Arc::new(Mutex::new(mers_lib::info::Info::neverused())), | ||||||
|  |                 out: Arc::new(|a, _| { | ||||||
|  |                     if a.is_included_in(&mers_lib::data::Type::newm(vec![ | ||||||
|  |                         Arc::new(mers_lib::data::tuple::TupleT(vec![])), | ||||||
|  |                         Arc::new(mers_lib::data::tuple::TupleT(vec![ | ||||||
|  |                             mers_lib::data::Type::new(mers_lib::data::float::FloatT), | ||||||
|  |                             mers_lib::data::Type::new(mers_lib::data::float::FloatT), | ||||||
|  |                             mers_lib::data::Type::new(mers_lib::data::float::FloatT), | ||||||
|  |                             mers_lib::data::Type::new(mers_lib::data::float::FloatT), | ||||||
|  |                         ])) | ||||||
|  |                     ])) { | ||||||
|  |                         Ok(Type::empty_tuple()) | ||||||
|  |                     } else { | ||||||
|  |                         Err(format!("Can't call `set_idle_screen_cover_pos` with argument of type `{a}` (must be `()` or `(Float, Float, Float, Float)`).").into()) | ||||||
|  |                     } | ||||||
|  |                 }), | ||||||
|  |                 run: Arc::new(move |a, _| { | ||||||
|  |                     let a = a.get(); | ||||||
|  |                     let mut vals = a.as_any().downcast_ref::<mers_lib::data::tuple::Tuple>().unwrap().0.iter().map(|v| v.get().as_any().downcast_ref::<mers_lib::data::float::Float>().unwrap().0); | ||||||
|  |                     update.update( | ||||||
|  |                     if vals.len() >= 4 { | ||||||
|  |                         Some(Rectangle::from_tuples((vals.next().unwrap() as _, vals.next().unwrap() as _), (vals.next().unwrap() as _, vals.next().unwrap() as _))) | ||||||
|  |                     } else { None }); | ||||||
|  |                     es.send_event(GuiEvent::Refresh).unwrap(); | ||||||
|  |                     Data::empty_tuple() | ||||||
|  |                 }), | ||||||
|  |                 inner_statements: None, | ||||||
|  |             }) | ||||||
|  |         }).add_var("set_idle_screen_artist_image_pos".to_owned(),{ | ||||||
|  |             let es = event_sender.clone(); | ||||||
|  |             let update = Arc::clone(&self.updated_idle_screen_artist_image_pos); | ||||||
|  |             Data::new(mers_lib::data::function::Function { | ||||||
|  |                 info: Arc::new(mers_lib::info::Info::neverused()), | ||||||
|  |                 info_check: Arc::new(Mutex::new(mers_lib::info::Info::neverused())), | ||||||
|  |                 out: Arc::new(|a, _| { | ||||||
|  |                     if a.is_included_in(&mers_lib::data::Type::newm(vec![ | ||||||
|  |                         Arc::new(mers_lib::data::tuple::TupleT(vec![])), | ||||||
|  |                         Arc::new(mers_lib::data::tuple::TupleT(vec![ | ||||||
|  |                             mers_lib::data::Type::new(mers_lib::data::float::FloatT), | ||||||
|  |                             mers_lib::data::Type::new(mers_lib::data::float::FloatT), | ||||||
|  |                             mers_lib::data::Type::new(mers_lib::data::float::FloatT), | ||||||
|  |                             mers_lib::data::Type::new(mers_lib::data::float::FloatT), | ||||||
|  |                         ])) | ||||||
|  |                     ])) { | ||||||
|  |                         Ok(Type::empty_tuple()) | ||||||
|  |                     } else { | ||||||
|  |                         Err(format!("Can't call `set_idle_screen_artist_image_pos` with argument of type `{a}` (must be `()` or `(Float, Float, Float, Float)`).").into()) | ||||||
|  |                     } | ||||||
|  |                 }), | ||||||
|  |                 run: Arc::new(move |a, _| { | ||||||
|  |                     let a = a.get(); | ||||||
|  |                     let mut vals = a.as_any().downcast_ref::<mers_lib::data::tuple::Tuple>().unwrap().0.iter().map(|v| v.get().as_any().downcast_ref::<mers_lib::data::float::Float>().unwrap().0); | ||||||
|  |                     update.update( | ||||||
|  |                     if vals.len() >= 4 { | ||||||
|  |                         Some(Rectangle::from_tuples((vals.next().unwrap() as _, vals.next().unwrap() as _), (vals.next().unwrap() as _, vals.next().unwrap() as _))) | ||||||
|  |                     } else { None }); | ||||||
|  |                     es.send_event(GuiEvent::Refresh).unwrap(); | ||||||
|  |                     Data::empty_tuple() | ||||||
|  |                 }), | ||||||
|  |                 inner_statements: None, | ||||||
|  |             }) | ||||||
|  |         }) | ||||||
|  |         .add_var("set_idle_screen_top_text_pos".to_owned(), gen_set_pos_func("set_idle_screen_top_text_pos", Arc::clone(&event_sender), Arc::clone(&self.updated_idle_screen_top_text_pos))) | ||||||
|  |         .add_var("set_idle_screen_side_text_1_pos".to_owned(), gen_set_pos_func("set_idle_screen_side_text_1_pos", Arc::clone(&event_sender), Arc::clone(&self.updated_idle_screen_side_text_1_pos))) | ||||||
|  |         .add_var("set_idle_screen_side_text_2_pos".to_owned(), gen_set_pos_func("set_idle_screen_side_text_2_pos", Arc::clone(&event_sender), Arc::clone(&self.updated_idle_screen_side_text_2_pos))) | ||||||
|  |         .add_var("set_idle_screen_playback_buttons_pos".to_owned(), gen_set_pos_func("set_idle_screen_playback_buttons_pos", Arc::clone(&event_sender), Arc::clone(&self.updated_idle_screen_playback_buttons_pos))) | ||||||
|  | 
 | ||||||
|  |         .add_var("set_statusbar_text_format".to_owned(),{ | ||||||
|  |             let es = event_sender.clone(); | ||||||
|  |             let update = Arc::clone(&self.updated_statusbar_text_format); | ||||||
|  |             Data::new(mers_lib::data::function::Function { | ||||||
|  |                 info: Arc::new(mers_lib::info::Info::neverused()), | ||||||
|  |                 info_check: Arc::new(Mutex::new(mers_lib::info::Info::neverused())), | ||||||
|  |                 out: Arc::new(|a, _| { | ||||||
|  |                     if a.is_included_in(&mers_lib::data::string::StringT) { | ||||||
|  |                         Ok(Type::newm(vec![ | ||||||
|  |                             Arc::new(mers_lib::data::tuple::TupleT(vec![])), | ||||||
|  |                             Arc::new(mers_lib::data::string::StringT), | ||||||
|  |                         ])) | ||||||
|  |                     } else { | ||||||
|  |                         Err(format!("Can't call `set_statusbar_text_format` with argument of type `{a}` (must be `String`).").into()) | ||||||
|  |                     } | ||||||
|  |                 }), | ||||||
|  |                 run: Arc::new(move |a, _| { | ||||||
|  |                     let a = a.get(); | ||||||
|  |                     let o = match a.as_any().downcast_ref::<mers_lib::data::string::String>().unwrap().0.parse() { | ||||||
|  |                         Ok(v) => { | ||||||
|  |                             update.update(v); | ||||||
|  |                             Data::empty_tuple() | ||||||
|  |                         } | ||||||
|  |                         Err(e) => mers_lib::data::Data::new(mers_lib::data::string::String(e.to_string())), | ||||||
|  |                     }; | ||||||
|  |                     es.send_event(GuiEvent::Refresh).unwrap(); | ||||||
|  |                     o | ||||||
|  |                 }), | ||||||
|  |                 inner_statements: None, | ||||||
|  |             }) | ||||||
|  |         }) | ||||||
|  |         .add_var("set_idle_screen_top_text_format".to_owned(),{ | ||||||
|  |             let es = event_sender.clone(); | ||||||
|  |             let update = Arc::clone(&self.updated_idle_screen_top_text_format); | ||||||
|  |             Data::new(mers_lib::data::function::Function { | ||||||
|  |                 info: Arc::new(mers_lib::info::Info::neverused()), | ||||||
|  |                 info_check: Arc::new(Mutex::new(mers_lib::info::Info::neverused())), | ||||||
|  |                 out: Arc::new(|a, _| { | ||||||
|  |                     if a.is_included_in(&mers_lib::data::string::StringT) { | ||||||
|  |                         Ok(Type::newm(vec![ | ||||||
|  |                             Arc::new(mers_lib::data::tuple::TupleT(vec![])), | ||||||
|  |                             Arc::new(mers_lib::data::string::StringT), | ||||||
|  |                         ])) | ||||||
|  |                     } else { | ||||||
|  |                         Err(format!("Can't call `set_idle_screen_top_text_format` with argument of type `{a}` (must be `String`).").into()) | ||||||
|  |                     } | ||||||
|  |                 }), | ||||||
|  |                 run: Arc::new(move |a, _| { | ||||||
|  |                     let a = a.get(); | ||||||
|  |                     let o = match a.as_any().downcast_ref::<mers_lib::data::string::String>().unwrap().0.parse() { | ||||||
|  |                         Ok(v) => { | ||||||
|  |                             update.update(v); | ||||||
|  |                             Data::empty_tuple() | ||||||
|  |                         } | ||||||
|  |                         Err(e) => mers_lib::data::Data::new(mers_lib::data::string::String(e.to_string())), | ||||||
|  |                     }; | ||||||
|  |                     es.send_event(GuiEvent::Refresh).unwrap(); | ||||||
|  |                     o | ||||||
|  |                 }), | ||||||
|  |                 inner_statements: None, | ||||||
|  |             }) | ||||||
|  |         }).add_var("set_idle_screen_side_text_1_format".to_owned(),{ | ||||||
|  |             let es = event_sender.clone(); | ||||||
|  |             let update = Arc::clone(&self.updated_idle_screen_side_text_1_format); | ||||||
|  |             Data::new(mers_lib::data::function::Function { | ||||||
|  |                 info: Arc::new(mers_lib::info::Info::neverused()), | ||||||
|  |                 info_check: Arc::new(Mutex::new(mers_lib::info::Info::neverused())), | ||||||
|  |                 out: Arc::new(|a, _| { | ||||||
|  |                     if a.is_included_in(&mers_lib::data::string::StringT) { | ||||||
|  |                         Ok(Type::newm(vec![ | ||||||
|  |                             Arc::new(mers_lib::data::tuple::TupleT(vec![])), | ||||||
|  |                             Arc::new(mers_lib::data::string::StringT), | ||||||
|  |                         ])) | ||||||
|  |                     } else { | ||||||
|  |                         Err(format!("Can't call `set_idle_screen_side_text_1_format` with argument of type `{a}` (must be `String`).").into()) | ||||||
|  |                     } | ||||||
|  |                 }), | ||||||
|  |                 run: Arc::new(move |a, _| { | ||||||
|  |                     let a = a.get(); | ||||||
|  |                     let o = match a.as_any().downcast_ref::<mers_lib::data::string::String>().unwrap().0.parse() { | ||||||
|  |                         Ok(v) => { | ||||||
|  |                             update.update(v); | ||||||
|  |                             Data::empty_tuple() | ||||||
|  |                         } | ||||||
|  |                         Err(e) => mers_lib::data::Data::new(mers_lib::data::string::String(e.to_string())), | ||||||
|  |                     }; | ||||||
|  |                     es.send_event(GuiEvent::Refresh).unwrap(); | ||||||
|  |                     o | ||||||
|  |                 }), | ||||||
|  |                 inner_statements: None, | ||||||
|  |             }) | ||||||
|  |         }).add_var("set_idle_screen_side_text_2_format".to_owned(),{ | ||||||
|  |             let es = event_sender.clone(); | ||||||
|  |             let update = Arc::clone(&self.updated_idle_screen_side_text_2_format); | ||||||
|  |             Data::new(mers_lib::data::function::Function { | ||||||
|  |                 info: Arc::new(mers_lib::info::Info::neverused()), | ||||||
|  |                 info_check: Arc::new(Mutex::new(mers_lib::info::Info::neverused())), | ||||||
|  |                 out: Arc::new(|a, _| { | ||||||
|  |                     if a.is_included_in(&mers_lib::data::string::StringT) { | ||||||
|  |                         Ok(Type::newm(vec![ | ||||||
|  |                             Arc::new(mers_lib::data::tuple::TupleT(vec![])), | ||||||
|  |                             Arc::new(mers_lib::data::string::StringT), | ||||||
|  |                         ])) | ||||||
|  |                     } else { | ||||||
|  |                         Err(format!("Can't call `set_idle_screen_side_text_2_format` with argument of type `{a}` (must be `String`).").into()) | ||||||
|  |                     } | ||||||
|  |                 }), | ||||||
|  |                 run: Arc::new(move |a, _| { | ||||||
|  |                     let a = a.get(); | ||||||
|  |                     let o = match a.as_any().downcast_ref::<mers_lib::data::string::String>().unwrap().0.parse() { | ||||||
|  |                         Ok(v) => { | ||||||
|  |                             update.update(v); | ||||||
|  |                             Data::empty_tuple() | ||||||
|  |                         } | ||||||
|  |                         Err(e) => mers_lib::data::Data::new(mers_lib::data::string::String(e.to_string())), | ||||||
|  |                     }; | ||||||
|  |                     es.send_event(GuiEvent::Refresh).unwrap(); | ||||||
|  |                     o | ||||||
|  |                 }), | ||||||
|  |                 inner_statements: None, | ||||||
|  |             }) | ||||||
|  |         }) | ||||||
|  |         // .add_type("Song".to_owned(), Ok(Arc::new(mers_lib::data::object::ObjectT(vec![
 | ||||||
|  |         //         ("id".to_owned(), Type::new(mers_lib::data::int::IntT)),
 | ||||||
|  |         //         ("title".to_owned(), Type::new(mers_lib::data::string::StringT)),
 | ||||||
|  |         //         ("album".to_owned(), Type::new(mers_lib::data::string::StringT)),
 | ||||||
|  |         //         ("artist".to_owned(), Type::new(mers_lib::data::string::StringT)),
 | ||||||
|  |         //     ]))))
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn run( | ||||||
|  |         gui_cfg: &mut GuiConfig, | ||||||
|  |         gui: &mut Gui, | ||||||
|  |         mut db: Option<&mut Database>, | ||||||
|  |         run: impl FnOnce(&Self) -> &OptFunc, | ||||||
|  |     ) { | ||||||
|  |         // prepare vars
 | ||||||
|  |         if let Some(db) = &mut db { | ||||||
|  |             *gui_cfg.merscfg.var_is_playing.write().unwrap() = | ||||||
|  |                 mers_lib::data::Data::new(mers_lib::data::bool::Bool(db.playing)); | ||||||
|  |         } | ||||||
|  |         *gui_cfg.merscfg.var_window_size_in_pixels.write().unwrap() = | ||||||
|  |             mers_lib::data::Data::new(mers_lib::data::tuple::Tuple(vec![ | ||||||
|  |                 mers_lib::data::Data::new(mers_lib::data::int::Int(gui.size.x as _)), | ||||||
|  |                 mers_lib::data::Data::new(mers_lib::data::int::Int(gui.size.y as _)), | ||||||
|  |             ])); | ||||||
|  |         *gui_cfg | ||||||
|  |             .merscfg | ||||||
|  |             .var_idle_screen_cover_aspect_ratio | ||||||
|  |             .write() | ||||||
|  |             .unwrap() = mers_lib::data::Data::new(mers_lib::data::float::Float( | ||||||
|  |             gui.gui.c_idle_display.cover_aspect_ratio.value as _, | ||||||
|  |         )); | ||||||
|  | 
 | ||||||
|  |         // run
 | ||||||
|  |         run(&gui_cfg.merscfg).run(); | ||||||
|  | 
 | ||||||
|  |         // apply updates
 | ||||||
|  | 
 | ||||||
|  |         match gui_cfg | ||||||
|  |             .merscfg | ||||||
|  |             .updated_playing_status | ||||||
|  |             .load(std::sync::atomic::Ordering::Relaxed) | ||||||
|  |         { | ||||||
|  |             0 => {} | ||||||
|  |             v => { | ||||||
|  |                 match v { | ||||||
|  |                     1 => gui.exec_gui_action(GuiAction::SendToServer(Command::Resume)), | ||||||
|  |                     2 => gui.exec_gui_action(GuiAction::SendToServer(Command::Pause)), | ||||||
|  |                     3 => gui.exec_gui_action(GuiAction::SendToServer(Command::Stop)), | ||||||
|  |                     _ => {} | ||||||
|  |                 } | ||||||
|  |                 gui_cfg | ||||||
|  |                     .merscfg | ||||||
|  |                     .updated_playing_status | ||||||
|  |                     .store(0, std::sync::atomic::Ordering::Relaxed); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         match gui_cfg | ||||||
|  |             .merscfg | ||||||
|  |             .updated_idle_status | ||||||
|  |             .load(std::sync::atomic::Ordering::Relaxed) | ||||||
|  |         { | ||||||
|  |             0 => {} | ||||||
|  |             v => { | ||||||
|  |                 match v { | ||||||
|  |                     1 => gui.gui.force_idle(), | ||||||
|  |                     2 => gui.gui.unidle(), | ||||||
|  |                     3 => gui.gui.not_idle(), | ||||||
|  |                     _ => {} | ||||||
|  |                 } | ||||||
|  |                 gui_cfg | ||||||
|  |                     .merscfg | ||||||
|  |                     .updated_idle_status | ||||||
|  |                     .store(0, std::sync::atomic::Ordering::Relaxed); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if let Some(maybe_rect) = gui_cfg.merscfg.updated_idle_screen_cover_pos.take_val() { | ||||||
|  |             gui.gui.c_idle_display.cover_pos = maybe_rect; | ||||||
|  |         } | ||||||
|  |         if let Some(maybe_rect) = gui_cfg | ||||||
|  |             .merscfg | ||||||
|  |             .updated_idle_screen_artist_image_pos | ||||||
|  |             .take_val() | ||||||
|  |         { | ||||||
|  |             gui.gui.c_idle_display.artist_image_pos = maybe_rect; | ||||||
|  |         } | ||||||
|  |         if let Some(maybe_rect) = gui_cfg.merscfg.updated_idle_screen_top_text_pos.take_val() { | ||||||
|  |             gui.gui.c_idle_display.c_top_label.config_mut().pos = maybe_rect; | ||||||
|  |         } | ||||||
|  |         if let Some(maybe_rect) = gui_cfg | ||||||
|  |             .merscfg | ||||||
|  |             .updated_idle_screen_side_text_1_pos | ||||||
|  |             .take_val() | ||||||
|  |         { | ||||||
|  |             gui.gui.c_idle_display.c_side1_label.config_mut().pos = maybe_rect; | ||||||
|  |         } | ||||||
|  |         if let Some(maybe_rect) = gui_cfg | ||||||
|  |             .merscfg | ||||||
|  |             .updated_idle_screen_side_text_2_pos | ||||||
|  |             .take_val() | ||||||
|  |         { | ||||||
|  |             gui.gui.c_idle_display.c_side2_label.config_mut().pos = maybe_rect; | ||||||
|  |         } | ||||||
|  |         if let Some(maybe_rect) = gui_cfg | ||||||
|  |             .merscfg | ||||||
|  |             .updated_idle_screen_playback_buttons_pos | ||||||
|  |             .take_val() | ||||||
|  |         { | ||||||
|  |             gui.gui.c_idle_display.c_buttons.config_mut().pos = maybe_rect; | ||||||
|  |             gui.gui.c_idle_display.c_buttons_custom_pos = true; | ||||||
|  |         } | ||||||
|  |         if let Some(fmt) = gui_cfg.merscfg.updated_statusbar_text_format.take_val() { | ||||||
|  |             gui_cfg.status_bar_text = fmt; | ||||||
|  |             gui.gui.c_status_bar.force_reset_texts = true; | ||||||
|  |         } | ||||||
|  |         if let Some(fmt) = gui_cfg | ||||||
|  |             .merscfg | ||||||
|  |             .updated_idle_screen_top_text_format | ||||||
|  |             .take_val() | ||||||
|  |         { | ||||||
|  |             gui_cfg.idle_top_text = fmt; | ||||||
|  |             gui.gui.c_idle_display.force_reset_texts = true; | ||||||
|  |         } | ||||||
|  |         if let Some(fmt) = gui_cfg | ||||||
|  |             .merscfg | ||||||
|  |             .updated_idle_screen_side_text_1_format | ||||||
|  |             .take_val() | ||||||
|  |         { | ||||||
|  |             gui_cfg.idle_side1_text = fmt; | ||||||
|  |             gui.gui.c_idle_display.force_reset_texts = true; | ||||||
|  |         } | ||||||
|  |         if let Some(fmt) = gui_cfg | ||||||
|  |             .merscfg | ||||||
|  |             .updated_idle_screen_side_text_2_format | ||||||
|  |             .take_val() | ||||||
|  |         { | ||||||
|  |             gui_cfg.idle_side2_text = fmt; | ||||||
|  |             gui.gui.c_idle_display.force_reset_texts = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn load( | ||||||
|  |         &mut self, | ||||||
|  |         event_sender: Arc<UserEventSender<GuiEvent>>, | ||||||
|  |         notif_sender: Sender< | ||||||
|  |             Box<dyn FnOnce(&NotifOverlay) -> (Box<dyn GuiElem>, NotifInfo) + Send>, | ||||||
|  |         >, | ||||||
|  |     ) -> std::io::Result<Result<Result<(), (String, Option<CheckError>)>, CheckError>> { | ||||||
|  |         let src = mers_lib::prelude_compile::Source::new_from_file(self.source_file.clone())?; | ||||||
|  |         Ok(self.load2(src, event_sender, notif_sender)) | ||||||
|  |     } | ||||||
|  |     fn load2( | ||||||
|  |         &mut self, | ||||||
|  |         mut src: mers_lib::prelude_compile::Source, | ||||||
|  |         event_sender: Arc<UserEventSender<GuiEvent>>, | ||||||
|  |         notif_sender: Sender< | ||||||
|  |             Box<dyn FnOnce(&NotifOverlay) -> (Box<dyn GuiElem>, NotifInfo) + Send>, | ||||||
|  |         >, | ||||||
|  |     ) -> Result<Result<(), (String, Option<CheckError>)>, CheckError> { | ||||||
|  |         let srca = Arc::new(src.clone()); | ||||||
|  |         let (mut i1, mut i2, mut i3) = self | ||||||
|  |             .custom_globals( | ||||||
|  |                 mers_lib::prelude_extend_config::Config::new().bundle_std(), | ||||||
|  |                 event_sender, | ||||||
|  |                 notif_sender, | ||||||
|  |             ) | ||||||
|  |             .infos(); | ||||||
|  |         let compiled = mers_lib::prelude_compile::parse(&mut src, &srca)? | ||||||
|  |             .compile(&mut i1, CompInfo::default())?; | ||||||
|  |         let _ = compiled.check(&mut i3, None)?; | ||||||
|  |         let out = compiled.run(&mut i2); | ||||||
|  |         Ok(self.load3(out)) | ||||||
|  |     } | ||||||
|  |     fn load3(&mut self, out: mers_lib::data::Data) -> Result<(), (String, Option<CheckError>)> { | ||||||
|  |         if let Some(obj) = out | ||||||
|  |             .get() | ||||||
|  |             .as_any() | ||||||
|  |             .downcast_ref::<mers_lib::data::object::Object>() | ||||||
|  |         { | ||||||
|  |             for (name, val) in obj.0.iter() { | ||||||
|  |                 let name = name.as_str(); | ||||||
|  |                 match name { | ||||||
|  |                     "before_draw" => { | ||||||
|  |                         self.func_before_draw = OptFunc::some(check_handler(name, val)?); | ||||||
|  |                     } | ||||||
|  |                     "library_updated" => { | ||||||
|  |                         self.func_library_updated = OptFunc::some(check_handler(name, val)?); | ||||||
|  |                     } | ||||||
|  |                     "queue_updated" => { | ||||||
|  |                         self.func_queue_updated = OptFunc::some(check_handler(name, val)?); | ||||||
|  |                     } | ||||||
|  |                     name => { | ||||||
|  |                         eprintln!("merscfg: ignoring unexpected field named '{name}'.") | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             return Err((format!("mers config file must return an object!"), None)); | ||||||
|  |         } | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn check_handler( | ||||||
|  |     name: &str, | ||||||
|  |     val: &mers_lib::data::Data, | ||||||
|  | ) -> Result<mers_lib::data::function::Function, (String, Option<CheckError>)> { | ||||||
|  |     if let Some(func) = val | ||||||
|  |         .get() | ||||||
|  |         .as_any() | ||||||
|  |         .downcast_ref::<mers_lib::data::function::Function>() | ||||||
|  |     { | ||||||
|  |         match func.check(&Type::empty_tuple()) { | ||||||
|  |             Ok(_) => Ok(func.clone()), | ||||||
|  |             Err(e) => Err((format!("Function '{name}' causes an error:"), Some(e))), | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         Err((format!("Expected a function for field '{name}'!"), None)) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn gen_set_pos_func( | ||||||
|  |     name: &'static str, | ||||||
|  |     es: Arc<UserEventSender<GuiEvent>>, | ||||||
|  |     update: Arc<Updatable<Rectangle>>, | ||||||
|  | ) -> Data { | ||||||
|  |     Data::new(mers_lib::data::function::Function { | ||||||
|  |         info: Arc::new(mers_lib::info::Info::neverused()), | ||||||
|  |         info_check: Arc::new(Mutex::new(mers_lib::info::Info::neverused())), | ||||||
|  |         out: Arc::new(move |a, _| { | ||||||
|  |             if a.is_included_in(&mers_lib::data::Type::newm(vec![Arc::new( | ||||||
|  |                 mers_lib::data::tuple::TupleT(vec![ | ||||||
|  |                     mers_lib::data::Type::new(mers_lib::data::float::FloatT), | ||||||
|  |                     mers_lib::data::Type::new(mers_lib::data::float::FloatT), | ||||||
|  |                     mers_lib::data::Type::new(mers_lib::data::float::FloatT), | ||||||
|  |                     mers_lib::data::Type::new(mers_lib::data::float::FloatT), | ||||||
|  |                 ]), | ||||||
|  |             )])) { | ||||||
|  |                 Ok(Type::empty_tuple()) | ||||||
|  |             } else { | ||||||
|  |                 Err(format!("Can't call `{name}` with argument of type `{a}` (must be `(Float, Float, Float, Float)`).").into()) | ||||||
|  |             } | ||||||
|  |         }), | ||||||
|  |         run: Arc::new(move |a, _| { | ||||||
|  |             let a = a.get(); | ||||||
|  |             let mut vals = a | ||||||
|  |                 .as_any() | ||||||
|  |                 .downcast_ref::<mers_lib::data::tuple::Tuple>() | ||||||
|  |                 .unwrap() | ||||||
|  |                 .0 | ||||||
|  |                 .iter() | ||||||
|  |                 .map(|v| { | ||||||
|  |                     v.get() | ||||||
|  |                         .as_any() | ||||||
|  |                         .downcast_ref::<mers_lib::data::float::Float>() | ||||||
|  |                         .unwrap() | ||||||
|  |                         .0 | ||||||
|  |                 }); | ||||||
|  |             update.update(Rectangle::from_tuples( | ||||||
|  |                 (vals.next().unwrap() as _, vals.next().unwrap() as _), | ||||||
|  |                 (vals.next().unwrap() as _, vals.next().unwrap() as _), | ||||||
|  |             )); | ||||||
|  |             es.send_event(GuiEvent::Refresh).unwrap(); | ||||||
|  |             Data::empty_tuple() | ||||||
|  |         }), | ||||||
|  |         inner_statements: None, | ||||||
|  |     }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub struct Updatable<T> { | ||||||
|  |     updated: AtomicBool, | ||||||
|  |     value: Mutex<Option<T>>, | ||||||
|  | } | ||||||
|  | impl<T> Updatable<T> { | ||||||
|  |     pub fn new() -> Self { | ||||||
|  |         Self { | ||||||
|  |             updated: AtomicBool::new(false), | ||||||
|  |             value: Mutex::new(None), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     pub fn update(&self, val: T) { | ||||||
|  |         self.updated | ||||||
|  |             .store(true, std::sync::atomic::Ordering::Relaxed); | ||||||
|  |         *self.value.lock().unwrap() = Some(val); | ||||||
|  |     } | ||||||
|  |     pub fn take_val(&self) -> Option<T> { | ||||||
|  |         if self.updated.load(std::sync::atomic::Ordering::Relaxed) { | ||||||
|  |             self.updated | ||||||
|  |                 .store(false, std::sync::atomic::Ordering::Relaxed); | ||||||
|  |             self.value.lock().unwrap().take() | ||||||
|  |         } else { | ||||||
|  |             None | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | impl<T> Updatable<T> | ||||||
|  | where | ||||||
|  |     T: Default, | ||||||
|  | { | ||||||
|  |     pub fn modify<R>(&self, func: impl FnOnce(&mut T) -> R) -> R { | ||||||
|  |         self.updated | ||||||
|  |             .store(true, std::sync::atomic::Ordering::Relaxed); | ||||||
|  |         let mut val = self.value.lock().unwrap(); | ||||||
|  |         if val.is_none() { | ||||||
|  |             *val = Some(Default::default()); | ||||||
|  |         } | ||||||
|  |         func(val.as_mut().unwrap()) | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										7
									
								
								musicdb-lib/Cargo.toml
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										7
									
								
								musicdb-lib/Cargo.toml
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							| @ -6,8 +6,11 @@ edition = "2021" | |||||||
| # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||||||
| 
 | 
 | ||||||
| [dependencies] | [dependencies] | ||||||
| awedio = "0.2.0" | awedio = { version = "0.2.0", optional = true } | ||||||
| base64 = "0.21.2" | base64 = "0.21.2" | ||||||
| rand = "0.8.5" | rand = "0.8.5" | ||||||
| rc-u8-reader = "2.0.16" | rc-u8-reader = "2.0.16" | ||||||
| tokio = "1.29.1" | tokio = { version = "1.29.1", features = ["sync"] } | ||||||
|  | 
 | ||||||
|  | [features] | ||||||
|  | playback = ["awedio"] | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| pub mod data; | pub mod data; | ||||||
| pub mod load; | pub mod load; | ||||||
|  | #[cfg(feature = "playback")] | ||||||
| pub mod player; | pub mod player; | ||||||
| pub mod server; | pub mod server; | ||||||
|  | |||||||
| @ -1,11 +1,8 @@ | |||||||
| pub mod get; | pub mod get; | ||||||
| 
 | 
 | ||||||
| use std::{ | use std::{ | ||||||
|     io::{BufRead, BufReader, Read, Write}, |     io::{Read, Write}, | ||||||
|     net::{SocketAddr, TcpListener}, |  | ||||||
|     sync::{mpsc, Arc, Mutex}, |     sync::{mpsc, Arc, Mutex}, | ||||||
|     thread, |  | ||||||
|     time::Duration, |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::{ | ||||||
| @ -18,8 +15,15 @@ use crate::{ | |||||||
|         AlbumId, ArtistId, SongId, |         AlbumId, ArtistId, SongId, | ||||||
|     }, |     }, | ||||||
|     load::ToFromBytes, |     load::ToFromBytes, | ||||||
|     player::Player, | }; | ||||||
|     server::get::handle_one_connection_as_get, | #[cfg(feature = "playback")] | ||||||
|  | use crate::{player::Player, server::get::handle_one_connection_as_get}; | ||||||
|  | #[cfg(feature = "playback")] | ||||||
|  | use std::{ | ||||||
|  |     io::{BufRead, BufReader}, | ||||||
|  |     net::{SocketAddr, TcpListener}, | ||||||
|  |     thread, | ||||||
|  |     time::Duration, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| #[derive(Clone, Debug)] | #[derive(Clone, Debug)] | ||||||
| @ -84,6 +88,7 @@ impl Command { | |||||||
| /// a) initialize new connections using db.init_connection() to synchronize the new client
 | /// a) initialize new connections using db.init_connection() to synchronize the new client
 | ||||||
| /// b) handle the decoding of messages using Command::from_bytes()
 | /// b) handle the decoding of messages using Command::from_bytes()
 | ||||||
| /// c) re-encode all received messages using Command::to_bytes_vec(), send them to the db, and send them to all your clients.
 | /// c) re-encode all received messages using Command::to_bytes_vec(), send them to the db, and send them to all your clients.
 | ||||||
|  | #[cfg(feature = "playback")] | ||||||
| pub fn run_server( | pub fn run_server( | ||||||
|     database: Arc<Mutex<Database>>, |     database: Arc<Mutex<Database>>, | ||||||
|     addr_tcp: Option<SocketAddr>, |     addr_tcp: Option<SocketAddr>, | ||||||
|  | |||||||
| @ -10,7 +10,7 @@ axum = { version = "0.6.19", features = ["headers"] } | |||||||
| clap = { version = "4.4.6", features = ["derive"] } | clap = { version = "4.4.6", features = ["derive"] } | ||||||
| futures = "0.3.28" | futures = "0.3.28" | ||||||
| headers = "0.3.8" | headers = "0.3.8" | ||||||
| musicdb-lib = { version = "0.1.0", path = "../musicdb-lib" } | musicdb-lib = { version = "0.1.0", path = "../musicdb-lib", features = ["playback"] } | ||||||
| serde = { version = "1.0", features = ["derive"] } | serde = { version = "1.0", features = ["derive"] } | ||||||
| serde_json = "1.0" | serde_json = "1.0" | ||||||
| tokio = { version = "1.0", features = ["full"] } | tokio = { version = "1.0", features = ["full"] } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Mark
						Mark