1
0
Fork 0
mirror of https://github.com/Zedfrigg/ironbar.git synced 2025-04-19 19:34:24 +02:00

Merge branch 'feat/volume-icon' into develop

This commit is contained in:
Reinout Meliesie 2024-05-31 00:24:17 +02:00
commit 12074027a5
Signed by: zedfrigg
GPG key ID: 3AFCC06481308BC6

View file

@ -1,6 +1,7 @@
use crate::clients::volume::{self, Event}; use crate::clients::volume::{self, Event};
use crate::config::CommonConfig; use crate::config::CommonConfig;
use crate::gtk_helpers::IronbarGtkExt; use crate::gtk_helpers::IronbarGtkExt;
use crate::image::ImageProvider;
use crate::modules::{ use crate::modules::{
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, PopupButton, WidgetContext, Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, PopupButton, WidgetContext,
}; };
@ -8,20 +9,16 @@ use crate::{glib_recv, lock, module_impl, send_async, spawn, try_send};
use glib::Propagation; use glib::Propagation;
use gtk::pango::EllipsizeMode; use gtk::pango::EllipsizeMode;
use gtk::prelude::*; use gtk::prelude::*;
use gtk::{Button, CellRendererText, ComboBoxText, Label, Orientation, Scale, ToggleButton}; use gtk::{
Box as GtkBox, Button, CellRendererText, ComboBoxText, Image, Label, Orientation, Scale,
ToggleButton,
};
use serde::Deserialize; use serde::Deserialize;
use std::collections::HashMap; use std::collections::HashMap;
use tokio::sync::mpsc; use tokio::sync::mpsc;
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize)]
pub struct VolumeModule { pub struct VolumeModule {
/// The format string to use for the widget button label.
/// For available tokens, see [below](#formatting-tokens).
///
/// **Default**: `{icon} {percentage}%`
#[serde(default = "default_format")]
format: String,
/// Maximum value to allow volume sliders to reach. /// Maximum value to allow volume sliders to reach.
/// Pulse supports values > 100 but this may result in distortion. /// Pulse supports values > 100 but this may result in distortion.
/// ///
@ -29,87 +26,20 @@ pub struct VolumeModule {
#[serde(default = "default_max_volume")] #[serde(default = "default_max_volume")]
max_volume: f64, max_volume: f64,
/// Volume state icons. #[serde(default = "default_icon_size")]
/// icon_size: i32,
/// See [icons](#icons).
#[serde(default)]
icons: Icons,
/// See [common options](module-level-options#common-options). /// See [common options](module-level-options#common-options).
#[serde(flatten)] #[serde(flatten)]
pub common: Option<CommonConfig>, pub common: Option<CommonConfig>,
} }
fn default_format() -> String {
String::from("{icon} {percentage}%")
}
#[derive(Debug, Clone, Deserialize)]
pub struct Icons {
/// Icon to show for high volume levels.
///
/// **Default**: `󰕾`
#[serde(default = "default_icon_volume_high")]
volume_high: String,
/// Icon to show for medium volume levels.
///
/// **Default**: `󰖀`
#[serde(default = "default_icon_volume_medium")]
volume_medium: String,
/// Icon to show for low volume levels.
///
/// **Default**: `󰕿`
#[serde(default = "default_icon_volume_low")]
volume_low: String,
/// Icon to show for muted outputs.
///
/// **Default**: `󰝟`
#[serde(default = "default_icon_muted")]
muted: String,
}
impl Icons {
fn volume_icon(&self, volume_percent: f64) -> &str {
match volume_percent as u32 {
0..=33 => &self.volume_low,
34..=66 => &self.volume_medium,
67.. => &self.volume_high,
}
}
}
impl Default for Icons {
fn default() -> Self {
Self {
volume_high: default_icon_volume_high(),
volume_medium: default_icon_volume_medium(),
volume_low: default_icon_volume_low(),
muted: default_icon_muted(),
}
}
}
const fn default_max_volume() -> f64 { const fn default_max_volume() -> f64 {
100.0 100.0
} }
fn default_icon_volume_high() -> String { const fn default_icon_size() -> i32 {
String::from("󰕾") 24
}
fn default_icon_volume_medium() -> String {
String::from("󰖀")
}
fn default_icon_volume_low() -> String {
String::from("󰕿")
}
fn default_icon_muted() -> String {
String::from("󰝟")
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -212,22 +142,23 @@ impl Module<Button> for VolumeModule {
{ {
let rx = context.subscribe(); let rx = context.subscribe();
let icons = self.icons.clone(); let icon_theme = info.icon_theme.clone();
let button = button.clone();
let format = self.format.clone(); let image_icon = Image::new();
image_icon.add_class("icon");
button.set_image(Some(&image_icon));
glib_recv!(rx, event => { glib_recv!(rx, event => {
match event { match event {
Event::AddSink(sink) | Event::UpdateSink(sink) if sink.active => { Event::AddSink(sink) | Event::UpdateSink(sink) if sink.active => {
let label = format ImageProvider::parse(
.replace("{icon}", if sink.muted { &icons.muted } else { icons.volume_icon(sink.volume) }) &determine_volume_icon(sink.muted, sink.volume),
.replace("{percentage}", &sink.volume.to_string()) &icon_theme,
.replace("{name}", &sink.description); false,
self.icon_size,
button.set_label(&label); ).map(|provider| provider.load_into_image(image_icon.clone()));
}, },
_ => {} _ => {},
} }
}); });
} }
@ -249,17 +180,17 @@ impl Module<Button> for VolumeModule {
tx: mpsc::Sender<Self::ReceiveMessage>, tx: mpsc::Sender<Self::ReceiveMessage>,
rx: tokio::sync::broadcast::Receiver<Self::SendMessage>, rx: tokio::sync::broadcast::Receiver<Self::SendMessage>,
_context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>, _context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
_info: &ModuleInfo, info: &ModuleInfo,
) -> Option<gtk::Box> ) -> Option<GtkBox>
where where
Self: Sized, Self: Sized,
{ {
let container = gtk::Box::new(Orientation::Horizontal, 10); let container = GtkBox::new(Orientation::Horizontal, 10);
let sink_container = gtk::Box::new(Orientation::Vertical, 5); let sink_container = GtkBox::new(Orientation::Vertical, 5);
sink_container.add_class("device-box"); sink_container.add_class("device-box");
let input_container = gtk::Box::new(Orientation::Vertical, 5); let input_container = GtkBox::new(Orientation::Vertical, 5);
input_container.add_class("apps-box"); input_container.add_class("apps-box");
container.add(&sink_container); container.add(&sink_container);
@ -319,6 +250,8 @@ impl Module<Button> for VolumeModule {
let btn_mute = ToggleButton::new(); let btn_mute = ToggleButton::new();
btn_mute.add_class("btn-mute"); btn_mute.add_class("btn-mute");
let btn_mute_icon = Image::new();
btn_mute.set_image(Some(&btn_mute_icon));
sink_container.add(&btn_mute); sink_container.add(&btn_mute);
{ {
@ -338,6 +271,7 @@ impl Module<Button> for VolumeModule {
let mut inputs = HashMap::new(); let mut inputs = HashMap::new();
{ {
let icon_theme = info.icon_theme.clone();
let input_container = input_container.clone(); let input_container = input_container.clone();
let mut sinks = vec![]; let mut sinks = vec![];
@ -352,7 +286,12 @@ impl Module<Button> for VolumeModule {
slider.set_value(info.volume); slider.set_value(info.volume);
btn_mute.set_active(info.muted); btn_mute.set_active(info.muted);
btn_mute.set_label(if info.muted { &self.icons.muted } else { self.icons.volume_icon(info.volume) }); ImageProvider::parse(
&determine_volume_icon(info.muted, info.volume),
&icon_theme,
false,
self.icon_size,
).map(|provider| provider.load_into_image(btn_mute_icon.clone()));
} }
sinks.push(info); sinks.push(info);
@ -364,7 +303,12 @@ impl Module<Button> for VolumeModule {
slider.set_value(info.volume); slider.set_value(info.volume);
btn_mute.set_active(info.muted); btn_mute.set_active(info.muted);
btn_mute.set_label(if info.muted { &self.icons.muted } else { self.icons.volume_icon(info.volume) }); ImageProvider::parse(
&determine_volume_icon(info.muted, info.volume),
&icon_theme,
false,
self.icon_size,
).map(|provider| provider.load_into_image(btn_mute_icon.clone()));
} }
} }
} }
@ -378,7 +322,7 @@ impl Module<Button> for VolumeModule {
Event::AddInput(info) => { Event::AddInput(info) => {
let index = info.index; let index = info.index;
let item_container = gtk::Box::new(Orientation::Vertical, 0); let item_container = GtkBox::new(Orientation::Vertical, 0);
item_container.add_class("app-box"); item_container.add_class("app-box");
let label = Label::new(Some(&info.name)); let label = Label::new(Some(&info.name));
@ -402,9 +346,16 @@ impl Module<Button> for VolumeModule {
let btn_mute = ToggleButton::new(); let btn_mute = ToggleButton::new();
btn_mute.add_class("btn-mute"); btn_mute.add_class("btn-mute");
let btn_mute_icon = Image::new();
btn_mute.set_image(Some(&btn_mute_icon));
btn_mute.set_active(info.muted); btn_mute.set_active(info.muted);
btn_mute.set_label(if info.muted { &self.icons.muted } else { self.icons.volume_icon(info.volume) }); ImageProvider::parse(
&determine_volume_icon(info.muted, info.volume),
&icon_theme,
false,
self.icon_size,
).map(|provider| provider.load_into_image(btn_mute_icon.clone()));
{ {
let tx = tx.clone(); let tx = tx.clone();
@ -425,7 +376,7 @@ impl Module<Button> for VolumeModule {
container: item_container, container: item_container,
label, label,
slider, slider,
btn_mute btn_mute_icon,
}); });
} }
Event::UpdateInput(info) => { Event::UpdateInput(info) => {
@ -433,7 +384,12 @@ impl Module<Button> for VolumeModule {
ui.label.set_label(&info.name); ui.label.set_label(&info.name);
ui.slider.set_value(info.volume); ui.slider.set_value(info.volume);
ui.slider.set_sensitive(info.can_set_volume); ui.slider.set_sensitive(info.can_set_volume);
ui.btn_mute.set_label(if info.muted { &self.icons.muted } else { self.icons.volume_icon(info.volume) }); ImageProvider::parse(
&determine_volume_icon(info.muted, info.volume),
&icon_theme,
false,
self.icon_size,
).map(|provider| provider.load_into_image(ui.btn_mute_icon.clone()));
} }
} }
Event::RemoveInput(index) => { Event::RemoveInput(index) => {
@ -450,8 +406,21 @@ impl Module<Button> for VolumeModule {
} }
struct InputUi { struct InputUi {
container: gtk::Box, container: GtkBox,
label: Label, label: Label,
slider: Scale, slider: Scale,
btn_mute: ToggleButton, btn_mute_icon: Image,
}
fn determine_volume_icon(muted: bool, volume: f64) -> String {
let icon_variant = if muted {
"muted"
} else if volume <= 33.3333 {
"low"
} else if volume <= 66.6667 {
"medium"
} else {
"high"
};
format!("audio-volume-{icon_variant}-symbolic")
} }