mirror of
https://github.com/arabianq/pipewire-soundpad.git
synced 2026-04-28 06:21:23 +00:00
feat: add master volume slider
This commit is contained in:
+49
-13
@@ -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
@@ -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
@@ -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();
|
||||||
|
|||||||
@@ -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
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user