feat: add master volume slider

This commit is contained in:
2026-01-24 23:45:33 +03:00
parent 5afe3dd45b
commit 4d54443593
5 changed files with 91 additions and 17 deletions
+49 -13
View File
@@ -18,6 +18,18 @@ enum TrackAction {
} }
impl SoundpadGui { 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) { pub fn draw_waiting_for_daemon(&mut self, ui: &mut Ui) {
ui.centered_and_justified(|ui| { ui.centered_and_justified(|ui| {
ui.label( ui.label(
@@ -215,17 +227,10 @@ impl SoundpadGui {
// -------------------------------- // --------------------------------
// ---------- Volume Icon ---------- // ---------- Volume Icon ----------
let volume_icon = if track.volume > 0.7 { let volume_icon = Self::get_volume_icon(track.volume);
icons::ICON_VOLUME_UP let volume_label = Label::new(RichText::new(volume_icon).size(18.0));
} else if track.volume == 0.0 { ui.add_sized([30.0, 30.0], volume_label)
icons::ICON_VOLUME_OFF .on_hover_text(format!("Volume: {:.0}%", track.volume * 100.0));
} 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);
// -------------------------------- // --------------------------------
// ---------- Volume Slider ---------- // ---------- Volume Slider ----------
@@ -396,7 +401,7 @@ impl SoundpadGui {
fn draw_footer(&mut self, ui: &mut Ui) { fn draw_footer(&mut self, ui: &mut Ui) {
ui.add_space(5.0); ui.add_space(5.0);
ui.horizontal_top(|ui| { ui.horizontal(|ui| {
// ---------- Microphone selection ---------- // ---------- Microphone selection ----------
let mut mics: Vec<(&String, &String)> = let mut mics: Vec<(&String, &String)> =
self.audio_player_state.all_inputs.iter().collect(); 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 mut selected_input = self.audio_player_state.current_input.to_owned();
let prev_input = selected_input.to_owned(); let prev_input = selected_input.to_owned();
ComboBox::from_label("Choose microphone") ComboBox::from_label("Choose microphone")
.height(30.0)
.selected_text( .selected_text(
self.audio_player_state self.audio_player_state
.all_inputs .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); ui.add_space(ui.available_width() - 18.0 - ui.spacing().item_spacing.x);
// ---------- Settings button ---------- // ---------- 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); let settings_button_response = ui.add_sized([18.0, 18.0], settings_button);
if settings_button_response.clicked() { if settings_button_response.clicked() {
self.app_state.show_settings = true; self.app_state.show_settings = true;
+1 -1
View File
@@ -17,9 +17,9 @@ use pwsp::{
}, },
}; };
use rfd::FileDialog; use rfd::FileDialog;
use std::path::PathBuf;
use std::{ use std::{
error::Error, error::Error,
path::PathBuf,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
}; };
+22 -1
View File
@@ -3,7 +3,10 @@ use eframe::{App, Frame as EFrame};
use egui::{CentralPanel, Context}; use egui::{CentralPanel, Context};
use pwsp::{ use pwsp::{
types::socket::Request, 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}; 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(); let guard = self.audio_player_state_shared.lock().unwrap();
self.audio_player_state = guard.clone(); self.audio_player_state = guard.clone();
+9
View File
@@ -12,8 +12,10 @@ use std::{
pub struct TrackUiState { pub struct TrackUiState {
pub position_slider_value: f32, pub position_slider_value: f32,
pub volume_slider_value: f32, pub volume_slider_value: f32,
pub position_dragged: bool, pub position_dragged: bool,
pub volume_dragged: bool, pub volume_dragged: bool,
pub ignore_position_update_until: Option<Instant>, pub ignore_position_update_until: Option<Instant>,
pub ignore_volume_update_until: Option<Instant>, pub ignore_volume_update_until: Option<Instant>,
} }
@@ -25,6 +27,11 @@ pub struct AppState {
pub track_ui_states: HashMap<u32, TrackUiState>, pub track_ui_states: HashMap<u32, TrackUiState>,
pub show_settings: bool, pub show_settings: bool,
pub volume_dragged: bool,
pub volume_slider_value: f32,
pub ignore_volume_update_until: Option<Instant>,
pub current_dir: Option<PathBuf>, pub current_dir: Option<PathBuf>,
pub dirs: HashSet<PathBuf>, pub dirs: HashSet<PathBuf>,
@@ -43,6 +50,8 @@ pub struct AudioPlayerState {
pub tracks: Vec<TrackInfo>, pub tracks: Vec<TrackInfo>,
pub volume: f32, // Master volume
pub current_input: String, pub current_input: String,
pub all_inputs: HashMap<String, String>, pub all_inputs: HashMap<String, String>,
} }
+10 -2
View File
@@ -50,18 +50,21 @@ pub fn start_app_state_thread(audio_player_state_shared: Arc<Mutex<AudioPlayerSt
let state_req = Request::get_state(); let state_req = Request::get_state();
let tracks_req = Request::get_tracks(); let tracks_req = Request::get_tracks();
let volume_req = Request::get_volume();
let current_input_req = Request::get_input(); let current_input_req = Request::get_input();
let all_inputs_req = Request::get_inputs(); let all_inputs_req = Request::get_inputs();
let (state_res, tracks_res, current_input_res, all_inputs_res) = tokio::join!( let (state_res, tracks_res, volume_res, current_input_res, all_inputs_res) = tokio::join!(
make_request(state_req), make_request(state_req),
make_request(tracks_req), make_request(tracks_req),
make_request(volume_req),
make_request(current_input_req), make_request(current_input_req),
make_request(all_inputs_req), make_request(all_inputs_req),
); );
let state_res = state_res.unwrap_or_default(); let state_res = state_res.unwrap_or_default();
let tracks_res = tracks_res.unwrap_or_default(); let tracks_res = tracks_res.unwrap_or_default();
let volume_res = volume_res.unwrap_or_default();
let current_input_res = current_input_res.unwrap_or_default(); let current_input_res = current_input_res.unwrap_or_default();
let all_inputs_res = all_inputs_res.unwrap_or_default(); let all_inputs_res = all_inputs_res.unwrap_or_default();
@@ -77,6 +80,11 @@ pub fn start_app_state_thread(audio_player_state_shared: Arc<Mutex<AudioPlayerSt
false => vec![], false => vec![],
}; };
let volume = match volume_res.status {
true => volume_res.message.parse::<f32>().unwrap(),
false => 0.0,
};
let current_input = match current_input_res.status { let current_input = match current_input_res.status {
true => current_input_res true => current_input_res
.message .message
@@ -117,7 +125,7 @@ pub fn start_app_state_thread(audio_player_state_shared: Arc<Mutex<AudioPlayerSt
None => state, None => state,
}; };
guard.tracks = tracks.clone(); guard.tracks = tracks.clone();
guard.volume = volume;
guard.current_input = current_input; guard.current_input = current_input;
guard.all_inputs = all_inputs; guard.all_inputs = all_inputs;
} }