1
0
Fork 0
mirror of https://github.com/Zedfrigg/ironbar.git synced 2025-07-03 03:31:03 +02:00

feat(ipc): commands for opening/closing popups

Also includes some refactoring around related GTK helper code
This commit is contained in:
Jake Stanger 2023-07-16 18:57:00 +01:00
parent c582bc3390
commit b7ee794bfc
No known key found for this signature in database
GPG key ID: C51FC8F9CB0BEA61
30 changed files with 747 additions and 345 deletions

View file

@ -2,8 +2,9 @@ use crate::clients::clipboard::{self, ClipboardEvent};
use crate::clients::wayland::{ClipboardItem, ClipboardValue};
use crate::config::{CommonConfig, TruncateMode};
use crate::image::new_icon_button;
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
use crate::popup::Popup;
use crate::modules::{
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, PopupButton, WidgetContext,
};
use crate::try_send;
use gtk::gdk_pixbuf::Pixbuf;
use gtk::gio::{Cancellable, MemoryInputStream};
@ -124,25 +125,26 @@ impl Module<Button> for ClipboardModule {
self,
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
info: &ModuleInfo,
) -> color_eyre::Result<ModuleWidget<Button>> {
let position = info.bar_position;
) -> color_eyre::Result<ModuleParts<Button>> {
let button = new_icon_button(&self.icon, info.icon_theme, self.icon_size);
button.style_context().add_class("btn");
button.connect_clicked(move |button| {
let pos = Popup::widget_geometry(button, position.get_orientation());
try_send!(context.tx, ModuleUpdateEvent::TogglePopup(pos));
try_send!(
context.tx,
ModuleUpdateEvent::TogglePopup(button.popup_id())
);
});
// we need to bind to the receiver as the channel does not open
// until the popup is first opened.
context.widget_rx.attach(None, |_| Continue(true));
Ok(ModuleWidget {
widget: button,
popup: self.into_popup(context.controller_tx, context.popup_rx, info),
})
let popup = self
.into_popup(context.controller_tx, context.popup_rx, info)
.into_popup_parts(vec![&button]);
Ok(ModuleParts::new(button, popup))
}
fn into_popup(

View file

@ -1,19 +1,22 @@
use crate::config::CommonConfig;
use crate::gtk_helpers::add_class;
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
use crate::popup::Popup;
use crate::{send_async, try_send};
use std::env;
use chrono::{DateTime, Local, Locale};
use color_eyre::Result;
use glib::Continue;
use gtk::prelude::*;
use gtk::{Align, Button, Calendar, Label, Orientation};
use serde::Deserialize;
use std::env;
use tokio::spawn;
use tokio::sync::mpsc;
use tokio::time::sleep;
use crate::config::CommonConfig;
use crate::gtk_helpers::IronbarGtkExt;
use crate::modules::{
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, PopupButton, WidgetContext,
};
use crate::{send_async, try_send};
#[derive(Debug, Deserialize, Clone)]
pub struct ClockModule {
/// Date/time format string.
@ -96,17 +99,16 @@ impl Module<Button> for ClockModule {
self,
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
info: &ModuleInfo,
) -> Result<ModuleWidget<Button>> {
) -> Result<ModuleParts<Button>> {
let button = Button::new();
let label = Label::new(None);
label.set_angle(info.bar_position.get_angle());
button.add(&label);
let orientation = info.bar_position.get_orientation();
button.connect_clicked(move |button| {
try_send!(
context.tx,
ModuleUpdateEvent::TogglePopup(Popup::widget_geometry(button, orientation))
ModuleUpdateEvent::TogglePopup(button.popup_id())
);
});
@ -119,12 +121,11 @@ impl Module<Button> for ClockModule {
Continue(true)
});
let popup = self.into_popup(context.controller_tx, context.popup_rx, info);
let popup = self
.into_popup(context.controller_tx, context.popup_rx, info)
.into_popup_parts(vec![&button]);
Ok(ModuleWidget {
widget: button,
popup,
})
Ok(ModuleParts::new(button, popup))
}
fn into_popup(
@ -136,12 +137,12 @@ impl Module<Button> for ClockModule {
let container = gtk::Box::new(Orientation::Vertical, 0);
let clock = Label::builder().halign(Align::Center).build();
add_class(&clock, "calendar-clock");
clock.add_class("calendar-clock");
container.add(&clock);
let calendar = Calendar::new();
add_class(&calendar, "calendar");
calendar.add_class("calendar");
container.add(&calendar);
let format = self.format_popup;

View file

@ -27,7 +27,7 @@ impl CustomWidget for BoxWidget {
if let Some(widgets) = self.widgets {
for widget in widgets {
widget.widget.add_to(&container, context, widget.common);
widget.widget.add_to(&container, &context, widget.common);
}
}

View file

@ -1,11 +1,13 @@
use super::{CustomWidget, CustomWidgetContext, ExecEvent};
use crate::dynamic_value::dynamic_string;
use crate::popup::Popup;
use crate::{build, try_send};
use gtk::prelude::*;
use gtk::{Button, Label};
use serde::Deserialize;
use crate::dynamic_value::dynamic_string;
use crate::modules::PopupButton;
use crate::{build, try_send};
use super::{CustomWidget, CustomWidgetContext, ExecEvent};
#[derive(Debug, Deserialize, Clone)]
pub struct ButtonWidget {
name: Option<String>,
@ -19,6 +21,7 @@ impl CustomWidget for ButtonWidget {
fn into_widget(self, context: CustomWidgetContext) -> Self::Widget {
let button = build!(self, Self::Widget);
context.popup_buttons.borrow_mut().push(button.clone());
if let Some(text) = self.label {
let label = Label::new(None);
@ -32,7 +35,6 @@ impl CustomWidget for ButtonWidget {
}
if let Some(exec) = self.on_click {
let bar_orientation = context.bar_orientation;
let tx = context.tx.clone();
button.connect_clicked(move |button| {
@ -41,7 +43,7 @@ impl CustomWidget for ButtonWidget {
ExecEvent {
cmd: exec.clone(),
args: None,
geometry: Popup::widget_geometry(button, bar_orientation),
id: button.popup_id(),
}
);
});

View file

@ -1,11 +1,13 @@
use super::{CustomWidget, CustomWidgetContext};
use crate::build;
use crate::dynamic_value::dynamic_string;
use crate::image::ImageProvider;
use gtk::prelude::*;
use gtk::Image;
use serde::Deserialize;
use crate::build;
use crate::dynamic_value::dynamic_string;
use crate::image::ImageProvider;
use super::{CustomWidget, CustomWidgetContext};
#[derive(Debug, Deserialize, Clone)]
pub struct ImageWidget {
name: Option<String>,

View file

@ -1,10 +1,12 @@
use super::{CustomWidget, CustomWidgetContext};
use crate::build;
use crate::dynamic_value::dynamic_string;
use gtk::prelude::*;
use gtk::Label;
use serde::Deserialize;
use crate::build;
use crate::dynamic_value::dynamic_string;
use super::{CustomWidget, CustomWidgetContext};
#[derive(Debug, Deserialize, Clone)]
pub struct LabelWidget {
name: Option<String>,

View file

@ -13,15 +13,16 @@ use crate::config::CommonConfig;
use crate::modules::custom::button::ButtonWidget;
use crate::modules::custom::progress::ProgressWidget;
use crate::modules::{
wrap_widget, Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext,
wrap_widget, Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, WidgetContext,
};
use crate::popup::WidgetGeometry;
use crate::script::Script;
use crate::send_async;
use color_eyre::{Report, Result};
use gtk::prelude::*;
use gtk::{IconTheme, Orientation};
use gtk::{Button, IconTheme, Orientation};
use serde::Deserialize;
use std::cell::RefCell;
use std::rc::Rc;
use tokio::spawn;
use tokio::sync::mpsc::{Receiver, Sender};
use tracing::{debug, error};
@ -56,11 +57,12 @@ pub enum Widget {
Progress(ProgressWidget),
}
#[derive(Clone, Copy)]
#[derive(Clone)]
struct CustomWidgetContext<'a> {
tx: &'a Sender<ExecEvent>,
bar_orientation: Orientation,
icon_theme: &'a IconTheme,
popup_buttons: Rc<RefCell<Vec<Button>>>,
}
trait CustomWidget {
@ -115,11 +117,11 @@ fn try_get_orientation(orientation: &str) -> Result<Orientation> {
impl Widget {
/// Creates this widget and adds it to the parent container
fn add_to(self, parent: &gtk::Box, context: CustomWidgetContext, common: CommonConfig) {
fn add_to(self, parent: &gtk::Box, context: &CustomWidgetContext, common: CommonConfig) {
macro_rules! create {
($widget:expr) => {
wrap_widget(
&$widget.into_widget(context),
&$widget.into_widget(context.clone()),
common,
context.bar_orientation,
)
@ -143,7 +145,7 @@ impl Widget {
pub struct ExecEvent {
cmd: String,
args: Option<Vec<String>>,
geometry: WidgetGeometry,
id: usize,
}
impl Module<gtk::Box> for CustomModule {
@ -173,9 +175,9 @@ impl Module<gtk::Box> for CustomModule {
error!("{err:?}");
}
} else if event.cmd == "popup:toggle" {
send_async!(tx, ModuleUpdateEvent::TogglePopup(event.geometry));
send_async!(tx, ModuleUpdateEvent::TogglePopup(event.id));
} else if event.cmd == "popup:open" {
send_async!(tx, ModuleUpdateEvent::OpenPopup(event.geometry));
send_async!(tx, ModuleUpdateEvent::OpenPopup(event.id));
} else if event.cmd == "popup:close" {
send_async!(tx, ModuleUpdateEvent::ClosePopup);
} else {
@ -191,25 +193,30 @@ impl Module<gtk::Box> for CustomModule {
self,
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
info: &ModuleInfo,
) -> Result<ModuleWidget<gtk::Box>> {
) -> Result<ModuleParts<gtk::Box>> {
let orientation = info.bar_position.get_orientation();
let container = gtk::Box::builder().orientation(orientation).build();
let popup_buttons = Rc::new(RefCell::new(Vec::new()));
let custom_context = CustomWidgetContext {
tx: &context.controller_tx,
bar_orientation: orientation,
icon_theme: info.icon_theme,
popup_buttons: popup_buttons.clone(),
};
self.bar.clone().into_iter().for_each(|widget| {
widget
.widget
.add_to(&container, custom_context, widget.common);
.add_to(&container, &custom_context, widget.common);
});
let popup = self.into_popup(context.controller_tx, context.popup_rx, info);
let popup = self
.into_popup(context.controller_tx, context.popup_rx, info)
.into_popup_parts_owned(popup_buttons.take());
Ok(ModuleWidget {
Ok(ModuleParts {
widget: container,
popup,
})
@ -231,12 +238,13 @@ impl Module<gtk::Box> for CustomModule {
tx: &tx,
bar_orientation: info.bar_position.get_orientation(),
icon_theme: info.icon_theme,
popup_buttons: Rc::new(RefCell::new(vec![])),
};
for widget in popup {
widget
.widget
.add_to(&container, custom_context, widget.common);
.add_to(&container, &custom_context, widget.common);
}
}

View file

@ -1,14 +1,16 @@
use super::{try_get_orientation, CustomWidget, CustomWidgetContext};
use crate::dynamic_value::dynamic_string;
use crate::modules::custom::set_length;
use crate::script::{OutputStream, Script, ScriptInput};
use crate::{build, send};
use gtk::prelude::*;
use gtk::ProgressBar;
use serde::Deserialize;
use tokio::spawn;
use tracing::error;
use crate::dynamic_value::dynamic_string;
use crate::modules::custom::set_length;
use crate::script::{OutputStream, Script, ScriptInput};
use crate::{build, send};
use super::{try_get_orientation, CustomWidget, CustomWidgetContext};
#[derive(Debug, Deserialize, Clone)]
pub struct ProgressWidget {
name: Option<String>,

View file

@ -1,16 +1,18 @@
use super::{try_get_orientation, CustomWidget, CustomWidgetContext, ExecEvent};
use crate::modules::custom::set_length;
use crate::popup::Popup;
use crate::script::{OutputStream, Script, ScriptInput};
use crate::{build, send, try_send};
use std::cell::Cell;
use std::ops::Neg;
use gtk::prelude::*;
use gtk::Scale;
use serde::Deserialize;
use std::cell::Cell;
use std::ops::Neg;
use tokio::spawn;
use tracing::error;
use crate::modules::custom::set_length;
use crate::script::{OutputStream, Script, ScriptInput};
use crate::{build, send, try_send};
use super::{try_get_orientation, CustomWidget, CustomWidgetContext, ExecEvent};
#[derive(Debug, Deserialize, Clone)]
pub struct SliderWidget {
name: Option<String>,
@ -78,7 +80,7 @@ impl CustomWidget for SliderWidget {
Inhibit(false)
});
scale.connect_change_value(move |scale, _, val| {
scale.connect_change_value(move |_, _, val| {
// GTK will send values outside min/max range
let val = val.clamp(min, max);
@ -88,7 +90,7 @@ impl CustomWidget for SliderWidget {
ExecEvent {
cmd: on_change.clone(),
args: Some(vec![val.to_string()]),
geometry: Popup::widget_geometry(scale, context.bar_orientation),
id: usize::MAX // ignored
}
);

View file

@ -1,8 +1,8 @@
use crate::clients::wayland::{self, ToplevelEvent};
use crate::config::{CommonConfig, TruncateMode};
use crate::gtk_helpers::add_class;
use crate::gtk_helpers::IronbarGtkExt;
use crate::image::ImageProvider;
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
use crate::{lock, send_async, try_send};
use color_eyre::Result;
use glib::Continue;
@ -104,19 +104,19 @@ impl Module<gtk::Box> for FocusedModule {
self,
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
info: &ModuleInfo,
) -> Result<ModuleWidget<gtk::Box>> {
) -> Result<ModuleParts<gtk::Box>> {
let icon_theme = info.icon_theme;
let container = gtk::Box::new(info.bar_position.get_orientation(), 5);
let icon = gtk::Image::new();
if self.show_icon {
add_class(&icon, "icon");
icon.add_class("icon");
container.add(&icon);
}
let label = Label::new(None);
add_class(&label, "label");
label.add_class("label");
if let Some(truncate) = self.truncate {
truncate.truncate_label(&label);
@ -144,7 +144,7 @@ impl Module<gtk::Box> for FocusedModule {
});
}
Ok(ModuleWidget {
Ok(ModuleParts {
widget: container,
popup: None,
})

View file

@ -1,6 +1,6 @@
use crate::config::CommonConfig;
use crate::dynamic_value::dynamic_string;
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
use crate::try_send;
use color_eyre::Result;
use glib::Continue;
@ -52,7 +52,7 @@ impl Module<Label> for LabelModule {
self,
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
_info: &ModuleInfo,
) -> Result<ModuleWidget<Label>> {
) -> Result<ModuleParts<Label>> {
let label = Label::new(None);
label.set_use_markup(true);
@ -64,7 +64,7 @@ impl Module<Label> for LabelModule {
});
}
Ok(ModuleWidget {
Ok(ModuleParts {
widget: label,
popup: None,
})

View file

@ -1,9 +1,9 @@
use super::open_state::OpenState;
use crate::clients::wayland::ToplevelHandle;
use crate::gtk_helpers::IronbarGtkExt;
use crate::image::ImageProvider;
use crate::modules::launcher::{ItemEvent, LauncherUpdate};
use crate::modules::ModuleUpdateEvent;
use crate::popup::Popup;
use crate::{read_lock, try_send};
use color_eyre::{Report, Result};
use gtk::prelude::*;
@ -249,7 +249,7 @@ impl ItemButton {
try_send!(
tx,
ModuleUpdateEvent::OpenPopup(Popup::widget_geometry(button, orientation))
ModuleUpdateEvent::OpenPopupAt(button.geometry(orientation))
);
} else {
try_send!(tx, ModuleUpdateEvent::ClosePopup);

View file

@ -7,7 +7,9 @@ use crate::clients::wayland::{self, ToplevelEvent};
use crate::config::CommonConfig;
use crate::desktop_file::find_desktop_file;
use crate::modules::launcher::item::AppearanceOptions;
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
use crate::modules::{
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, WidgetContext,
};
use crate::{arc_mut, lock, send_async, try_send, write_lock};
use color_eyre::{Help, Report};
use glib::Continue;
@ -313,7 +315,7 @@ impl Module<gtk::Box> for LauncherModule {
self,
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
info: &ModuleInfo,
) -> crate::Result<ModuleWidget<gtk::Box>> {
) -> crate::Result<ModuleParts<gtk::Box>> {
let icon_theme = info.icon_theme;
let container = gtk::Box::new(info.bar_position.get_orientation(), 0);
@ -408,8 +410,11 @@ impl Module<gtk::Box> for LauncherModule {
});
}
let popup = self.into_popup(context.controller_tx, context.popup_rx, info);
Ok(ModuleWidget {
let popup = self
.into_popup(context.controller_tx, context.popup_rx, info)
.into_popup_parts(vec![]); // since item buttons are dynamic, they pass their geometry directly
Ok(ModuleParts {
widget: container,
popup,
})

View file

@ -1,3 +1,19 @@
use std::sync::{Arc, RwLock};
use color_eyre::Result;
use glib::IsA;
use gtk::gdk::{EventMask, Monitor};
use gtk::prelude::*;
use gtk::{Application, Button, EventBox, IconTheme, Orientation, Revealer, Widget};
use tokio::sync::mpsc;
use tracing::debug;
use crate::bridge_channel::BridgeChannel;
use crate::config::{BarPosition, CommonConfig, TransitionType};
use crate::gtk_helpers::{IronbarGtkExt, WidgetGeometry};
use crate::popup::Popup;
use crate::{send, write_lock};
#[cfg(feature = "clipboard")]
pub mod clipboard;
/// Displays the current date and time.
@ -24,19 +40,6 @@ pub mod upower;
#[cfg(feature = "workspaces")]
pub mod workspaces;
use crate::bridge_channel::BridgeChannel;
use crate::config::{BarPosition, CommonConfig, TransitionType};
use crate::popup::{Popup, WidgetGeometry};
use crate::{read_lock, send, write_lock};
use color_eyre::Result;
use glib::IsA;
use gtk::gdk::{EventMask, Monitor};
use gtk::prelude::*;
use gtk::{Application, EventBox, IconTheme, Orientation, Revealer, Widget};
use std::sync::{Arc, RwLock};
use tokio::sync::mpsc;
use tracing::debug;
#[derive(Clone)]
pub enum ModuleLocation {
Left,
@ -54,13 +57,15 @@ pub struct ModuleInfo<'a> {
#[derive(Debug)]
pub enum ModuleUpdateEvent<T> {
/// Sends an update to the module UI
/// Sends an update to the module UI.
Update(T),
/// Toggles the open state of the popup.
TogglePopup(WidgetGeometry),
/// Takes the button ID.
TogglePopup(usize),
/// Force sets the popup open.
/// Takes the button X position and width.
OpenPopup(WidgetGeometry),
/// Takes the button ID.
OpenPopup(usize),
OpenPopupAt(WidgetGeometry),
/// Force sets the popup closed.
ClosePopup,
}
@ -73,9 +78,55 @@ pub struct WidgetContext<TSend, TReceive> {
pub popup_rx: glib::Receiver<TSend>,
}
pub struct ModuleWidget<W: IsA<Widget>> {
pub struct ModuleParts<W: IsA<Widget>> {
pub widget: W,
pub popup: Option<gtk::Box>,
pub popup: Option<ModulePopupParts>,
}
impl<W: IsA<Widget>> ModuleParts<W> {
fn new(widget: W, popup: Option<ModulePopupParts>) -> Self {
Self { widget, popup }
}
}
#[derive(Debug, Clone)]
pub struct ModulePopupParts {
/// The popup container, with all its contents
pub container: gtk::Box,
/// An array of buttons which can be used for opening the popup.
/// For most modules, this will only be a single button.
/// For some advanced modules, such as `Launcher`, this is all item buttons.
pub buttons: Vec<Button>,
}
pub trait ModulePopup {
fn into_popup_parts(self, buttons: Vec<&Button>) -> Option<ModulePopupParts>;
fn into_popup_parts_owned(self, buttons: Vec<Button>) -> Option<ModulePopupParts>;
}
impl ModulePopup for Option<gtk::Box> {
fn into_popup_parts(self, buttons: Vec<&Button>) -> Option<ModulePopupParts> {
self.into_popup_parts_owned(buttons.into_iter().cloned().collect())
}
fn into_popup_parts_owned(self, buttons: Vec<Button>) -> Option<ModulePopupParts> {
self.map(|container| ModulePopupParts { container, buttons })
}
}
pub trait PopupButton {
fn popup_id(&self) -> usize;
}
impl PopupButton for Button {
/// Gets the popup ID associated with this button.
/// This should only be called on buttons which are known to be associated with popups.
///
/// # Panics
/// Will panic if an ID has not been set.
fn popup_id(&self) -> usize {
*self.get_tag("popup-id").expect("data to exist")
}
}
pub trait Module<W>
@ -98,7 +149,7 @@ where
self,
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
info: &ModuleInfo,
) -> Result<ModuleWidget<W>>;
) -> Result<ModuleParts<W>>;
fn into_popup(
self,
@ -118,9 +169,10 @@ where
pub fn create_module<TModule, TWidget, TSend, TRec>(
module: TModule,
id: usize,
name: Option<String>,
info: &ModuleInfo,
popup: &Arc<RwLock<Popup>>,
) -> Result<ModuleWidget<TWidget>>
) -> Result<ModuleParts<TWidget>>
where
TModule: Module<TWidget, SendMessage = TSend, ReceiveMessage = TRec>,
TWidget: IsA<Widget>,
@ -142,29 +194,45 @@ where
controller_tx: ui_tx,
};
let name = TModule::name();
let module_name = TModule::name();
let instance_name = name.unwrap_or_else(|| module_name.to_string());
let module_parts = module.into_widget(context, info)?;
module_parts.widget.style_context().add_class(name);
module_parts.widget.style_context().add_class(module_name);
let mut has_popup = false;
if let Some(popup_content) = module_parts.popup.clone() {
let has_popup = if let Some(popup_content) = module_parts.popup.clone() {
popup_content
.container
.style_context()
.add_class(&format!("popup-{name}"));
.add_class(&format!("popup-{module_name}"));
register_popup_content(popup, id, popup_content);
has_popup = true;
}
register_popup_content(popup, id, instance_name, popup_content);
true
} else {
false
};
setup_receiver(channel, w_tx, p_tx, popup.clone(), name, id, has_popup);
setup_receiver(
channel,
w_tx,
p_tx,
popup.clone(),
module_name,
id,
has_popup,
);
Ok(module_parts)
}
/// Registers the popup content with the popup.
fn register_popup_content(popup: &Arc<RwLock<Popup>>, id: usize, popup_content: gtk::Box) {
write_lock!(popup).register_content(id, popup_content);
fn register_popup_content(
popup: &Arc<RwLock<Popup>>,
id: usize,
name: String,
popup_content: ModulePopupParts,
) {
write_lock!(popup).register_content(id, name, popup_content);
}
/// Sets up the bridge channel receiver
@ -196,40 +264,51 @@ fn setup_receiver<TSend>(
send!(w_tx, update);
}
ModuleUpdateEvent::TogglePopup(geometry) => {
ModuleUpdateEvent::TogglePopup(button_id) => {
debug!("Toggling popup for {} [#{}]", name, id);
let popup = read_lock!(popup);
let mut popup = write_lock!(popup);
if popup.is_visible() {
popup.hide();
} else {
popup.show_content(id);
popup.show(geometry);
popup.show(id, button_id);
// force re-render on initial open to try and fix size issue
if !has_popup_opened {
popup.show_content(id);
popup.show(geometry);
popup.show(id, button_id);
has_popup_opened = true;
}
}
}
ModuleUpdateEvent::OpenPopup(geometry) => {
ModuleUpdateEvent::OpenPopup(button_id) => {
debug!("Opening popup for {} [#{}]", name, id);
let popup = read_lock!(popup);
let mut popup = write_lock!(popup);
popup.hide();
popup.show_content(id);
popup.show(geometry);
popup.show(id, button_id);
// force re-render on initial open to try and fix size issue
if !has_popup_opened {
popup.show_content(id);
popup.show(geometry);
popup.show(id, button_id);
has_popup_opened = true;
}
}
ModuleUpdateEvent::OpenPopupAt(geometry) => {
debug!("Opening popup for {} [#{}]", name, id);
let mut popup = write_lock!(popup);
popup.hide();
popup.show_at(id, geometry);
// force re-render on initial open to try and fix size issue
if !has_popup_opened {
popup.show_at(id, geometry);
has_popup_opened = true;
}
}
ModuleUpdateEvent::ClosePopup => {
debug!("Closing popup for {} [#{}]", name, id);
let popup = read_lock!(popup);
let mut popup = write_lock!(popup);
popup.hide();
}
}
@ -239,14 +318,14 @@ fn setup_receiver<TSend>(
}
pub fn set_widget_identifiers<TWidget: IsA<Widget>>(
widget_parts: &ModuleWidget<TWidget>,
widget_parts: &ModuleParts<TWidget>,
common: &CommonConfig,
) {
if let Some(ref name) = common.name {
widget_parts.widget.set_widget_name(name);
if let Some(ref popup) = widget_parts.popup {
popup.set_widget_name(&format!("popup-{name}"));
popup.container.set_widget_name(&format!("popup-{name}"));
}
}
@ -258,7 +337,10 @@ pub fn set_widget_identifiers<TWidget: IsA<Widget>>(
if let Some(ref popup) = widget_parts.popup {
for part in class.split(' ') {
popup.style_context().add_class(&format!("popup-{part}"));
popup
.container
.style_context()
.add_class(&format!("popup-{part}"));
}
}
}

View file

@ -1,29 +1,33 @@
mod config;
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;
use crate::clients::music::{
self, MusicClient, PlayerState, PlayerUpdate, ProgressTick, Status, Track,
};
use crate::gtk_helpers::add_class;
use crate::image::{new_icon_button, new_icon_label, ImageProvider};
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
use crate::popup::Popup;
use crate::{send_async, try_send};
use color_eyre::Result;
use glib::{Continue, PropertySet};
use gtk::prelude::*;
use gtk::{Button, IconTheme, Label, Orientation, Scale};
use regex::Regex;
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;
use tokio::spawn;
use tokio::sync::mpsc::{Receiver, Sender};
use tracing::error;
use crate::clients::music::{
self, MusicClient, PlayerState, PlayerUpdate, ProgressTick, Status, Track,
};
use crate::gtk_helpers::IronbarGtkExt;
use crate::image::{new_icon_button, new_icon_label, ImageProvider};
use crate::modules::PopupButton;
use crate::modules::{
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, WidgetContext,
};
use crate::{send_async, try_send};
pub use self::config::MusicModule;
use self::config::PlayerType;
mod config;
#[derive(Debug)]
pub enum PlayerCommand {
Previous,
@ -178,10 +182,10 @@ impl Module<Button> for MusicModule {
self,
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
info: &ModuleInfo,
) -> Result<ModuleWidget<Button>> {
) -> Result<ModuleParts<Button>> {
let button = Button::new();
let button_contents = gtk::Box::new(Orientation::Horizontal, 5);
add_class(&button_contents, "contents");
button_contents.add_class("contents");
button.add(&button_contents);
@ -199,16 +203,11 @@ impl Module<Button> for MusicModule {
button_contents.add(&icon_play);
button_contents.add(&label);
let orientation = info.bar_position.get_orientation();
{
let tx = context.tx.clone();
button.connect_clicked(move |button| {
try_send!(
tx,
ModuleUpdateEvent::TogglePopup(Popup::widget_geometry(button, orientation,))
);
try_send!(tx, ModuleUpdateEvent::TogglePopup(button.popup_id()));
});
}
@ -252,12 +251,11 @@ impl Module<Button> for MusicModule {
});
};
let popup = self.into_popup(context.controller_tx, context.popup_rx, info);
let popup = self
.into_popup(context.controller_tx, context.popup_rx, info)
.into_popup_parts(vec![&button]);
Ok(ModuleWidget {
widget: button,
popup,
})
Ok(ModuleParts::new(button, popup))
}
fn into_popup(
@ -275,7 +273,7 @@ impl Module<Button> for MusicModule {
.width_request(128)
.height_request(128)
.build();
add_class(&album_image, "album-art");
album_image.add_class("album-art");
let icons = self.icons;
@ -284,28 +282,28 @@ impl Module<Button> for MusicModule {
let album_label = IconLabel::new(&icons.album, None, icon_theme);
let artist_label = IconLabel::new(&icons.artist, None, icon_theme);
add_class(&title_label.container, "title");
add_class(&album_label.container, "album");
add_class(&artist_label.container, "artist");
title_label.container.add_class("title");
album_label.container.add_class("album");
artist_label.container.add_class("artist");
info_box.add(&title_label.container);
info_box.add(&album_label.container);
info_box.add(&artist_label.container);
let controls_box = gtk::Box::new(Orientation::Horizontal, 0);
add_class(&controls_box, "controls");
controls_box.add_class("controls");
let btn_prev = new_icon_button(&icons.prev, icon_theme, self.icon_size);
add_class(&btn_prev, "btn-prev");
btn_prev.add_class("btn-prev");
let btn_play = new_icon_button(&icons.play, icon_theme, self.icon_size);
add_class(&btn_play, "btn-play");
btn_play.add_class("btn-play");
let btn_pause = new_icon_button(&icons.pause, icon_theme, self.icon_size);
add_class(&btn_pause, "btn-pause");
btn_pause.add_class("btn-pause");
let btn_next = new_icon_button(&icons.next, icon_theme, self.icon_size);
add_class(&btn_next, "btn-next");
btn_next.add_class("btn-next");
controls_box.add(&btn_prev);
controls_box.add(&btn_play);
@ -315,14 +313,14 @@ impl Module<Button> for MusicModule {
info_box.add(&controls_box);
let volume_box = gtk::Box::new(Orientation::Vertical, 5);
add_class(&volume_box, "volume");
volume_box.add_class("volume");
let volume_slider = Scale::with_range(Orientation::Vertical, 0.0, 100.0, 5.0);
volume_slider.set_inverted(true);
add_class(&volume_slider, "slider");
volume_slider.add_class("slider");
let volume_icon = new_icon_label(&icons.volume, icon_theme, self.icon_size);
add_class(&volume_icon, "icon");
volume_icon.add_class("icon");
volume_box.pack_start(&volume_slider, true, true, 0);
volume_box.pack_end(&volume_icon, false, false, 0);
@ -359,17 +357,17 @@ impl Module<Button> for MusicModule {
});
let progress_box = gtk::Box::new(Orientation::Horizontal, 5);
add_class(&progress_box, "progress");
progress_box.add_class("progress");
let progress_label = Label::new(None);
add_class(&progress_label, "label");
progress_label.add_class("label");
let progress = Scale::builder()
.orientation(Orientation::Horizontal)
.draw_value(false)
.hexpand(true)
.build();
add_class(&progress, "slider");
progress.add_class("slider");
progress_box.add(&progress);
progress_box.add(&progress_label);
@ -546,8 +544,8 @@ impl IconLabel {
let icon = new_icon_label(icon_input, icon_theme, 24);
let label = Label::new(label);
add_class(&icon, "icon-box");
add_class(&label, "label");
icon.add_class("icon-box");
label.add_class("label");
container.add(&icon);
container.add(&label);

View file

@ -1,5 +1,5 @@
use crate::config::CommonConfig;
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
use crate::script::{OutputStream, Script, ScriptMode};
use crate::try_send;
use color_eyre::{Help, Report, Result};
@ -83,7 +83,7 @@ impl Module<Label> for ScriptModule {
self,
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
info: &ModuleInfo,
) -> Result<ModuleWidget<Label>> {
) -> Result<ModuleParts<Label>> {
let label = Label::builder().use_markup(true).build();
label.set_angle(info.bar_position.get_angle());
@ -95,7 +95,7 @@ impl Module<Label> for ScriptModule {
});
}
Ok(ModuleWidget {
Ok(ModuleParts {
widget: label,
popup: None,
})

View file

@ -1,6 +1,6 @@
use crate::config::CommonConfig;
use crate::gtk_helpers::add_class;
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
use crate::gtk_helpers::IronbarGtkExt;
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
use crate::send_async;
use color_eyre::Result;
use gtk::prelude::*;
@ -186,7 +186,7 @@ impl Module<gtk::Box> for SysInfoModule {
self,
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
info: &ModuleInfo,
) -> Result<ModuleWidget<gtk::Box>> {
) -> Result<ModuleParts<gtk::Box>> {
let re = Regex::new(r"\{([^}]+)}")?;
let container = gtk::Box::new(info.bar_position.get_orientation(), 10);
@ -196,7 +196,7 @@ impl Module<gtk::Box> for SysInfoModule {
for format in &self.format {
let label = Label::builder().label(format).use_markup(true).build();
add_class(&label, "item");
label.add_class("item");
label.set_angle(info.bar_position.get_angle());
container.add(&label);
@ -220,7 +220,7 @@ impl Module<gtk::Box> for SysInfoModule {
});
}
Ok(ModuleWidget {
Ok(ModuleParts {
widget: container,
popup: None,
})

View file

@ -1,6 +1,6 @@
use crate::clients::system_tray::get_tray_event_client;
use crate::config::CommonConfig;
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
use crate::{await_sync, try_send};
use color_eyre::Result;
use gtk::gdk_pixbuf::{Colorspace, InterpType};
@ -172,7 +172,7 @@ impl Module<MenuBar> for TrayModule {
self,
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
_info: &ModuleInfo,
) -> Result<ModuleWidget<MenuBar>> {
) -> Result<ModuleParts<MenuBar>> {
let container = MenuBar::new();
{
@ -238,7 +238,7 @@ impl Module<MenuBar> for TrayModule {
});
};
Ok(ModuleWidget {
Ok(ModuleParts {
widget: container,
popup: None,
})

View file

@ -1,10 +1,3 @@
use crate::clients::upower::get_display_proxy;
use crate::config::CommonConfig;
use crate::gtk_helpers::add_class;
use crate::image::ImageProvider;
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
use crate::popup::Popup;
use crate::{await_sync, error, send_async, try_send};
use color_eyre::Result;
use futures_lite::stream::StreamExt;
use gtk::{prelude::*, Button};
@ -15,6 +8,16 @@ use tokio::sync::mpsc::{Receiver, Sender};
use upower_dbus::BatteryState;
use zbus;
use crate::clients::upower::get_display_proxy;
use crate::config::CommonConfig;
use crate::gtk_helpers::IronbarGtkExt;
use crate::image::ImageProvider;
use crate::modules::PopupButton;
use crate::modules::{
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, WidgetContext,
};
use crate::{await_sync, error, send_async, try_send};
const DAY: i64 = 24 * 60 * 60;
const HOUR: i64 = 60 * 60;
const MINUTE: i64 = 60;
@ -150,32 +153,31 @@ impl Module<gtk::Button> for UpowerModule {
self,
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
info: &ModuleInfo,
) -> Result<ModuleWidget<Button>> {
) -> Result<ModuleParts<Button>> {
let icon_theme = info.icon_theme.clone();
let icon = gtk::Image::new();
add_class(&icon, "icon");
icon.add_class("icon");
let label = Label::builder()
.label(&self.format)
.use_markup(true)
.build();
add_class(&label, "label");
label.add_class("label");
let container = gtk::Box::new(Orientation::Horizontal, 5);
add_class(&container, "contents");
container.add_class("contents");
let button = Button::new();
add_class(&button, "button");
button.add_class("button");
container.add(&icon);
container.add(&label);
button.add(&container);
let orientation = info.bar_position.get_orientation();
button.connect_clicked(move |button| {
try_send!(
context.tx,
ModuleUpdateEvent::TogglePopup(Popup::widget_geometry(button, orientation))
ModuleUpdateEvent::TogglePopup(button.popup_id())
);
});
@ -193,12 +195,11 @@ impl Module<gtk::Button> for UpowerModule {
Continue(true)
});
let popup = self.into_popup(context.controller_tx, context.popup_rx, info);
let popup = self
.into_popup(context.controller_tx, context.popup_rx, info)
.into_popup_parts(vec![&button]);
Ok(ModuleWidget {
widget: button,
popup,
})
Ok(ModuleParts::new(button, popup))
}
fn into_popup(
@ -215,7 +216,7 @@ impl Module<gtk::Button> for UpowerModule {
.build();
let label = Label::new(None);
add_class(&label, "upower-details");
label.add_class("upower-details");
container.add(&label);
rx.attach(None, move |properties| {

View file

@ -1,7 +1,7 @@
use crate::clients::compositor::{Compositor, WorkspaceUpdate};
use crate::config::CommonConfig;
use crate::image::new_icon_button;
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
use crate::{send_async, try_send};
use color_eyre::{Report, Result};
use gtk::prelude::*;
@ -154,7 +154,7 @@ impl Module<gtk::Box> for WorkspacesModule {
self,
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
info: &ModuleInfo,
) -> Result<ModuleWidget<gtk::Box>> {
) -> Result<ModuleParts<gtk::Box>> {
let container = gtk::Box::new(info.bar_position.get_orientation(), 0);
let name_map = self.name_map.unwrap_or_default();
@ -277,7 +277,7 @@ impl Module<gtk::Box> for WorkspacesModule {
});
}
Ok(ModuleWidget {
Ok(ModuleParts {
widget: container,
popup: None,
})