Skip to content

Commit 5f349fb

Browse files
committed
Add led style vu meter option.
1 parent 807f81c commit 5f349fb

8 files changed

+247
-15
lines changed

data/com.saivert.pwvucontrol.gschema.xml.in

+5
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,10 @@
2121
<summary>Enable over-amplification</summary>
2222
<description></description>
2323
</key>
24+
<key name="use-peakmeter-led" type="b">
25+
<default>false</default>
26+
<summary>Use led style peak meter</summary>
27+
<description></description>
28+
</key>
2429
</schema>
2530
</schemalist>

data/resources/ui/volumebox.ui

+15
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@
115115
<property name="use-overamplification">1</property>
116116
</object>
117117
</child>
118+
<!--
118119
<child>
119120
<object class="GtkLevelBar" id="level_bar">
120121
<property name="hexpand">True</property>
@@ -124,6 +125,20 @@
124125
<property name="min-value">0.0</property>
125126
</object>
126127
</child>
128+
<child>
129+
<object class="PwPeakMeter" id="peak_meter">
130+
<property name="hexpand">True</property>
131+
<property name="hexpand-set">True</property>
132+
</object>
133+
</child>
134+
-->
135+
<child>
136+
<object class="GtkBox" id="peakmeterbox">
137+
<property name="orientation">vertical</property>
138+
<property name="valign">center</property>
139+
<property name="hexpand">1</property>
140+
</object>
141+
</child>
127142
</object>
128143
</child>
129144
<child>

data/resources/ui/window.ui

+4
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,10 @@
309309
<attribute name="label" translatable="yes">_Enable over-amplification</attribute>
310310
<attribute name="action">win.enable-overamplification</attribute>
311311
</item>
312+
<item>
313+
<attribute name="label" translatable="yes">Use led peak meter</attribute>
314+
<attribute name="action">win.use-peakmeter-led</attribute>
315+
</item>
312316
<item>
313317
<attribute name="label" translatable="yes">_Keyboard Shortcuts</attribute>
314318
<attribute name="action">win.show-help-overlay</attribute>

src/ui/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ mod devicebox;
1111
mod profilerow;
1212
mod route_dropdown;
1313
mod volumescale;
14+
mod peakmeter;
15+
mod peak_meter_abstraction;
1416

1517
pub use window::PwvucontrolWindow;
1618
pub use profile_dropdown::PwProfileDropDown;
@@ -24,3 +26,5 @@ pub use streambox::PwStreamBox;
2426
pub use profilerow::PwProfileRow;
2527
pub use route_dropdown::PwRouteDropDown;
2628
pub use volumescale::PwVolumeScale;
29+
pub use peakmeter::PwPeakMeter;
30+
pub use peak_meter_abstraction::{PeakMeterAbstraction, PeakMeterType};

