mirror of
https://github.com/Dummi26/musicdb.git
synced 2025-03-10 05:43: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"
|
||||
speedy2d = { version = "1.12.0", optional = true }
|
||||
toml = "0.7.6"
|
||||
mers_lib = { path = "../../mers/mers_lib", optional = true }
|
||||
|
||||
[features]
|
||||
default = ["speedy2d"]
|
||||
merscfg = ["mers_lib"]
|
||||
playback = ["musicdb-lib/playback"]
|
||||
|
@ -9,7 +9,7 @@ use std::{
|
||||
};
|
||||
|
||||
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,
|
||||
server::{get, Command},
|
||||
};
|
||||
@ -26,8 +26,11 @@ use speedy2d::{
|
||||
Graphics2D,
|
||||
};
|
||||
|
||||
#[cfg(feature = "merscfg")]
|
||||
use crate::merscfg::MersCfg;
|
||||
use crate::{
|
||||
gui_base::Panel,
|
||||
gui_base::{Panel, ScrollBox},
|
||||
gui_edit_song::EditorForSongs,
|
||||
gui_notif::{NotifInfo, NotifOverlay},
|
||||
gui_screen::GuiScreen,
|
||||
gui_text::Label,
|
||||
@ -76,8 +79,8 @@ pub fn main(
|
||||
get_con: get::Client<TcpStream>,
|
||||
event_sender_arc: Arc<Mutex<Option<UserEventSender<GuiEvent>>>>,
|
||||
) {
|
||||
let mut config_file = super::get_config_file_path();
|
||||
config_file.push("config_gui.toml");
|
||||
let config_dir = super::get_config_file_path();
|
||||
let config_file = config_dir.join("config_gui.toml");
|
||||
let mut font = None;
|
||||
let mut line_height = 32.0;
|
||||
let mut scroll_pixels_multiplier = 1.0;
|
||||
@ -214,7 +217,7 @@ pub fn main(
|
||||
connection,
|
||||
Arc::new(Mutex::new(get_con)),
|
||||
event_sender_arc,
|
||||
sender,
|
||||
Arc::new(sender),
|
||||
line_height,
|
||||
scroll_pixels_multiplier,
|
||||
scroll_lines_multiplier,
|
||||
@ -254,6 +257,8 @@ pub fn main(
|
||||
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_album: 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 event_sender: UserEventSender<GuiEvent>,
|
||||
pub event_sender: Arc<UserEventSender<GuiEvent>>,
|
||||
pub database: Arc<Mutex<Database>>,
|
||||
pub connection: TcpStream,
|
||||
pub get_con: Arc<Mutex<get::Client<TcpStream>>>,
|
||||
@ -304,7 +311,7 @@ impl Gui {
|
||||
connection: TcpStream,
|
||||
get_con: Arc<Mutex<get::Client<TcpStream>>>,
|
||||
event_sender_arc: Arc<Mutex<Option<UserEventSender<GuiEvent>>>>,
|
||||
event_sender: UserEventSender<GuiEvent>,
|
||||
event_sender: Arc<UserEventSender<GuiEvent>>,
|
||||
line_height: f32,
|
||||
scroll_pixels_multiplier: f64,
|
||||
scroll_lines_multiplier: f64,
|
||||
@ -313,6 +320,28 @@ impl Gui {
|
||||
) -> Self {
|
||||
let (notif_overlay, notif_sender) = NotifOverlay::new();
|
||||
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(
|
||||
musicdb_lib::data::database::UpdateEndpoint::Custom(Box::new(move |cmd| match cmd {
|
||||
Command::Resume
|
||||
@ -662,6 +691,9 @@ pub(crate) trait GuiElemInternal: GuiElem {
|
||||
}
|
||||
}
|
||||
fn _keyboard_move_focus(&mut self, decrement: bool, refocus: bool) -> bool {
|
||||
if self.config().enabled == false {
|
||||
return false;
|
||||
}
|
||||
let mut focus_index = if refocus {
|
||||
usize::MAX
|
||||
} 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(Box<dyn FnOnce(&mut Database) -> Vec<Self>>),
|
||||
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.
|
||||
ResetKeyboardFocus,
|
||||
SetDragging(
|
||||
@ -998,8 +1030,11 @@ pub enum GuiAction {
|
||||
SetLineHeight(f32),
|
||||
LoadCover(CoverId),
|
||||
/// Run a custom closure with mutable access to the Gui struct
|
||||
Do(Box<dyn FnMut(&mut Gui)>),
|
||||
Do(Box<dyn FnOnce(&mut Gui)>),
|
||||
Exit,
|
||||
EditSongs(Vec<Song>),
|
||||
// EditAlbums(Vec<Album>),
|
||||
// EditArtists(Vec<Artist>),
|
||||
}
|
||||
pub enum Dragging {
|
||||
Artist(ArtistId),
|
||||
@ -1032,7 +1067,6 @@ pub struct DrawInfo<'a> {
|
||||
Dragging,
|
||||
Option<Box<dyn FnMut(&mut DrawInfo, &mut Graphics2D)>>,
|
||||
)>,
|
||||
pub context_menu: Option<Box<dyn GuiElem>>,
|
||||
pub gui_config: &'a mut GuiConfig,
|
||||
pub high_performance: bool,
|
||||
}
|
||||
@ -1068,7 +1102,40 @@ impl Gui {
|
||||
GuiAction::ResetKeyboardFocus => _ = self.gui._keyboard_reset_focus(),
|
||||
GuiAction::SetDragging(d) => self.dragging = 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) => {
|
||||
self.line_height = h;
|
||||
self.gui
|
||||
@ -1080,7 +1147,7 @@ impl Gui {
|
||||
.unwrap()
|
||||
.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::EndIdle(v) => {
|
||||
if v {
|
||||
@ -1090,19 +1157,20 @@ impl Gui {
|
||||
}
|
||||
}
|
||||
GuiAction::OpenSettings(v) => {
|
||||
self.gui.idle.target = 0.0;
|
||||
self.gui.last_interaction = Instant::now();
|
||||
self.gui.unidle();
|
||||
if self.gui.settings.0 != v {
|
||||
self.gui.settings = (v, Some(Instant::now()));
|
||||
}
|
||||
}
|
||||
GuiAction::OpenMain => {
|
||||
self.gui.idle.target = 0.0;
|
||||
self.gui.last_interaction = Instant::now();
|
||||
self.gui.unidle();
|
||||
if self.gui.settings.0 {
|
||||
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()),
|
||||
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 custom_images = self.custom_images.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 {
|
||||
time: draw_start_time,
|
||||
actions: Vec::with_capacity(0),
|
||||
@ -1133,12 +1204,10 @@ impl WindowHandler<GuiEvent> for Gui {
|
||||
line_height: self.line_height,
|
||||
high_performance: self.high_performance,
|
||||
dragging: self.dragging.take(),
|
||||
context_menu: self.gui.c_context_menu.take(),
|
||||
gui_config: &mut cfg,
|
||||
};
|
||||
self.gui._draw(&mut info, graphics);
|
||||
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();
|
||||
if let Some((d, f)) = &mut self.dragging {
|
||||
if let Some(f) = f {
|
||||
@ -1242,6 +1311,9 @@ impl WindowHandler<GuiEvent> for Gui {
|
||||
self.exec_gui_action(a)
|
||||
}
|
||||
}
|
||||
if button != MouseButton::Right {
|
||||
self.gui.c_context_menu = None;
|
||||
}
|
||||
helper.request_redraw();
|
||||
}
|
||||
fn on_mouse_wheel_scroll(
|
||||
@ -1365,10 +1437,34 @@ impl WindowHandler<GuiEvent> for Gui {
|
||||
match user_event {
|
||||
GuiEvent::Refresh => helper.request_redraw(),
|
||||
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());
|
||||
helper.request_redraw();
|
||||
}
|
||||
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());
|
||||
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,
|
||||
pub children: C,
|
||||
pub children_heights: Vec<f32>,
|
||||
pub default_size: f32,
|
||||
pub size_unit: ScrollBoxSizeUnit,
|
||||
pub scroll_target: f32,
|
||||
pub scroll_display: f32,
|
||||
/// the y-position of the bottom edge of the last element (i.e. the total height)
|
||||
height_bottom: f32,
|
||||
/// 0.max(height_bottom - 1)
|
||||
max_scroll: f32,
|
||||
@ -145,15 +147,16 @@ impl<C: GuiElemChildren> ScrollBox<C> {
|
||||
size_unit: ScrollBoxSizeUnit,
|
||||
children: C,
|
||||
children_heights: Vec<f32>,
|
||||
default_size: f32,
|
||||
) -> Self {
|
||||
Self {
|
||||
config: config.w_scroll().w_mouse(),
|
||||
children,
|
||||
children_heights,
|
||||
default_size,
|
||||
size_unit,
|
||||
scroll_target: 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,
|
||||
max_scroll: 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() {
|
||||
let target = self.children.len();
|
||||
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 {
|
||||
self.children_heights.pop();
|
||||
@ -341,7 +344,7 @@ impl<C: GuiElemChildren> Button<C> {
|
||||
children: C,
|
||||
) -> Self {
|
||||
Self {
|
||||
config: config.w_mouse(),
|
||||
config: config.w_mouse().w_keyboard_focus(),
|
||||
children,
|
||||
action: Arc::new(action),
|
||||
}
|
||||
@ -376,6 +379,27 @@ impl<C: GuiElemChildren + 'static> GuiElem for Button<C> {
|
||||
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) {
|
||||
let mouse_down = self.config.mouse_down.0;
|
||||
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)
|
||||
},
|
||||
);
|
||||
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 crate::{
|
||||
gui::{DrawInfo, GuiAction, GuiElem, GuiElemCfg, GuiServerImage},
|
||||
gui::{rect_from_rel, DrawInfo, GuiAction, GuiElem, GuiElemCfg, GuiServerImage},
|
||||
gui_anim::AnimationController,
|
||||
gui_base::Button,
|
||||
gui_playback::{get_right_x, image_display, CurrentInfo},
|
||||
@ -13,24 +13,32 @@ use crate::{
|
||||
};
|
||||
|
||||
pub struct IdleDisplay {
|
||||
config: GuiElemCfg,
|
||||
pub config: GuiElemCfg,
|
||||
pub idle_mode: f32,
|
||||
current_info: CurrentInfo,
|
||||
current_artist_image: Option<(ArtistId, Option<(String, Option<Option<ImageHandle>>)>)>,
|
||||
pub current_info: CurrentInfo,
|
||||
pub current_artist_image: Option<(ArtistId, Option<(String, Option<Option<ImageHandle>>)>)>,
|
||||
pub c_idle_exit_hint: Button<[Label; 1]>,
|
||||
c_top_label: AdvancedLabel,
|
||||
c_side1_label: AdvancedLabel,
|
||||
c_side2_label: AdvancedLabel,
|
||||
c_buttons: PlayPause,
|
||||
cover_aspect_ratio: AnimationController<f32>,
|
||||
artist_image_aspect_ratio: AnimationController<f32>,
|
||||
cover_left: f32,
|
||||
cover_top: f32,
|
||||
cover_bottom: f32,
|
||||
pub c_top_label: AdvancedLabel,
|
||||
pub c_side1_label: AdvancedLabel,
|
||||
pub c_side2_label: AdvancedLabel,
|
||||
pub c_buttons: PlayPause,
|
||||
pub c_buttons_custom_pos: bool,
|
||||
|
||||
pub cover_aspect_ratio: AnimationController<f32>,
|
||||
pub artist_image_aspect_ratio: AnimationController<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.5 -> lower half of cover
|
||||
artist_image_top: f32,
|
||||
artist_image_to_cover_margin: f32,
|
||||
pub artist_image_top: f32,
|
||||
pub artist_image_to_cover_margin: f32,
|
||||
|
||||
pub force_reset_texts: bool,
|
||||
}
|
||||
|
||||
impl IdleDisplay {
|
||||
@ -60,6 +68,7 @@ impl IdleDisplay {
|
||||
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_buttons: PlayPause::new(GuiElemCfg::default()),
|
||||
c_buttons_custom_pos: false,
|
||||
cover_aspect_ratio: AnimationController::new(
|
||||
1.0,
|
||||
1.0,
|
||||
@ -78,11 +87,14 @@ impl IdleDisplay {
|
||||
0.6,
|
||||
Instant::now(),
|
||||
),
|
||||
cover_pos: None,
|
||||
cover_left: 0.01,
|
||||
cover_top: 0.21,
|
||||
cover_bottom,
|
||||
artist_image_pos: None,
|
||||
artist_image_top: 0.5,
|
||||
artist_image_to_cover_margin: 0.01,
|
||||
force_reset_texts: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -108,7 +120,7 @@ impl GuiElem for IdleDisplay {
|
||||
);
|
||||
// update current_info
|
||||
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.c_top_label.content = if let Some(song) = self.current_info.current_song {
|
||||
info.gui_config
|
||||
@ -207,6 +219,7 @@ impl GuiElem for IdleDisplay {
|
||||
image_display(
|
||||
g,
|
||||
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().y + info.pos.height() * self.cover_top,
|
||||
info.pos.top_left().y + info.pos.height() * self.cover_bottom,
|
||||
@ -220,6 +233,9 @@ impl GuiElem for IdleDisplay {
|
||||
image_display(
|
||||
g,
|
||||
img.as_ref(),
|
||||
self.artist_image_pos
|
||||
.as_ref()
|
||||
.map(|v| rect_from_rel(v, &info.pos)),
|
||||
get_right_x(
|
||||
info.pos.top_left().x + info.pos.height() * self.cover_left,
|
||||
top,
|
||||
@ -265,12 +281,14 @@ impl GuiElem for IdleDisplay {
|
||||
let buttons_right_pos = 0.99;
|
||||
let buttons_width_max = info.pos.height() * 0.08 / 0.3 / info.pos.width();
|
||||
let buttons_width = buttons_width_max.min(0.2);
|
||||
if !self.c_buttons_custom_pos {
|
||||
self.c_buttons.config_mut().pos = Rectangle::from_tuples(
|
||||
(buttons_right_pos - buttons_width, 0.86),
|
||||
(buttons_right_pos, 0.94),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
fn config(&self) -> &GuiElemCfg {
|
||||
&self.config
|
||||
}
|
||||
|
@ -123,6 +123,7 @@ impl LibraryBrowser {
|
||||
crate::gui_base::ScrollBoxSizeUnit::Pixels,
|
||||
vec![],
|
||||
vec![],
|
||||
0.0,
|
||||
);
|
||||
let (do_something_sender, do_something_receiver) = mpsc::channel();
|
||||
let search_settings_changed = Arc::new(AtomicBool::new(false));
|
||||
@ -936,7 +937,7 @@ impl GuiElem for ListArtist {
|
||||
let selected = self.selected.clone();
|
||||
info.actions.push(GuiAction::Do(Box::new(move |gui| {
|
||||
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.exec_gui_action(GuiAction::SetDragging(Some((
|
||||
@ -1074,7 +1075,7 @@ impl GuiElem for ListAlbum {
|
||||
let selected = self.selected.clone();
|
||||
info.actions.push(GuiAction::Do(Box::new(move |gui| {
|
||||
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.exec_gui_action(GuiAction::SetDragging(Some((
|
||||
@ -1208,7 +1209,7 @@ impl GuiElem for ListSong {
|
||||
let selected = self.selected.clone();
|
||||
info.actions.push(GuiAction::Do(Box::new(move |gui| {
|
||||
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.exec_gui_action(GuiAction::SetDragging(Some((
|
||||
@ -1251,6 +1252,31 @@ impl GuiElem for ListSong {
|
||||
}
|
||||
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 {
|
||||
@ -1484,24 +1510,28 @@ impl FilterPanel {
|
||||
),
|
||||
),
|
||||
vec![0.0; 10],
|
||||
0.0,
|
||||
);
|
||||
let c_tab_filters_songs = ScrollBox::new(
|
||||
GuiElemCfg::at(Rectangle::from_tuples((VSPLIT, HEIGHT), (1.0, 1.0))),
|
||||
crate::gui_base::ScrollBoxSizeUnit::Pixels,
|
||||
FilterTab::default(),
|
||||
vec![],
|
||||
0.0,
|
||||
);
|
||||
let c_tab_filters_albums = ScrollBox::new(
|
||||
GuiElemCfg::at(Rectangle::from_tuples((VSPLIT, HEIGHT), (1.0, 1.0))).disabled(),
|
||||
crate::gui_base::ScrollBoxSizeUnit::Pixels,
|
||||
FilterTab::default(),
|
||||
vec![],
|
||||
0.0,
|
||||
);
|
||||
let c_tab_filters_artists = ScrollBox::new(
|
||||
GuiElemCfg::at(Rectangle::from_tuples((VSPLIT, HEIGHT), (1.0, 1.0))).disabled(),
|
||||
crate::gui_base::ScrollBoxSizeUnit::Pixels,
|
||||
FilterTab::default(),
|
||||
vec![],
|
||||
0.0,
|
||||
);
|
||||
let new_tab = Arc::new(AtomicUsize::new(0));
|
||||
let set_tab_1 = Arc::clone(&new_tab);
|
||||
|
@ -147,6 +147,7 @@ impl CurrentInfo {
|
||||
pub fn image_display(
|
||||
g: &mut speedy2d::Graphics2D,
|
||||
img: Option<&ImageHandle>,
|
||||
pos: Option<Rectangle>,
|
||||
left: f32,
|
||||
top: f32,
|
||||
bottom: f32,
|
||||
@ -155,8 +156,12 @@ pub fn image_display(
|
||||
if let Some(cover) = &img {
|
||||
let cover_size = cover.size();
|
||||
aspect_ratio.target = if cover_size.x > 0 && cover_size.y > 0 {
|
||||
let pos = if let Some(pos) = pos {
|
||||
pos
|
||||
} else {
|
||||
let right_x = get_right_x(left, top, bottom, aspect_ratio.value);
|
||||
let pos = Rectangle::from_tuples((left, top), (right_x, bottom));
|
||||
Rectangle::from_tuples((left, top), (right_x, bottom))
|
||||
};
|
||||
let aspect_ratio = cover_size.x as f32 / cover_size.y as f32;
|
||||
g.draw_rectangle_image(pos, cover);
|
||||
aspect_ratio
|
||||
|
@ -96,6 +96,7 @@ impl QueueViewer {
|
||||
crate::gui_base::ScrollBoxSizeUnit::Pixels,
|
||||
vec![],
|
||||
vec![],
|
||||
0.0,
|
||||
),
|
||||
c_empty_space_drag_handler: QueueEmptySpaceDragHandler::new(GuiElemCfg::at(
|
||||
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 crate::{
|
||||
gui::{DrawInfo, GuiAction, GuiElem, GuiElemCfg},
|
||||
gui::{DrawInfo, GuiAction, GuiElem, GuiElemCfg, GuiElemChildren},
|
||||
gui_anim::AnimationController,
|
||||
gui_base::{Button, Panel},
|
||||
gui_edit_song::EditorForSongs,
|
||||
gui_idle_display::IdleDisplay,
|
||||
gui_library::LibraryBrowser,
|
||||
gui_notif::NotifOverlay,
|
||||
@ -37,17 +38,12 @@ pub fn transition(p: f32) -> f32 {
|
||||
|
||||
pub struct GuiScreen {
|
||||
config: GuiElemCfg,
|
||||
c_notif_overlay: NotifOverlay,
|
||||
c_idle_display: IdleDisplay,
|
||||
c_status_bar: StatusBar,
|
||||
pub c_notif_overlay: NotifOverlay,
|
||||
pub c_idle_display: IdleDisplay,
|
||||
pub c_editing_songs: Option<EditorForSongs>,
|
||||
pub c_status_bar: StatusBar,
|
||||
pub c_settings: Settings,
|
||||
pub c_main_view: Panel<(
|
||||
Button<[Label; 1]>,
|
||||
Button<[Label; 1]>,
|
||||
LibraryBrowser,
|
||||
QueueViewer,
|
||||
Button<[Label; 1]>,
|
||||
)>,
|
||||
pub c_main_view: Panel<MainView>,
|
||||
pub c_context_menu: Option<Box<dyn GuiElem>>,
|
||||
pub idle: AnimationController<f32>,
|
||||
// pub settings: (bool, Option<Instant>),
|
||||
@ -57,6 +53,30 @@ pub struct GuiScreen {
|
||||
pub prev_mouse_pos: Vec2,
|
||||
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 {
|
||||
pub fn new(
|
||||
config: GuiElemCfg,
|
||||
@ -74,6 +94,7 @@ impl GuiScreen {
|
||||
(0.0, 0.9),
|
||||
(1.0, 1.0),
|
||||
))),
|
||||
c_editing_songs: None,
|
||||
c_idle_display: IdleDisplay::new(GuiElemCfg::default().disabled()),
|
||||
c_settings: Settings::new(
|
||||
GuiElemCfg::default().disabled(),
|
||||
@ -85,38 +106,8 @@ impl GuiScreen {
|
||||
),
|
||||
c_main_view: Panel::new(
|
||||
GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (1.0, 0.9))),
|
||||
(
|
||||
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(
|
||||
MainView {
|
||||
button_clear_queue: Button::new(
|
||||
GuiElemCfg::at(Rectangle::from_tuples((0.5, 0.0), (0.75, 0.03))),
|
||||
|_| {
|
||||
vec![GuiAction::SendToServer(
|
||||
@ -139,7 +130,37 @@ impl GuiScreen {
|
||||
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,
|
||||
hotkey: Hotkey::new_noshift(VirtualKeyCode::Escape),
|
||||
@ -173,6 +194,9 @@ impl GuiScreen {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
pub fn force_idle(&mut self) {
|
||||
self.idle.target = 1.0;
|
||||
}
|
||||
pub fn not_idle(&mut self) {
|
||||
self.last_interaction = Instant::now();
|
||||
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 {
|
||||
fn config(&self) -> &GuiElemCfg {
|
||||
@ -207,15 +237,19 @@ impl GuiElem for GuiScreen {
|
||||
}
|
||||
fn children(&mut self) -> Box<dyn Iterator<Item = &mut dyn GuiElem> + '_> {
|
||||
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(),
|
||||
]
|
||||
.into_iter()
|
||||
.chain(self.c_editing_songs.as_mut().map(|v| v.elem_mut()))
|
||||
.chain([
|
||||
self.c_status_bar.elem_mut(),
|
||||
self.c_settings.elem_mut(),
|
||||
self.c_main_view.elem_mut(),
|
||||
]
|
||||
.into_iter()
|
||||
.chain(self.c_context_menu.as_mut().map(|v| v.elem_mut())),
|
||||
]),
|
||||
),
|
||||
)
|
||||
}
|
||||
fn any(&self) -> &dyn std::any::Any {
|
||||
@ -301,9 +335,7 @@ impl GuiElem for GuiScreen {
|
||||
// animations: idle
|
||||
if idle_changed {
|
||||
let enable_normal_ui = self.idle.value < 1.0;
|
||||
self.c_main_view.config_mut().enabled = enable_normal_ui;
|
||||
// self.c_settings.config_mut().enabled = enable_normal_ui;
|
||||
self.c_status_bar.config_mut().enabled = enable_normal_ui;
|
||||
self.set_normal_ui_enabled(enable_normal_ui);
|
||||
if let Some(h) = &info.helper {
|
||||
h.set_cursor_visible(enable_normal_ui);
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ impl Settings {
|
||||
scroll_sensitivity_pages,
|
||||
),
|
||||
vec![],
|
||||
0.0,
|
||||
),
|
||||
c_background: Panel::with_background(GuiElemCfg::default().w_mouse(), (), Color::BLACK),
|
||||
}
|
||||
|
@ -1,11 +1,10 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle};
|
||||
use speedy2d::{dimen::Vec2, shape::Rectangle};
|
||||
|
||||
use crate::{
|
||||
gui::{DrawInfo, GuiElem, GuiElemCfg},
|
||||
gui_anim::AnimationController,
|
||||
gui_base::Panel,
|
||||
gui_playback::{image_display, CurrentInfo},
|
||||
gui_playpause::PlayPause,
|
||||
gui_text::AdvancedLabel,
|
||||
@ -17,6 +16,7 @@ pub struct StatusBar {
|
||||
current_info: CurrentInfo,
|
||||
cover_aspect_ratio: AnimationController<f32>,
|
||||
c_song_label: AdvancedLabel,
|
||||
pub force_reset_texts: bool,
|
||||
c_buttons: PlayPause,
|
||||
}
|
||||
|
||||
@ -36,6 +36,7 @@ impl StatusBar {
|
||||
Instant::now(),
|
||||
),
|
||||
c_song_label: AdvancedLabel::new(GuiElemCfg::default(), Vec2::new(0.0, 0.5), vec![]),
|
||||
force_reset_texts: false,
|
||||
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) {
|
||||
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.c_song_label.content = if let Some(song) = self.current_info.current_song {
|
||||
info.gui_config
|
||||
@ -101,6 +102,7 @@ impl GuiElem for StatusBar {
|
||||
image_display(
|
||||
g,
|
||||
cover.as_ref(),
|
||||
None,
|
||||
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.95,
|
||||
|
@ -10,21 +10,24 @@ use std::{
|
||||
use clap::{Parser, Subcommand};
|
||||
#[cfg(feature = "speedy2d")]
|
||||
use gui::GuiEvent;
|
||||
#[cfg(feature = "playback")]
|
||||
use musicdb_lib::player::Player;
|
||||
use musicdb_lib::{
|
||||
data::{
|
||||
database::{ClientIo, Database},
|
||||
CoverId, SongId,
|
||||
},
|
||||
load::ToFromBytes,
|
||||
player::Player,
|
||||
server::{get, Command},
|
||||
};
|
||||
use speedy2d::color::Color;
|
||||
#[cfg(feature = "speedy2d")]
|
||||
mod gui;
|
||||
#[cfg(feature = "speedy2d")]
|
||||
mod gui_anim;
|
||||
#[cfg(feature = "speedy2d")]
|
||||
mod gui_base;
|
||||
mod gui_edit_song;
|
||||
#[cfg(feature = "speedy2d")]
|
||||
mod gui_idle_display;
|
||||
#[cfg(feature = "speedy2d")]
|
||||
@ -47,6 +50,8 @@ mod gui_statusbar;
|
||||
mod gui_text;
|
||||
#[cfg(feature = "speedy2d")]
|
||||
mod gui_wrappers;
|
||||
#[cfg(feature = "merscfg")]
|
||||
mod merscfg;
|
||||
#[cfg(feature = "speedy2d")]
|
||||
mod textcfg;
|
||||
|
||||
@ -65,8 +70,10 @@ enum Mode {
|
||||
/// graphical user interface
|
||||
Gui,
|
||||
/// 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 },
|
||||
/// 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,
|
||||
}
|
||||
|
||||
@ -97,23 +104,31 @@ fn main() {
|
||||
let mut con = con.try_clone().unwrap();
|
||||
// this is all you need to keep the db in sync
|
||||
thread::spawn(move || {
|
||||
#[cfg(feature = "playback")]
|
||||
let mut player =
|
||||
if matches!(mode, Mode::SyncplayerLocal { .. } | Mode::SyncplayerNetwork) {
|
||||
Some(Player::new().unwrap().without_sending_commands())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
#[allow(unused_labels)]
|
||||
'ifstatementworkaround: {
|
||||
// use if+break instead of if-else because we can't #[cfg(feature)] the if statement,
|
||||
// since we want the else part to run if the feature is disabled
|
||||
#[cfg(feature = "playback")]
|
||||
if let Mode::SyncplayerLocal { lib_dir } = mode {
|
||||
let mut db = database.lock().unwrap();
|
||||
db.lib_directory = lib_dir;
|
||||
} else {
|
||||
break 'ifstatementworkaround;
|
||||
}
|
||||
let mut db = database.lock().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(
|
||||
musicdb_lib::server::get::Client::new(BufReader::new(client_con)).unwrap(),
|
||||
)));
|
||||
};
|
||||
}
|
||||
loop {
|
||||
#[cfg(feature = "playback")]
|
||||
if let Some(player) = &mut player {
|
||||
let mut db = database.lock().unwrap();
|
||||
if db.is_client_init() {
|
||||
@ -121,6 +136,7 @@ fn main() {
|
||||
}
|
||||
}
|
||||
let update = Command::from_bytes(&mut con).unwrap();
|
||||
#[cfg(feature = "playback")]
|
||||
if let Some(player) = &mut player {
|
||||
player.handle_command(&update);
|
||||
}
|
||||
@ -154,6 +170,7 @@ fn main() {
|
||||
)
|
||||
};
|
||||
}
|
||||
#[cfg(feature = "playback")]
|
||||
Mode::SyncplayerLocal { .. } | Mode::SyncplayerNetwork => {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
[dependencies]
|
||||
awedio = "0.2.0"
|
||||
awedio = { version = "0.2.0", optional = true }
|
||||
base64 = "0.21.2"
|
||||
rand = "0.8.5"
|
||||
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 load;
|
||||
#[cfg(feature = "playback")]
|
||||
pub mod player;
|
||||
pub mod server;
|
||||
|
@ -1,11 +1,8 @@
|
||||
pub mod get;
|
||||
|
||||
use std::{
|
||||
io::{BufRead, BufReader, Read, Write},
|
||||
net::{SocketAddr, TcpListener},
|
||||
io::{Read, Write},
|
||||
sync::{mpsc, Arc, Mutex},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@ -18,8 +15,15 @@ use crate::{
|
||||
AlbumId, ArtistId, SongId,
|
||||
},
|
||||
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)]
|
||||
@ -84,6 +88,7 @@ impl Command {
|
||||
/// a) initialize new connections using db.init_connection() to synchronize the new client
|
||||
/// 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.
|
||||
#[cfg(feature = "playback")]
|
||||
pub fn run_server(
|
||||
database: Arc<Mutex<Database>>,
|
||||
addr_tcp: Option<SocketAddr>,
|
||||
|
@ -10,7 +10,7 @@ axum = { version = "0.6.19", features = ["headers"] }
|
||||
clap = { version = "4.4.6", features = ["derive"] }
|
||||
futures = "0.3.28"
|
||||
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_json = "1.0"
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
|
Loading…
Reference in New Issue
Block a user