From 168f51a5fc985a7d1dfe4416d0a694ef9b693e80 Mon Sep 17 00:00:00 2001 From: Mark Date: Sat, 25 Nov 2023 17:02:35 +0100 Subject: [PATCH] add play/pause/stop button to status bar and idle display --- musicdb-client/src/config_gui.toml | 11 +- musicdb-client/src/gui_idle_display.rs | 15 +- musicdb-client/src/gui_playpause.rs | 207 +++++++++++++++++++++++++ musicdb-client/src/gui_statusbar.rs | 16 +- musicdb-client/src/main.rs | 2 + 5 files changed, 241 insertions(+), 10 deletions(-) create mode 100644 musicdb-client/src/gui_playpause.rs diff --git a/musicdb-client/src/config_gui.toml b/musicdb-client/src/config_gui.toml index bcbede9..abb4501 100755 --- a/musicdb-client/src/config_gui.toml +++ b/musicdb-client/src/config_gui.toml @@ -31,11 +31,8 @@ font = '' status_bar = '''\t \s0.5;?\A#\c505050by \c593D6E\A##?\a#?\A# ##\c505050on \c264524\a##\c808080?%>Year=%# (%>Year=%)## | \d''' -idle_top = '\t' +idle_top = '''\t \s0.5;\c505050(\d?%>Genre=%#, %>Genre=%##) +?\A#\c505050by \c593D6E\A##?\a#?\A# ##\c505050on \c264524\a##\c808080?%>Year=%# (%>Year=%)##''' -idle_side1 = '''?\A#\c505050by \c593D6E\A## -?\a#\c505050on \c264524\a##''' -idle_side2 = ''' -\c505050\d -%>Year=% -%>Genre=% ''' +idle_side1 = '' +idle_side2 = '' diff --git a/musicdb-client/src/gui_idle_display.rs b/musicdb-client/src/gui_idle_display.rs index 9b00d7c..3078171 100644 --- a/musicdb-client/src/gui_idle_display.rs +++ b/musicdb-client/src/gui_idle_display.rs @@ -8,6 +8,7 @@ use crate::{ gui_anim::AnimationController, gui_base::Button, gui_playback::{get_right_x, image_display, CurrentInfo}, + gui_playpause::PlayPause, gui_text::{AdvancedLabel, Label}, }; @@ -20,6 +21,7 @@ pub struct IdleDisplay { c_top_label: AdvancedLabel, c_side1_label: AdvancedLabel, c_side2_label: AdvancedLabel, + c_buttons: PlayPause, cover_aspect_ratio: AnimationController, artist_image_aspect_ratio: AnimationController, cover_left: f32, @@ -33,6 +35,7 @@ pub struct IdleDisplay { impl IdleDisplay { pub fn new(config: GuiElemCfg) -> Self { + let cover_bottom = 0.79; Self { config, idle_mode: 0.0, @@ -56,6 +59,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()), cover_aspect_ratio: AnimationController::new( 1.0, 1.0, @@ -76,7 +80,7 @@ impl IdleDisplay { ), cover_left: 0.01, cover_top: 0.21, - cover_bottom: 0.79, + cover_bottom, artist_image_top: 0.5, artist_image_to_cover_margin: 0.01, } @@ -91,6 +95,7 @@ impl GuiElem for IdleDisplay { self.c_top_label.elem_mut(), self.c_side1_label.elem_mut(), self.c_side2_label.elem_mut(), + self.c_buttons.elem_mut(), ] .into_iter(), ) @@ -256,6 +261,14 @@ impl GuiElem for IdleDisplay { ); self.c_side2_label.config_mut().pos = Rectangle::from_tuples((left, ai_top), (max_right, bottom)); + // limit width of c_buttons + 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); + 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 { diff --git a/musicdb-client/src/gui_playpause.rs b/musicdb-client/src/gui_playpause.rs new file mode 100644 index 0000000..8a6e53a --- /dev/null +++ b/musicdb-client/src/gui_playpause.rs @@ -0,0 +1,207 @@ +use musicdb_lib::server::Command; +use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle, Graphics2D}; + +use crate::{ + gui::{DrawInfo, GuiAction, GuiElem, GuiElemCfg}, + gui_base::{Button, Panel}, +}; + +pub struct PlayPause { + config: GuiElemCfg, + to_zero: Button<[Panel<()>; 1]>, + play_pause: Button<[PlayPauseDisplay; 1]>, + to_end: Button<[NextSongShape; 1]>, +} + +impl PlayPause { + pub fn new(config: GuiElemCfg) -> Self { + Self { + config, + to_zero: Button::new( + GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (0.3, 1.0))), + |_| vec![GuiAction::SendToServer(Command::Stop)], + [Panel::with_background( + GuiElemCfg::at(Rectangle::from_tuples((0.2, 0.2), (0.8, 0.8))), + (), + Color::MAGENTA, + )], + ), + play_pause: Button::new( + GuiElemCfg::at(Rectangle::from_tuples((0.35, 0.0), (0.65, 1.0))), + |btn| { + vec![GuiAction::SendToServer(if btn.children[0].is_playing { + Command::Pause + } else { + Command::Resume + })] + }, + [PlayPauseDisplay::new(GuiElemCfg::at( + Rectangle::from_tuples((0.2, 0.2), (0.8, 0.8)), + ))], + ), + to_end: Button::new( + GuiElemCfg::at(Rectangle::from_tuples((0.7, 0.0), (1.0, 1.0))), + |_| vec![GuiAction::SendToServer(Command::NextSong)], + [NextSongShape::new(GuiElemCfg::at(Rectangle::from_tuples( + (0.2, 0.2), + (0.8, 0.8), + )))], + ), + } + } +} + +struct PlayPauseDisplay { + config: GuiElemCfg, + is_playing: bool, +} +impl PlayPauseDisplay { + pub fn new(config: GuiElemCfg) -> Self { + Self { + config, + is_playing: false, + } + } +} +impl GuiElem for PlayPauseDisplay { + fn draw(&mut self, info: &mut DrawInfo, g: &mut Graphics2D) { + self.is_playing = info.database.playing; + if info.database.playing { + g.draw_rectangle( + Rectangle::from_tuples( + ( + info.pos.top_left().x + info.pos.width() * 0.2, + info.pos.top_left().y, + ), + ( + info.pos.top_left().x + info.pos.width() * 0.4, + info.pos.bottom_right().y, + ), + ), + Color::BLUE, + ); + g.draw_rectangle( + Rectangle::from_tuples( + ( + info.pos.bottom_right().x - info.pos.width() * 0.4, + info.pos.top_left().y, + ), + ( + info.pos.bottom_right().x - info.pos.width() * 0.2, + info.pos.bottom_right().y, + ), + ), + Color::BLUE, + ); + } else { + g.draw_triangle( + [ + *info.pos.top_left(), + Vec2::new( + info.pos.bottom_right().x, + (info.pos.top_left().y + info.pos.bottom_right().y) / 2.0, + ), + info.pos.bottom_left(), + ], + Color::GREEN, + ); + } + } + fn config(&self) -> &GuiElemCfg { + &self.config + } + fn config_mut(&mut self) -> &mut GuiElemCfg { + &mut self.config + } + fn children(&mut self) -> Box + '_> { + Box::new([].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 + } +} + +struct NextSongShape { + config: GuiElemCfg, +} +impl NextSongShape { + pub fn new(config: GuiElemCfg) -> Self { + Self { config } + } +} +impl GuiElem for NextSongShape { + fn draw(&mut self, info: &mut DrawInfo, g: &mut Graphics2D) { + let top = *info.pos.top_left(); + let bottom = info.pos.bottom_left(); + let right = Vec2::new(info.pos.bottom_right().x, (top.y + bottom.y) / 2.0); + g.draw_triangle([top, right, bottom], Color::CYAN); + let half_width = info.pos.width() * 0.04; + let top_right = Vec2::new(info.pos.top_right().x - half_width, info.pos.top_left().y); + let bottom_right = Vec2::new( + info.pos.top_right().x - half_width, + info.pos.bottom_right().y, + ); + g.draw_line(top_right, bottom_right, 2.0 * half_width, Color::CYAN); + } + fn config(&self) -> &GuiElemCfg { + &self.config + } + fn config_mut(&mut self) -> &mut GuiElemCfg { + &mut self.config + } + fn children(&mut self) -> Box + '_> { + Box::new([].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 + } +} + +impl GuiElem for PlayPause { + fn config(&self) -> &GuiElemCfg { + &self.config + } + fn config_mut(&mut self) -> &mut GuiElemCfg { + &mut self.config + } + fn children(&mut self) -> Box + '_> { + Box::new( + [ + self.to_zero.elem_mut(), + self.play_pause.elem_mut(), + self.to_end.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 + } +} diff --git a/musicdb-client/src/gui_statusbar.rs b/musicdb-client/src/gui_statusbar.rs index 7b3755b..29b95d6 100644 --- a/musicdb-client/src/gui_statusbar.rs +++ b/musicdb-client/src/gui_statusbar.rs @@ -5,7 +5,9 @@ use speedy2d::{color::Color, 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, }; @@ -15,6 +17,7 @@ pub struct StatusBar { current_info: CurrentInfo, cover_aspect_ratio: AnimationController, c_song_label: AdvancedLabel, + c_buttons: PlayPause, } impl StatusBar { @@ -33,13 +36,14 @@ impl StatusBar { Instant::now(), ), c_song_label: AdvancedLabel::new(GuiElemCfg::default(), Vec2::new(0.0, 0.5), vec![]), + c_buttons: PlayPause::new(GuiElemCfg::default()), } } } impl GuiElem for StatusBar { fn children(&mut self) -> Box + '_> { - Box::new([self.c_song_label.elem_mut()].into_iter()) + Box::new([self.c_song_label.elem_mut(), self.c_buttons.elem_mut()].into_iter()) } fn draw(&mut self, info: &mut DrawInfo, g: &mut speedy2d::Graphics2D) { self.current_info.update(info, g); @@ -71,12 +75,20 @@ impl GuiElem for StatusBar { if let Some(h) = &info.helper { h.request_redraw(); } + // limit width of c_buttons + let buttons_right_pos = 0.99; + let buttons_width_max = info.pos.height() * 0.7 / 0.3 / info.pos.width(); + let buttons_width = buttons_width_max.min(0.2); + self.c_buttons.config_mut().pos = Rectangle::from_tuples( + (buttons_right_pos - buttons_width, 0.15), + (buttons_right_pos, 0.85), + ); self.c_song_label.config_mut().pos = Rectangle::from_tuples( ( self.cover_aspect_ratio.value * info.pos.height() / info.pos.width(), 0.0, ), - (1.0, 1.0), + (buttons_right_pos - buttons_width, 1.0), ); } // draw cover diff --git a/musicdb-client/src/main.rs b/musicdb-client/src/main.rs index 9fd5f90..1f51787 100755 --- a/musicdb-client/src/main.rs +++ b/musicdb-client/src/main.rs @@ -34,6 +34,8 @@ mod gui_notif; #[cfg(feature = "speedy2d")] mod gui_playback; #[cfg(feature = "speedy2d")] +mod gui_playpause; +#[cfg(feature = "speedy2d")] mod gui_queue; #[cfg(feature = "speedy2d")] mod gui_screen;