1
0
mirror of https://github.com/Dummi26/musicdb.git synced 2025-05-16 08:26:15 +02:00
musicdb/musicdb-client/src/gui_screen.rs

413 lines
14 KiB
Rust
Executable File

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<GuiElem>,
pub idle: (bool, Option<Instant>),
pub settings: (bool, Option<Instant>),
pub edit_panel: (bool, Option<Instant>),
pub last_interaction: Instant,
idle_timeout: Option<f64>,
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<Instant>), 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<dyn Iterator<Item = &mut GuiElem> + '_> {
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<dyn GuiElemTrait> {
Box::new(self.clone())
}
fn key_watch(
&mut self,
_modifiers: speedy2d::window::ModifiersState,
_down: bool,
_key: Option<speedy2d::window::VirtualKeyCode>,
_scan: speedy2d::window::KeyScancode,
) -> Vec<GuiAction> {
self.not_idle();
vec![]
}
fn mouse_down(&mut self, _button: speedy2d::window::MouseButton) -> Vec<GuiAction> {
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::<StatusBar>()
.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::<Settings>()
.unwrap()
.get_timeout_val();
}
}
}
#[derive(Clone)]
pub struct StatusBar {
config: GuiElemCfg,
children: Vec<GuiElem>,
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<Color>) {
self.children[Self::index_bgpanel()]
.inner
.any_mut()
.downcast_mut::<Panel>()
.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<dyn Iterator<Item = &mut GuiElem> + '_> {
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<dyn GuiElemTrait> {
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::<CurrentSong>()
.unwrap();
current_song.set_idle_mode(self.idle_mode);
let play_pause = self.children[Self::index_play_pause_toggle()]
.inner
.any_mut()
.downcast_mut::<PlayPauseToggle>()
.unwrap();
// - - - - -
self.idle_prev = self.idle_mode;
}
}
}