diff --git a/src/gui/draw.rs b/src/gui/draw.rs index f733be2..64688d7 100644 --- a/src/gui/draw.rs +++ b/src/gui/draw.rs @@ -18,6 +18,18 @@ enum TrackAction { } impl SoundpadGui { + fn get_volume_icon(volume: f32) -> &'static str { + if volume > 0.7 { + icons::ICON_VOLUME_UP + } else if volume <= 0.0 { + icons::ICON_VOLUME_OFF + } else if volume < 0.3 { + icons::ICON_VOLUME_MUTE + } else { + icons::ICON_VOLUME_DOWN + } + } + pub fn draw_waiting_for_daemon(&mut self, ui: &mut Ui) { ui.centered_and_justified(|ui| { ui.label( @@ -215,17 +227,10 @@ impl SoundpadGui { // -------------------------------- // ---------- Volume Icon ---------- - let volume_icon = if track.volume > 0.7 { - icons::ICON_VOLUME_UP - } else if track.volume == 0.0 { - icons::ICON_VOLUME_OFF - } else if track.volume < 0.3 { - icons::ICON_VOLUME_MUTE - } else { - icons::ICON_VOLUME_DOWN - }; - let volume_icon = Label::new(RichText::new(volume_icon).size(18.0)); - ui.add_sized([30.0, 30.0], volume_icon); + let volume_icon = Self::get_volume_icon(track.volume); + let volume_label = Label::new(RichText::new(volume_icon).size(18.0)); + ui.add_sized([30.0, 30.0], volume_label) + .on_hover_text(format!("Volume: {:.0}%", track.volume * 100.0)); // -------------------------------- // ---------- Volume Slider ---------- @@ -396,7 +401,7 @@ impl SoundpadGui { fn draw_footer(&mut self, ui: &mut Ui) { ui.add_space(5.0); - ui.horizontal_top(|ui| { + ui.horizontal(|ui| { // ---------- Microphone selection ---------- let mut mics: Vec<(&String, &String)> = self.audio_player_state.all_inputs.iter().collect(); @@ -405,6 +410,7 @@ impl SoundpadGui { let mut selected_input = self.audio_player_state.current_input.to_owned(); let prev_input = selected_input.to_owned(); ComboBox::from_label("Choose microphone") + .height(30.0) .selected_text( self.audio_player_state .all_inputs @@ -422,10 +428,40 @@ impl SoundpadGui { } // -------------------------------- + // ---------- Master Volume Slider ---------- + let volume_icon = Self::get_volume_icon(self.audio_player_state.volume); + let volume_label = Label::new(RichText::new(volume_icon).size(18.0)); + ui.add_sized([18.0, 18.0], volume_label) + .on_hover_text(format!( + "Master Volume: {:.0}%", + self.audio_player_state.volume * 100.0 + )); + + let should_update_volume = !self.app_state.volume_dragged + && self + .app_state + .ignore_volume_update_until + .map(|t| Instant::now() > t) + .unwrap_or(true); + + if should_update_volume { + self.app_state.volume_slider_value = self.audio_player_state.volume; + } + + let volume_slider = Slider::new(&mut self.app_state.volume_slider_value, 0.0..=1.0) + .show_value(false) + .step_by(0.01); + let volume_slider_response = ui.add_sized([150.0, 18.0], volume_slider); + if volume_slider_response.drag_stopped() { + self.app_state.volume_dragged = true; + } + // ------------------------------------------ + ui.add_space(ui.available_width() - 18.0 - ui.spacing().item_spacing.x); // ---------- Settings button ---------- - let settings_button = Button::new(icons::ICON_SETTINGS).frame(false); + let settings_button = + Button::new(icons::ICON_SETTINGS.atom_size(Vec2::new(18.0, 18.0))).frame(false); let settings_button_response = ui.add_sized([18.0, 18.0], settings_button); if settings_button_response.clicked() { self.app_state.show_settings = true; diff --git a/src/gui/mod.rs b/src/gui/mod.rs index bb1934c..b3f6bab 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -17,9 +17,9 @@ use pwsp::{ }, }; use rfd::FileDialog; -use std::path::PathBuf; use std::{ error::Error, + path::PathBuf, sync::{Arc, Mutex}, }; diff --git a/src/gui/update.rs b/src/gui/update.rs index 935a8d4..a38a360 100644 --- a/src/gui/update.rs +++ b/src/gui/update.rs @@ -3,7 +3,10 @@ use eframe::{App, Frame as EFrame}; use egui::{CentralPanel, Context}; use pwsp::{ types::socket::Request, - utils::{daemon::is_daemon_running, gui::make_request_sync}, + utils::{ + daemon::{get_daemon_config, is_daemon_running}, + gui::make_request_sync, + }, }; use std::time::{Duration, Instant}; @@ -40,6 +43,24 @@ impl App for SoundpadGui { } } + if self.app_state.volume_dragged { + make_request_sync(Request::set_volume( + self.app_state.volume_slider_value, + None, + )) + .ok(); + + self.app_state.volume_dragged = false; + self.app_state.ignore_volume_update_until = + Some(Instant::now() + Duration::from_millis(200)); + + if self.config.save_volume { + let mut daemon_config = get_daemon_config(); + daemon_config.default_volume = Some(self.app_state.volume_slider_value); + daemon_config.save_to_file().ok(); + } + } + { let guard = self.audio_player_state_shared.lock().unwrap(); self.audio_player_state = guard.clone(); diff --git a/src/types/gui.rs b/src/types/gui.rs index 2d7ce04..ae8af2c 100644 --- a/src/types/gui.rs +++ b/src/types/gui.rs @@ -12,8 +12,10 @@ use std::{ pub struct TrackUiState { pub position_slider_value: f32, pub volume_slider_value: f32, + pub position_dragged: bool, pub volume_dragged: bool, + pub ignore_position_update_until: Option, pub ignore_volume_update_until: Option, } @@ -25,6 +27,11 @@ pub struct AppState { pub track_ui_states: HashMap, pub show_settings: bool, + pub volume_dragged: bool, + + pub volume_slider_value: f32, + + pub ignore_volume_update_until: Option, pub current_dir: Option, pub dirs: HashSet, @@ -43,6 +50,8 @@ pub struct AudioPlayerState { pub tracks: Vec, + pub volume: f32, // Master volume + pub current_input: String, pub all_inputs: HashMap, } diff --git a/src/utils/gui.rs b/src/utils/gui.rs index a27716f..b66a4a2 100644 --- a/src/utils/gui.rs +++ b/src/utils/gui.rs @@ -50,18 +50,21 @@ pub fn start_app_state_thread(audio_player_state_shared: Arc vec![], }; + let volume = match volume_res.status { + true => volume_res.message.parse::().unwrap(), + false => 0.0, + }; + let current_input = match current_input_res.status { true => current_input_res .message @@ -117,7 +125,7 @@ pub fn start_app_state_thread(audio_player_state_shared: Arc state, }; guard.tracks = tracks.clone(); - + guard.volume = volume; guard.current_input = current_input; guard.all_inputs = all_inputs; }