1
0
Fork 0
mirror of https://github.com/Zedfrigg/ironbar.git synced 2025-07-01 18:51:04 +02:00

feat(custom): slider widget

Resolves partially #68.
This commit is contained in:
Jake Stanger 2023-04-09 22:42:35 +01:00
parent e928b30f99
commit dfe1964abf
No known key found for this signature in database
GPG key ID: C51FC8F9CB0BEA61
14 changed files with 245 additions and 49 deletions

View file

@ -124,7 +124,7 @@ impl Module<Button> for ClipboardModule {
button.style_context().add_class("btn");
button.connect_clicked(move |button| {
let pos = Popup::button_pos(button, position.get_orientation());
let pos = Popup::widget_geometry(button, position.get_orientation());
try_send!(context.tx, ModuleUpdateEvent::TogglePopup(pos));
});

View file

@ -69,7 +69,7 @@ impl Module<Button> for ClockModule {
button.connect_clicked(move |button| {
try_send!(
context.tx,
ModuleUpdateEvent::TogglePopup(Popup::button_pos(button, orientation))
ModuleUpdateEvent::TogglePopup(Popup::widget_geometry(button, orientation))
);
});

View file

@ -45,7 +45,8 @@ impl CustomWidget for ButtonWidget {
tx,
ExecEvent {
cmd: exec.clone(),
geometry: Popup::button_pos(button, bar_orientation),
args: None,
geometry: Popup::widget_geometry(button, bar_orientation),
}
);
});

View file