src/ui/peak_meter_abstraction.rs

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
use glib::object::Cast;
2+
use gtk::prelude::WidgetExt;
3+
4+
use super::PwPeakMeter;
5+
6+
pub enum PeakMeterType {
7+
None,
8+
Basic,
9+
Led
10+
}
11+
12+
13+
#[derive(Debug)]
14+
enum PeakMeterWidget {
15+
None,
16+
Basic(gtk::LevelBar),
17+
Led(PwPeakMeter)
18+
}
19+
20+
#[derive(Debug)]
21+
pub struct PeakMeterAbstraction {
22+
peak_meter: PeakMeterWidget,
23+
}
24+
25+
impl PeakMeterAbstraction {
26+
pub fn new(peak_meter_type: PeakMeterType) -> PeakMeterAbstraction {
27+
match peak_meter_type {
28+
PeakMeterType::Basic => {
29+
let level_bar = gtk::LevelBar::new();
30+
level_bar.set_mode(gtk::LevelBarMode::Continuous);
31+
level_bar.set_min_value(0.0);
32+
level_bar.set_max_value(1.0);
33+
34+
level_bar.add_offset_value(gtk::LEVEL_BAR_OFFSET_LOW, 0.0);
35+
level_bar.add_offset_value(gtk::LEVEL_BAR_OFFSET_HIGH, 0.0);
36+
level_bar.add_offset_value(gtk::LEVEL_BAR_OFFSET_FULL, 1.0);
37+
38+
Self { peak_meter: PeakMeterWidget::Basic(level_bar)}
39+
},
40+
PeakMeterType::Led => {
41+
let widget = PwPeakMeter::new();
42+
Self { peak_meter: PeakMeterWidget::Led(widget)}
43+
},
44+
PeakMeterType::None => Self { peak_meter: PeakMeterWidget::None }
45+
}
46+
}
47+
48+
pub fn set_visible(&self, visible: bool) {
49+
match &self.peak_meter {
50+
PeakMeterWidget::Basic(level_bar) => level_bar.set_visible(visible),
51+
PeakMeterWidget::Led(pw_peak_meter) => pw_peak_meter.set_visible(visible),
52+
PeakMeterWidget::None => {},
53+
}
54+
}
55+
56+
pub fn set_level(&self, level: f32) {
57+
match &self.peak_meter {
58+
PeakMeterWidget::Basic(level_bar) => level_bar.set_value(level as f64),
59+
PeakMeterWidget::Led(pw_peak_meter) => pw_peak_meter.set_level(level),
60+
PeakMeterWidget::None => {},
61+
}
62+
}
63+
64+
pub fn get_widget(&self) -> Option<&gtk::Widget> {
65+
match &self.peak_meter {
66+
PeakMeterWidget::Basic(level_bar) => Some(level_bar.upcast_ref()),
67+
PeakMeterWidget::Led(pw_peak_meter) => Some(pw_peak_meter.upcast_ref()),
68+
PeakMeterWidget::None => None,
69+
}
70+
}
71+
}
72+
73+
impl Default for PeakMeterAbstraction {
74+
fn default() -> Self {
75+
Self::new(PeakMeterType::None)
76+
}
77+
}

