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:
commit
12074027a5
1 changed files with 71 additions and 102 deletions
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue