use std::{ io::{Read, Write}, time::{Duration, Instant}, }; use musicdb_lib::server::get; use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle, Graphics2D}; use crate::{ gui::{DrawInfo, GuiAction, GuiElem, GuiElemCfg, GuiElemTrait}, gui_base::{Button, Panel}, gui_library::LibraryBrowser, gui_playback::{CurrentSong, PlayPauseToggle}, gui_queue::QueueViewer, gui_settings::Settings, gui_text::Label, }; /* The root gui element. Contains the Library, Queue, StatusBar, and sometimes Settings elements. Resizes these elements to show/hide the settings menu and to smoothly switch to/from idle mode. */ /// calculates f(p), where f(x) = 3x^2 - 2x^3, because /// f(0) = 0 /// f(0.5) = 0.5 /// f(1) = 1 /// f'(0) = f'(1) = 0 /// -> smooth animation, fast to calculate pub fn transition(p: f32) -> f32 { 3.0 * p * p - 2.0 * p * p * p } #[derive(Clone)] pub struct GuiScreen { config: GuiElemCfg, /// 0: StatusBar / Idle display /// 1: Settings /// 2: Panel for Main view /// 3: Edit Panel children: Vec, pub idle: (bool, Option), pub settings: (bool, Option), pub edit_panel: (bool, Option), pub last_interaction: Instant, idle_timeout: Option, pub prev_mouse_pos: Vec2, } impl GuiScreen { pub fn open_edit(&mut self, mut edit: GuiElem) { if !self.edit_panel.0 { self.edit_panel = (true, Some(Instant::now())); edit.inner.config_mut().pos = Rectangle::from_tuples((-0.5, 0.0), (0.0, 0.9)); } else { edit.inner.config_mut().pos = Rectangle::from_tuples((0.0, 0.0), (0.5, 0.9)); } if let Some(prev) = self.children.get_mut(3) { prev.inner.config_mut().enabled = false; } self.children.insert(3, edit); } pub fn close_edit(&mut self) { if self.children.len() > 4 { self.children.remove(3); self.children[3].inner.config_mut().enabled = true; } else if self.edit_panel.0 { self.edit_panel = (false, Some(Instant::now())); } } pub fn new( config: GuiElemCfg, line_height: f32, scroll_sensitivity_pixels: f64, scroll_sensitivity_lines: f64, scroll_sensitivity_pages: f64, ) -> Self { Self { config: config.w_keyboard_watch().w_mouse(), children: vec![ GuiElem::new(StatusBar::new( GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.9), (1.0, 1.0))), true, )), GuiElem::new(Settings::new( GuiElemCfg::default().disabled(), line_height, scroll_sensitivity_pixels, scroll_sensitivity_lines, scroll_sensitivity_pages, )), GuiElem::new(Panel::new( GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (1.0, 0.9))), vec![ GuiElem::new(Button::new( GuiElemCfg::at(Rectangle::from_tuples((0.75, 0.0), (0.875, 0.03))), |_| vec![GuiAction::OpenSettings(true)], vec![GuiElem::new(Label::new( GuiElemCfg::default(), "Settings".to_string(), Color::WHITE, None, Vec2::new(0.5, 0.5), ))], )), GuiElem::new(Button::new( GuiElemCfg::at(Rectangle::from_tuples((0.875, 0.0), (1.0, 0.03))), |_| vec![GuiAction::Exit], vec![GuiElem::new(Label::new( GuiElemCfg::default(), "Exit".to_string(), Color::WHITE, None, Vec2::new(0.5, 0.5), ))], )), GuiElem::new(LibraryBrowser::new(GuiElemCfg::at(Rectangle::from_tuples( (0.0, 0.0), (0.5, 1.0), )))), GuiElem::new(QueueViewer::new(GuiElemCfg::at(Rectangle::from_tuples( (0.5, 0.03), (1.0, 1.0), )))), GuiElem::new(Button::new( GuiElemCfg::at(Rectangle::from_tuples((0.5, 0.0), (0.75, 0.03))), |_| { vec![GuiAction::SendToServer( musicdb_lib::server::Command::QueueUpdate( vec![], musicdb_lib::data::queue::QueueContent::Folder( 0, vec![], String::new(), ) .into(), ), )] }, vec![GuiElem::new(Label::new( GuiElemCfg::default(), "Clear Queue".to_string(), Color::WHITE, None, Vec2::new(0.5, 0.5), ))], )), ], )), ], idle: (false, None), settings: (false, None), edit_panel: (false, None), last_interaction: Instant::now(), idle_timeout: Some(60.0), prev_mouse_pos: Vec2::ZERO, } } fn get_prog(v: &mut (bool, Option), seconds: f32) -> f32 { if let Some(since) = &mut v.1 { let prog = since.elapsed().as_secs_f32() / seconds; if prog >= 1.0 { v.1 = None; if v.0 { 1.0 } else { 0.0 } } else { if v.0 { prog } else { 1.0 - prog } } } else if v.0 { 1.0 } else { 0.0 } } fn not_idle(&mut self) { self.last_interaction = Instant::now(); if self.idle.0 { self.idle = (false, Some(Instant::now())); } } fn idle_check(&mut self) { if !self.idle.0 { if let Some(dur) = &self.idle_timeout { if self.last_interaction.elapsed().as_secs_f64() > *dur { self.idle = (true, Some(Instant::now())); } } } } } impl GuiElemTrait for GuiScreen { fn config(&self) -> &GuiElemCfg { &self.config } fn config_mut(&mut self) -> &mut GuiElemCfg { &mut self.config } fn children(&mut self) -> Box + '_> { Box::new(self.children.iter_mut()) } fn any(&self) -> &dyn std::any::Any { self } fn any_mut(&mut self) -> &mut dyn std::any::Any { self } fn clone_gui(&self) -> Box { Box::new(self.clone()) } fn key_watch( &mut self, _modifiers: speedy2d::window::ModifiersState, _down: bool, _key: Option, _scan: speedy2d::window::KeyScancode, ) -> Vec { self.not_idle(); vec![] } fn mouse_down(&mut self, _button: speedy2d::window::MouseButton) -> Vec { self.not_idle(); vec![] } fn draw(&mut self, info: &mut DrawInfo, _g: &mut Graphics2D) { // idle stuff if self.prev_mouse_pos != info.mouse_pos { self.prev_mouse_pos = info.mouse_pos; self.not_idle(); } else if !self.idle.0 && self.config.pixel_pos.size() != info.pos.size() { // resizing prevents idle, but doesn't un-idle self.not_idle(); } self.idle_check(); // request_redraw for animations if self.idle.1.is_some() || self.settings.1.is_some() || self.edit_panel.1.is_some() { if let Some(h) = &info.helper { h.request_redraw() } } // animations: idle if self.idle.1.is_some() { let seconds = if self.idle.0 { 2.0 } else { 0.5 }; let p1 = Self::get_prog(&mut self.idle, seconds); if !self.idle.0 || self.idle.1.is_none() { if let Some(h) = &info.helper { h.set_cursor_visible(!self.idle.0); if self.settings.0 { self.children[1].inner.config_mut().enabled = !self.idle.0; } if self.edit_panel.0 { if let Some(c) = self.children.get_mut(3) { c.inner.config_mut().enabled = !self.idle.0; } } self.children[2].inner.config_mut().enabled = !self.idle.0; } } let p = transition(p1); self.children[0].inner.config_mut().pos = Rectangle::from_tuples((0.0, 0.9 - 0.9 * p), (1.0, 1.0)); self.children[0] .inner .any_mut() .downcast_mut::() .unwrap() .idle_mode = p1; } // animations: settings if self.settings.1.is_some() { let p1 = Self::get_prog(&mut self.settings, 0.3); let p = transition(p1); let cfg = self.children[1].inner.config_mut(); cfg.enabled = p > 0.0; cfg.pos = Rectangle::from_tuples((0.0, 0.9 - 0.9 * p), (1.0, 0.9)); } // animations: edit_panel if self.edit_panel.1.is_some() { let p1 = Self::get_prog(&mut self.edit_panel, 0.3); let p = transition(p1); if let Some(c) = self.children.get_mut(3) { c.inner.config_mut().enabled = p > 0.0; c.inner.config_mut().pos = Rectangle::from_tuples((-0.5 + 0.5 * p, 0.0), (0.5 * p, 0.9)); } if !self.edit_panel.0 && p == 0.0 { while self.children.len() > 3 { self.children.pop(); } } self.children[2].inner.config_mut().pos = Rectangle::from_tuples((0.5 * p, 0.0), (1.0 + 0.5 * p, 0.9)); } // set idle timeout (only when settings are open) if self.settings.0 || self.settings.1.is_some() { self.idle_timeout = self.children[1] .inner .any() .downcast_ref::() .unwrap() .get_timeout_val(); } } } #[derive(Clone)] pub struct StatusBar { config: GuiElemCfg, children: Vec, idle_mode: f32, idle_prev: f32, } impl StatusBar { pub fn new(config: GuiElemCfg, playing: bool) -> Self { Self { config, children: vec![ GuiElem::new(CurrentSong::new(GuiElemCfg::at(Rectangle::new( Vec2::ZERO, Vec2::new(0.8, 1.0), )))), GuiElem::new(PlayPauseToggle::new( GuiElemCfg::at(Rectangle::from_tuples((0.85, 0.0), (0.95, 1.0))), playing, )), GuiElem::new(Panel::new(GuiElemCfg::default(), vec![])), ], idle_mode: 0.0, idle_prev: 0.0, } } const fn index_current_song() -> usize { 0 } const fn index_play_pause_toggle() -> usize { 1 } const fn index_bgpanel() -> usize { 2 } pub fn set_background(&mut self, bg: Option) { self.children[Self::index_bgpanel()] .inner .any_mut() .downcast_mut::() .unwrap() .background = bg; } } impl GuiElemTrait for StatusBar { fn config(&self) -> &GuiElemCfg { &self.config } fn config_mut(&mut self) -> &mut GuiElemCfg { &mut self.config } fn children(&mut self) -> Box + '_> { Box::new(self.children.iter_mut()) } fn any(&self) -> &dyn std::any::Any { self } fn any_mut(&mut self) -> &mut dyn std::any::Any { self } fn clone_gui(&self) -> Box { Box::new(self.clone()) } fn draw(&mut self, info: &mut DrawInfo, g: &mut Graphics2D) { // the line that separates this section from the rest of the ui. // fades away when idle_mode approaches 1.0 if self.idle_mode < 1.0 { g.draw_line( info.pos.top_left(), info.pos.top_right(), 2.0, Color::from_rgba(1.0, 1.0, 1.0, 1.0 - self.idle_mode), ); } if self.idle_mode != self.idle_prev { // if exiting the moving stage, set background to transparent. // if entering the moving stage, set background to black. if self.idle_mode == 1.0 || self.idle_mode == 0.0 { self.set_background(None); } else if self.idle_prev == 1.0 || self.idle_prev == 0.0 { self.set_background(Some(Color::BLACK)); } // position the text let current_song = self.children[Self::index_current_song()] .inner .any_mut() .downcast_mut::() .unwrap(); current_song.set_idle_mode(self.idle_mode); let play_pause = self.children[Self::index_play_pause_toggle()] .inner .any_mut() .downcast_mut::() .unwrap(); // - - - - - self.idle_prev = self.idle_mode; } } }