mirror of
https://github.com/Dummi26/musicdb.git
synced 2025-03-10 14:13:53 +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,10 +281,12 @@ 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);
|
||||||
self.c_buttons.config_mut().pos = Rectangle::from_tuples(
|
if !self.c_buttons_custom_pos {
|
||||||
(buttons_right_pos - buttons_width, 0.86),
|
self.c_buttons.config_mut().pos = Rectangle::from_tuples(
|
||||||
(buttons_right_pos, 0.94),
|
(buttons_right_pos - buttons_width, 0.86),
|
||||||
);
|
(buttons_right_pos, 0.94),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn config(&self) -> &GuiElemCfg {
|
fn config(&self) -> &GuiElemCfg {
|
||||||
|
@ -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 right_x = get_right_x(left, top, bottom, aspect_ratio.value);
|
let pos = if let Some(pos) = pos {
|
||||||
let pos = Rectangle::from_tuples((left, top), (right_x, bottom));
|
pos
|
||||||
|
} else {
|
||||||
|
let right_x = get_right_x(left, top, bottom, aspect_ratio.value);
|
||||||
|
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_idle_display.elem_mut(),
|
self.c_notif_overlay.elem_mut(),
|
||||||
self.c_status_bar.elem_mut(),
|
self.c_idle_display.elem_mut(),
|
||||||
self.c_settings.elem_mut(),
|
]
|
||||||
self.c_main_view.elem_mut(),
|
.into_iter()
|
||||||
]
|
.chain(self.c_editing_songs.as_mut().map(|v| v.elem_mut()))
|
||||||
.into_iter()
|
.chain([
|
||||||
.chain(self.c_context_menu.as_mut().map(|v| v.elem_mut())),
|
self.c_status_bar.elem_mut(),
|
||||||
|
self.c_settings.elem_mut(),
|
||||||
|
self.c_main_view.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
|
||||||
};
|
};
|
||||||
if let Mode::SyncplayerLocal { lib_dir } = mode {
|
#[allow(unused_labels)]
|
||||||
let mut db = database.lock().unwrap();
|
'ifstatementworkaround: {
|
||||||
db.lib_directory = lib_dir;
|
// use if+break instead of if-else because we can't #[cfg(feature)] the if statement,
|
||||||
} else {
|
// since we want the else part to run if the feature is disabled
|
||||||
|
#[cfg(feature = "playback")]
|
||||||
|
if let Mode::SyncplayerLocal { lib_dir } = mode {
|
||||||
|
let mut db = database.lock().unwrap();
|
||||||
|
db.lib_directory = lib_dir;
|
||||||
|
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…
Reference in New Issue
Block a user