src/ui/peakmeter.rs

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// SPDX-License-Identifier: GPL-3.0-or-later
2+
3+
use gtk::{self, prelude::*, subclass::prelude::*};
4+
use std::cell::Cell;
5+
6+
mod imp {
7+
use gtk::{gdk::RGBA, graphene, gsk};
8+
9+
use super::*;
10+
11+
#[derive(Debug, Default, glib::Properties)]
12+
#[properties(wrapper_type = super::PwPeakMeter)]
13+
pub struct PwPeakMeter {
14+
#[property(get, set = Self::set_level)]
15+
pub(super) level: Cell<f32>,
16+
}
17+
18+
#[glib::object_subclass]
19+
impl ObjectSubclass for PwPeakMeter {
20+
const NAME: &'static str = "PwPeakMeter";
21+
type Type = super::PwPeakMeter;
22+
type ParentType = gtk::Widget;
23+
}
24+
25+
impl PwPeakMeter {
26+
fn set_level(&self, level: f32) {
27+
self.level.set(level);
28+
self.obj().queue_draw();
29+
}
30+
}
31+
32+
#[glib::derived_properties]
33+
impl ObjectImpl for PwPeakMeter {
34+
fn constructed(&self) {
35+
self.parent_constructed();
36+
self.obj().add_css_class("vumeter");
37+
}
38+
}
39+
40+
impl WidgetImpl for PwPeakMeter {
41+
fn snapshot(&self, snapshot: &gtk::Snapshot) {
42+
const NUM_BLOCKS: u32 = 20;
43+
const GREEN_LIMIT: u32 = (0.6 * NUM_BLOCKS as f32) as u32;
44+
const YELLOW_LIMIT: u32 = (0.9 * NUM_BLOCKS as f32) as u32;
45+
46+
let width = self.obj().width() as u32;
47+
let w = self.obj().width() as f32;
48+
let h = self.obj().height() as f32;
49+
50+
let level = self.level.get() as f32;
51+
let bounding_box = graphene::Rect::new(0.0, 0.0, w, h);
52+
53+
let rounded_rect = gsk::RoundedRect::from_rect(bounding_box, 5.0);
54+
55+
snapshot.push_rounded_clip(&rounded_rect);
56+
snapshot.append_color(&RGBA::new(1.0, 1.0, 1.0, 0.1), &bounding_box);
57+
58+
let discrete_level = (level * NUM_BLOCKS as f32).floor() as u32;
59+
let mut block_width = width / NUM_BLOCKS;
60+
let extra_space = width - block_width * NUM_BLOCKS;
61+
if extra_space > 0 {
62+
block_width += 1;
63+
}
64+
let mut block_area_width = block_width;
65+
let mut block_area_x = 0;
66+
67+
for i in 0..discrete_level {
68+
if extra_space > 0 && i == extra_space {
69+
block_area_width -= 1;
70+
}
71+
72+
let color = match i {
73+
0..GREEN_LIMIT => RGBA::GREEN,
74+
GREEN_LIMIT..YELLOW_LIMIT => RGBA::new(1.0, 1.0, 0.0, 1.0),
75+
_ => RGBA::RED,
76+
};
77+
snapshot.append_color(&color, &graphene::Rect::new(block_area_x as f32, 0.0, block_area_width as f32 - 1.0, h));
78+
block_area_x += block_area_width;
79+
}
80+
snapshot.pop();
81+
}
82+
83+
fn measure(&self, orientation: gtk::Orientation, _for_size: i32) -> (i32, i32, i32, i32) {
84+
match orientation {
85+
gtk::Orientation::Horizontal => (10, 10, -1, -1),
86+
gtk::Orientation::Vertical => (10, 20, -1, -1),
87+
_ => panic!("Invalid orientation passed to measure"),
88+
}
89+
}
90+
}
91+
}
92+
93+
glib::wrapper! {
94+
pub struct PwPeakMeter(ObjectSubclass<imp::PwPeakMeter>) @extends gtk::Widget;
95+
}
96+
97+
impl PwPeakMeter {
98+
pub fn new() -> Self {
99+
glib::Object::builder()
100+
.build()
101+
}
102+
}
103+
104+
impl Default for PwPeakMeter {
105+
fn default() -> Self {
106+
Self::new()
107+
}
108+
}

src/ui/volumebox.rs

+31-12
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// SPDX-License-Identifier: GPL-3.0-or-later
22

33
use crate::{
4-
backend::{NodeType, PwChannelObject, PwNodeObject, PwvucontrolManager}, ui::{LevelbarProvider, PwChannelBox, PwVolumeScale}
4+
backend::{NodeType, PwChannelObject, PwNodeObject, PwvucontrolManager}, ui::{LevelbarProvider, PwChannelBox, PwVolumeScale, PeakMeterAbstraction}
55
};
66
use glib::{clone, closure_local, ControlFlow, SignalHandlerId};
77
use gtk::{prelude::*, subclass::prelude::*};
@@ -25,6 +25,7 @@ mod imp {
2525
pub(super) level: Cell<f32>,
2626
pub(super) default_node: Cell<u32>,
2727
pub(super) default_node_changed_handler: RefCell<Option<Box<dyn Fn()>>>,
28+
pub peak_meter: RefCell<PeakMeterAbstraction>,
2829

2930
// Template widgets
3031
#[template_child]
@@ -36,7 +37,7 @@ mod imp {
3637
#[template_child]
3738
pub volume_scale: TemplateChild<PwVolumeScale>,
3839
#[template_child]
39-
pub level_bar: TemplateChild<gtk::LevelBar>,
40+
pub peakmeterbox: TemplateChild<gtk::Box>,
4041
#[template_child]
4142
pub mutebtn: TemplateChild<gtk::ToggleButton>,
4243
#[template_child]
@@ -156,13 +157,14 @@ mod imp {
156157
widget.obj().grab_focus();
157158
}));
158159