@ -2,14 +2,16 @@ mod r#box;
mod button;
mod image;
mod label;
mod slider;
use self::image::ImageWidget;
use self::label::LabelWidget;
use self::r#box::BoxWidget;
use self::slider::SliderWidget;
use crate::config::CommonConfig;
use crate::modules::custom::button::ButtonWidget;
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
use crate::popup::ButtonGeometry;
use crate::popup::WidgetGeometry;
use crate::script::Script;
use crate::send_async;
use color_eyre::{Report, Result};
@ -48,7 +50,8 @@ pub enum Widget {
Box(BoxWidget),
Label(LabelWidget),
Button(ButtonWidget),
Image(ImageWidget)
Image(ImageWidget),
Slider(SliderWidget),
}
#[derive(Clone, Copy)]
@ -72,6 +75,7 @@ impl Widget {
Widget::Label(widget) => parent.add(&widget.into_widget(context)),
Widget::Button(widget) => parent.add(&widget.into_widget(context)),
Widget::Image(widget) => parent.add(&widget.into_widget(context)),
Widget::Slider(widget) => parent.add(&widget.into_widget(context)),
}
}
}
@ -79,7 +83,8 @@ impl Widget {
#[derive(Debug)]
pub struct ExecEvent {
cmd: String,
geometry: ButtonGeometry,
args: Option<Vec<String>>,
geometry: WidgetGeometry,
}
impl Module<gtk::Box> for CustomModule {
@ -102,8 +107,10 @@ impl Module<gtk::Box> for CustomModule {
let script = Script::from(&event.cmd[1..]);
debug!("executing command: '{}'", script.cmd);
// TODO: Migrate to use script.run
if let Err(err) = script.get_output().await {
let args = event.args.unwrap_or(vec![]);
if let Err(err) = script.get_output(Some(&args)).await {
error!("{err:?}");
}
} else if event.cmd == "popup:toggle" {

View file

@ -0,0 +1,131 @@
use crate::modules::custom::{try_get_orientation, CustomWidget, CustomWidgetContext, ExecEvent};
use crate::popup::Popup;
use crate::script::{OutputStream, Script, ScriptInput};
use crate::{send, try_send};
use gtk::prelude::*;
use gtk::{Orientation, Scale};
use serde::Deserialize;
use std::cell::Cell;
use tokio::spawn;
use tracing::error;
#[derive(Debug, Deserialize, Clone)]
pub struct SliderWidget {
name: Option<String>,
class: Option<String>,
orientation: Option<String>,
value: Option<ScriptInput>,
on_change: Option<String>,
#[serde(default = "default_min")]
min: f64,
#[serde(default = "default_max")]
max: f64,
length: Option<i32>,
}
const fn default_min() -> f64 {
0.0
}
const fn default_max() -> f64 {
100.0
}
impl CustomWidget for SliderWidget {
type Widget = Scale;
fn into_widget(self, context: CustomWidgetContext) -> Self::Widget {
let mut builder = Scale::builder();
if let Some(name) = self.name {
builder = builder.name(name);
}
if let Some(orientation) = self.orientation {
builder = builder
.orientation(try_get_orientation(&orientation).unwrap_or(context.bar_orientation));
}
if let Some(length) = self.length {
builder = match context.bar_orientation {
Orientation::Horizontal => builder.width_request(length),
Orientation::Vertical => builder.height_request(length),
_ => builder,
}
}
let scale = builder.build();
scale.set_range(self.min, self.max);
if let Some(class) = self.class {
scale.style_context().add_class(&class);
}
if let Some(on_change) = self.on_change {
let min = self.min;
let max = self.max;
let tx = context.tx.clone();
// GTK will spam the same value over and over
let prev_value = Cell::new(scale.value());
scale.connect_change_value(move |scale, _, val| {
// GTK will send values outside min/max range
let val = clamp(val, min, max);
if val != prev_value.get() {
try_send!(
tx,
ExecEvent {
cmd: on_change.clone(),
args: Some(vec![val.to_string()]),
geometry: Popup::widget_geometry(scale, context.bar_orientation),
}
);
prev_value.set(val);
}
Inhibit(false)
});
}
if let Some(value) = self.value {
let script = Script::from(value);
let scale = scale.clone();
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
spawn(async move {
script
.run(None, move |stream, _success| match stream {
OutputStream::Stdout(out) => match out.parse() {
Ok(value) => send!(tx, value),
Err(err) => error!("{err:?}"),
},
OutputStream::Stderr(err) => error!("{err:?}"),
})
.await;
});
rx.attach(None, move |value| {
scale.set_value(value);
Continue(true)
});
}
scale
}
}
/// Ensures `num` is between `min` and `max` (inclusive).
fn clamp(num: f64, min: f64, max: f64) -> f64 {
if num < min {
min
} else if num > max {
max
} else {
num
}
}

View file

@ -217,7 +217,7 @@ impl ItemButton {
try_send!(
tx,
ModuleUpdateEvent::OpenPopup(Popup::button_pos(button, orientation,))
ModuleUpdateEvent::OpenPopup(Popup::widget_geometry(button, orientation,))
);
} else {
try_send!(tx, ModuleUpdateEvent::ClosePopup);

View file

@ -23,7 +23,7 @@ pub mod tray;
pub mod workspaces;
use crate::config::BarPosition;
use crate::popup::ButtonGeometry;
use crate::popup::WidgetGeometry;
use color_eyre::Result;
use glib::IsA;
use gtk::gdk::Monitor;
@ -50,10 +50,10 @@ pub enum ModuleUpdateEvent<T> {
/// Sends an update to the module UI
Update(T),
/// Toggles the open state of the popup.
TogglePopup(ButtonGeometry),
TogglePopup(WidgetGeometry),
/// Force sets the popup open.
/// Takes the button X position and width.
OpenPopup(ButtonGeometry),
OpenPopup(WidgetGeometry),
/// Force sets the popup closed.
ClosePopup,
}

View file

@ -179,7 +179,7 @@ impl Module<Button> for MusicModule {
button.connect_clicked(move |button| {
try_send!(
tx,
ModuleUpdateEvent::TogglePopup(Popup::button_pos(button, orientation,))
ModuleUpdateEvent::TogglePopup(Popup::widget_geometry(button, orientation,))
);
});
}

View file

@ -62,7 +62,7 @@ impl Module<Label> for ScriptModule {
let script: Script = self.into();
spawn(async move {
script.run(move |(out, _)| match out {
script.run(None, move |out, _| match out {
OutputStream::Stdout(stdout) => {
try_send!(tx, ModuleUpdateEvent::Update(stdout));
},