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 {
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;
+1 -1
View File
@@ -17,9 +17,9 @@ use pwsp::{
},
};
use rfd::FileDialog;
use std::path::PathBuf;
use std::{
error::Error,
path::PathBuf,
sync::{Arc, Mutex},
};
+22 -1
View File
@@ -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();
+9
View File
@@ -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<Instant>,
pub ignore_volume_update_until: Option<Instant>,
}
@@ -25,6 +27,11 @@ pub struct AppState {
pub track_ui_states: HashMap<u32, TrackUiState>,
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 dirs: HashSet<PathBuf>,
@@ -43,6 +50,8 @@ pub struct AudioPlayerState {
pub tracks: Vec<TrackInfo>,
pub volume: f32, // Master volume
pub current_input: 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 tracks_req = Request::get_tracks();
let volume_req = Request::get_volume();
let current_input_req = Request::get_input();
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(tracks_req),
make_request(volume_req),
make_request(current_input_req),
make_request(all_inputs_req),
);
let state_res = state_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 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![],
};
let volume = match volume_res.status {
true => volume_res.message.parse::<f32>().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<Mutex<AudioPlayerSt
None => state,
};
guard.tracks = tracks.clone();
guard.volume = volume;
guard.current_input = current_input;
guard.all_inputs = all_inputs;
}