159-
self.level_bar.set_min_value(0.0);
160-
self.level_bar.set_max_value(1.0);
161-
162-
self.level_bar.add_offset_value(gtk::LEVEL_BAR_OFFSET_LOW, 0.0);
163-
self.level_bar.add_offset_value(gtk::LEVEL_BAR_OFFSET_HIGH, 0.0);
164-
self.level_bar.add_offset_value(gtk::LEVEL_BAR_OFFSET_FULL, 1.0);
160+
let window = crate::ui::PwvucontrolWindow::default();
161+
window
162+
.imp()
163+
.settings.connect_changed(Some("use-peakmeter-led"), clone!(@weak self as widget => move |_settings, _detail| {
164+
widget.setup_levelbar_widget();
165+
}));
165166

167+
self.setup_levelbar_widget();
166168
}
167169

168170
fn dispose(&self) {
@@ -186,14 +188,14 @@ mod imp {
186188

187189
fn map(&self) {
188190
self.parent_map();
189-
191+
190192
// Monitoring ourselves cause an infinite loop.
191193
let item = self.node_object.borrow();
192194
let item = item.as_ref().unwrap();
193195
if item.name() != "pwvucontrol-peak-detect" {
194196
self.setuplevelbar();
195197
} else {
196-
self.level_bar.set_visible(false);
198+
self.peak_meter.borrow().set_visible(false);
197199
}
198200
}
199201
}
@@ -218,18 +220,35 @@ mod imp {
218220
impl PwVolumeBox {
219221
fn setuplevelbar(&self) {
220222
let item = self.node_object.borrow();
221-
let item = item.as_ref().cloned().unwrap();
223+
let item = item.as_ref().unwrap();
222224

223225
if let Ok(provider) = LevelbarProvider::new(&self.obj(), item.boundid()) {
224226
self.levelbarprovider.set(Some(provider));
225227

226228
let callbackid = self.obj().add_tick_callback(|widget, _fc| {
227-
widget.imp().level_bar.set_value(widget.imp().level.get() as f64);
229+
230+
widget.imp().peak_meter.borrow().set_level(widget.imp().level.get());
228231
ControlFlow::Continue
229232
});
230233
self.timeoutid.set(Some(callbackid));
231234
}
232235
}
236+
237+
fn setup_levelbar_widget(&self) {
238+
let window = crate::ui::PwvucontrolWindow::default();
239+
let peak_meter = match window.imp().settings.boolean("use-peakmeter-led") {
240+
true => PeakMeterAbstraction::new(crate::ui::PeakMeterType::Led),
241+
false => PeakMeterAbstraction::new(crate::ui::PeakMeterType::Basic)
242+
};
243+
244+
if let Some (widget) = self.peak_meter.borrow().get_widget() {
245+
self.peakmeterbox.remove(widget);
246+
}
247+
if let Some(widget) = peak_meter.get_widget() {
248+
self.peakmeterbox.append(widget);
249+
}
250+
self.peak_meter.set(peak_meter);
251+
}
233252
}
234253
}
235254

src/ui/window.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::{
55
application::PwvucontrolApplication,
66
backend::{PwDeviceObject, PwNodeObject, PwvucontrolManager},
77
config::{APP_ID, PROFILE},
8-
ui::{devicebox::PwDeviceBox, PwSinkBox, PwStreamBox, PwVolumeBox},
8+
ui::{devicebox::PwDeviceBox, PwSinkBox, PwStreamBox},
99
};
1010
use adw::subclass::prelude::*;
1111
use gettextrs::gettext;
@@ -71,8 +71,6 @@ mod imp {
7171
type ParentType = adw::ApplicationWindow;
7272

7373
fn class_init(klass: &mut Self::Class) {
74-
PwVolumeBox::ensure_type();
75-
7674
klass.bind_template();
7775
}
7876

@@ -179,6 +177,8 @@ mod imp {
179177

180178
let overamplification_action = self.settings.create_action("enable-overamplification");
181179
self.obj().add_action(&overamplification_action);
180+
let use_led_peakmeter_action = self.settings.create_action("use-peakmeter-led");
181+
self.obj().add_action(&use_led_peakmeter_action);
182182

183183
self.obj().load_window_state();
184184
}

0 commit comments

Comments
 (0)