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

feat: ability to add custom modules instead native modules

Resolves #131
This commit is contained in:
Jake Stanger 2024-03-14 22:35:33 +00:00
parent 42ae915645
commit 994f4a4a12
No known key found for this signature in database
GPG key ID: C51FC8F9CB0BEA61
21 changed files with 497 additions and 252 deletions

View file

@ -1,6 +1,11 @@
Allows you to compose custom modules consisting of multiple widgets, including popups. Allows you to compose custom modules consisting of multiple modules and widgets, including popups.
Labels can display dynamic content from scripts, and buttons can interact with the bar or execute commands on click. Labels can display dynamic content from scripts, and buttons can interact with the bar or execute commands on click.
The module provides a set of utility widgets, such as containers, labels and buttons.
In addition to these, you can also add any native module.
Paired with the other custom modules such as Cairo,
this provides a powerful declarative interface for constructing your own interfaces.
If you only intend to run a single script, prefer the [script](script) module, If you only intend to run a single script, prefer the [script](script) module,
or [label](label) if you only need a single text label. or [label](label) if you only need a single text label.
@ -13,6 +18,11 @@ or [label](label) if you only need a single text label.
This module can be quite fiddly to configure as you effectively have to build a tree of widgets by hand. This module can be quite fiddly to configure as you effectively have to build a tree of widgets by hand.
It is well worth looking at the examples. It is well worth looking at the examples.
| Name | Type | Default | Description |
|---------|------------------------|------------|------------------------------------------|
| `bar` | `(Module or Widget)[]` | `[]` | Modules and widgets to add to the bar. |
| `popup` | `(Module or Widget)[]` | `null` | Modules and widgets to add to the popup. |
### `Widget` ### `Widget`
There are many widget types, each with their own config options. There are many widget types, each with their own config options.
@ -36,7 +46,7 @@ A container to place nested widgets inside.
| Name | Type | Default | Description | | Name | Type | Default | Description |
|---------------|------------------------------------------------------------|----------------|-------------------------------------------------------------------| |---------------|------------------------------------------------------------|----------------|-------------------------------------------------------------------|
| `orientation` | `'horizontal'` or `'vertical'` (shorthand: `'h'` or `'v'`) | `'horizontal'` | Whether child widgets should be horizontally or vertically added. | | `orientation` | `'horizontal'` or `'vertical'` (shorthand: `'h'` or `'v'`) | `'horizontal'` | Whether child widgets should be horizontally or vertically added. |
| `widgets` | `Widget[]` | `[]` | List of widgets to add to this box. | | `widgets` | `(Module or Widget)[]` | `[]` | List of widgets to add to this box. |
#### Label #### Label

View file

@ -1,7 +1,5 @@
use crate::config::{BarConfig, BarPosition, MarginConfig, ModuleConfig}; use crate::config::{BarConfig, BarPosition, MarginConfig, ModuleConfig};
use crate::modules::{ use crate::modules::{BarModuleFactory, ModuleInfo, ModuleLocation};
create_module, set_widget_identifiers, wrap_widget, ModuleInfo, ModuleLocation,
};
use crate::popup::Popup; use crate::popup::Popup;
use crate::Ironbar; use crate::Ironbar;
use color_eyre::Result; use color_eyre::Result;
@ -350,55 +348,10 @@ fn add_modules(
ironbar: &Rc<Ironbar>, ironbar: &Rc<Ironbar>,
popup: &Rc<Popup>, popup: &Rc<Popup>,
) -> Result<()> { ) -> Result<()> {
let orientation = info.bar_position.orientation(); let module_factory = BarModuleFactory::new(ironbar.clone(), popup.clone()).into();
macro_rules! add_module {
($module:expr, $id:expr) => {{
let common = $module.common.take().expect("common config to exist");
let widget_parts = create_module(
*$module,
$id,
ironbar.clone(),
common.name.clone(),
&info,
&Rc::clone(&popup),
)?;
set_widget_identifiers(&widget_parts, &common);
let container = wrap_widget(&widget_parts.widget, common, orientation);
content.add(&container);
}};
}
for config in modules { for config in modules {
let id = Ironbar::unique_id(); config.create(&module_factory, content, info)?;
match config {
#[cfg(feature = "clipboard")]
ModuleConfig::Clipboard(mut module) => add_module!(module, id),
#[cfg(feature = "clock")]
ModuleConfig::Clock(mut module) => add_module!(module, id),
ModuleConfig::Custom(mut module) => add_module!(module, id),
#[cfg(feature = "focused")]
ModuleConfig::Focused(mut module) => add_module!(module, id),
ModuleConfig::Label(mut module) => add_module!(module, id),
#[cfg(feature = "launcher")]
ModuleConfig::Launcher(mut module) => add_module!(module, id),
#[cfg(feature = "music")]
ModuleConfig::Music(mut module) => add_module!(module, id),
#[cfg(feature = "notifications")]
ModuleConfig::Notifications(mut module) => add_module!(module, id),
ModuleConfig::Script(mut module) => add_module!(module, id),
#[cfg(feature = "sys_info")]
ModuleConfig::SysInfo(mut module) => add_module!(module, id),
#[cfg(feature = "tray")]
ModuleConfig::Tray(mut module) => add_module!(module, id),
#[cfg(feature = "upower")]
ModuleConfig::Upower(mut module) => add_module!(module, id),
#[cfg(feature = "volume")]
ModuleConfig::Volume(mut module) => add_module!(module, id),
#[cfg(feature = "workspaces")]
ModuleConfig::Workspaces(mut module) => add_module!(module, id),
}
} }
Ok(()) Ok(())

View file

@ -27,7 +27,10 @@ use crate::modules::upower::UpowerModule;
use crate::modules::volume::VolumeModule; use crate::modules::volume::VolumeModule;
#[cfg(feature = "workspaces")] #[cfg(feature = "workspaces")]
use crate::modules::workspaces::WorkspacesModule; use crate::modules::workspaces::WorkspacesModule;
use crate::modules::{AnyModuleFactory, ModuleFactory, ModuleInfo};
use cfg_if::cfg_if; use cfg_if::cfg_if;
use color_eyre::Result;
use serde::Deserialize; use serde::Deserialize;
use std::collections::HashMap; use std::collections::HashMap;
@ -64,6 +67,49 @@ pub enum ModuleConfig {
Workspaces(Box<WorkspacesModule>), Workspaces(Box<WorkspacesModule>),
} }
impl ModuleConfig {
pub fn create(
self,
module_factory: &AnyModuleFactory,
container: &gtk::Box,
info: &ModuleInfo,
) -> Result<()> {
macro_rules! create {
($module:expr) => {
module_factory.create(*$module, container, info)
};
}
match self {
#[cfg(feature = "clipboard")]
Self::Clipboard(module) => create!(module),
#[cfg(feature = "clock")]
Self::Clock(module) => create!(module),
Self::Custom(module) => create!(module),
#[cfg(feature = "focused")]
Self::Focused(module) => create!(module),
Self::Label(module) => create!(module),
#[cfg(feature = "launcher")]
Self::Launcher(module) => create!(module),
#[cfg(feature = "music")]
Self::Music(module) => create!(module),
#[cfg(feature = "notifications")]
Self::Notifications(module) => create!(module),
Self::Script(module) => create!(module),
#[cfg(feature = "sys_info")]
Self::SysInfo(module) => create!(module),
#[cfg(feature = "tray")]
Self::Tray(module) => create!(module),
#[cfg(feature = "upower")]
Self::Upower(module) => create!(module),
#[cfg(feature = "volume")]
Self::Volume(module) => create!(module),
#[cfg(feature = "workspaces")]
Self::Workspaces(module) => create!(module),
}
}
}
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
pub enum BarEntryConfig { pub enum BarEntryConfig {
Single(BarConfig), Single(BarConfig),

View file

@ -172,7 +172,7 @@ impl Ipc {
popup.hide(); popup.hide();
let data = popup let data = popup
.cache .container_cache
.borrow() .borrow()
.iter() .iter()
.find(|(_, value)| value.name == name) .find(|(_, value)| value.name == name)
@ -209,7 +209,7 @@ impl Ipc {
popup.hide(); popup.hide();
let data = popup let data = popup
.cache .container_cache
.borrow() .borrow()
.iter() .iter()
.find(|(_, value)| value.name == name) .find(|(_, value)| value.name == name)

View file

@ -1,3 +1,31 @@
/// Provides implementations of methods required by the `Module` trait
/// which cannot be included as part of the trait.
///
/// This removes the need to add the same boilerplate method definitions
/// to every module implementation.
///
/// # Usage:
///
/// ```rs
/// impl Module for ClockModule {
/// type SendMessage = DateTime<Local>;
/// type ReceiveMessage = ();
///
/// module_impl!("clock");
/// }
#[macro_export]
macro_rules! module_impl {
($name:literal) => {
fn name() -> &'static str {
$name
}
fn take_common(&mut self) -> $crate::config::CommonConfig {
self.common.take().expect("common config to exist")
}
};
}
/// Sends a message on an asynchronous `Sender` using `send()` /// Sends a message on an asynchronous `Sender` using `send()`
/// Panics if the message cannot be sent. /// Panics if the message cannot be sent.
/// ///

View file

@ -5,7 +5,7 @@ use crate::image::new_icon_button;
use crate::modules::{ use crate::modules::{
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, PopupButton, WidgetContext, Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, PopupButton, WidgetContext,
}; };
use crate::{glib_recv, spawn, try_send}; use crate::{glib_recv, module_impl, spawn, try_send};
use glib::Propagation; use glib::Propagation;
use gtk::gdk_pixbuf::Pixbuf; use gtk::gdk_pixbuf::Pixbuf;
use gtk::gio::{Cancellable, MemoryInputStream}; use gtk::gio::{Cancellable, MemoryInputStream};
@ -65,9 +65,7 @@ impl Module<Button> for ClipboardModule {
type SendMessage = ControllerEvent; type SendMessage = ControllerEvent;
type ReceiveMessage = UIEvent; type ReceiveMessage = UIEvent;
fn name() -> &'static str { module_impl!("clipboard");
"clipboard"
}
fn spawn_controller( fn spawn_controller(
&self, &self,
@ -137,7 +135,7 @@ impl Module<Button> for ClipboardModule {
let rx = context.subscribe(); let rx = context.subscribe();
let popup = self let popup = self
.into_popup(context.controller_tx, rx, info) .into_popup(context.controller_tx.clone(), rx, context, info)
.into_popup_parts(vec![&button]); .into_popup_parts(vec![&button]);
Ok(ModuleParts::new(button, popup)) Ok(ModuleParts::new(button, popup))
@ -147,6 +145,7 @@ impl Module<Button> for ClipboardModule {
self, self,
tx: mpsc::Sender<Self::ReceiveMessage>, tx: mpsc::Sender<Self::ReceiveMessage>,
rx: broadcast::Receiver<Self::SendMessage>, rx: broadcast::Receiver<Self::SendMessage>,
_context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
_info: &ModuleInfo, _info: &ModuleInfo,
) -> Option<gtk::Box> ) -> Option<gtk::Box>
where where

View file

@ -13,7 +13,7 @@ use crate::gtk_helpers::IronbarGtkExt;
use crate::modules::{ use crate::modules::{
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, PopupButton, WidgetContext, Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, PopupButton, WidgetContext,
}; };
use crate::{glib_recv, send_async, spawn, try_send}; use crate::{glib_recv, module_impl, send_async, spawn, try_send};
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
pub struct ClockModule { pub struct ClockModule {
@ -71,9 +71,7 @@ impl Module<Button> for ClockModule {
type SendMessage = DateTime<Local>; type SendMessage = DateTime<Local>;
type ReceiveMessage = (); type ReceiveMessage = ();
fn name() -> &'static str { module_impl!("clock");
"clock"
}
fn spawn_controller( fn spawn_controller(
&self, &self,
@ -120,7 +118,12 @@ impl Module<Button> for ClockModule {
}); });
let popup = self let popup = self
.into_popup(context.controller_tx.clone(), context.subscribe(), info) .into_popup(
context.controller_tx.clone(),
context.subscribe(),
context,
info,
)
.into_popup_parts(vec![&button]); .into_popup_parts(vec![&button]);
Ok(ModuleParts::new(button, popup)) Ok(ModuleParts::new(button, popup))
@ -130,6 +133,7 @@ impl Module<Button> for ClockModule {
self, self,
_tx: mpsc::Sender<Self::ReceiveMessage>, _tx: mpsc::Sender<Self::ReceiveMessage>,
rx: broadcast::Receiver<Self::SendMessage>, rx: broadcast::Receiver<Self::SendMessage>,
_context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
_info: &ModuleInfo, _info: &ModuleInfo,
) -> Option<gtk::Box> { ) -> Option<gtk::Box> {
let container = gtk::Box::new(Orientation::Vertical, 0); let container = gtk::Box::new(Orientation::Vertical, 0);

View file

@ -9,14 +9,15 @@ use self::image::ImageWidget;
use self::label::LabelWidget; use self::label::LabelWidget;
use self::r#box::BoxWidget; use self::r#box::BoxWidget;
use self::slider::SliderWidget; use self::slider::SliderWidget;
use crate::config::CommonConfig; use crate::config::{CommonConfig, ModuleConfig};
use crate::modules::custom::button::ButtonWidget; use crate::modules::custom::button::ButtonWidget;
use crate::modules::custom::progress::ProgressWidget; use crate::modules::custom::progress::ProgressWidget;
use crate::modules::{ use crate::modules::{
wrap_widget, Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, WidgetContext, wrap_widget, AnyModuleFactory, BarModuleFactory, Module, ModuleInfo, ModuleParts, ModulePopup,
ModuleUpdateEvent, PopupButton, PopupModuleFactory, WidgetContext,
}; };
use crate::script::Script; use crate::script::Script;
use crate::{send_async, spawn}; use crate::{module_impl, send_async, spawn};
use color_eyre::{Report, Result}; use color_eyre::{Report, Result};
use gtk::prelude::*; use gtk::prelude::*;
use gtk::{Button, IconTheme, Orientation}; use gtk::{Button, IconTheme, Orientation};
@ -40,11 +41,18 @@ pub struct CustomModule {
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
pub struct WidgetConfig { pub struct WidgetConfig {
#[serde(flatten)] #[serde(flatten)]
widget: Widget, widget: WidgetOrModule,
#[serde(flatten)] #[serde(flatten)]
common: CommonConfig, common: CommonConfig,
} }
#[derive(Debug, Deserialize, Clone)]
#[serde(untagged)]
pub enum WidgetOrModule {
Widget(Widget),
Module(ModuleConfig),
}
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
#[serde(tag = "type", rename_all = "snake_case")] #[serde(tag = "type", rename_all = "snake_case")]
pub enum Widget { pub enum Widget {
@ -58,10 +66,12 @@ pub enum Widget {
#[derive(Clone)] #[derive(Clone)]
struct CustomWidgetContext<'a> { struct CustomWidgetContext<'a> {
info: &'a ModuleInfo<'a>,
tx: &'a mpsc::Sender<ExecEvent>, tx: &'a mpsc::Sender<ExecEvent>,
bar_orientation: Orientation, bar_orientation: Orientation,
icon_theme: &'a IconTheme, icon_theme: &'a IconTheme,
popup_buttons: Rc<RefCell<Vec<Button>>>, popup_buttons: Rc<RefCell<Vec<Button>>>,
module_factory: AnyModuleFactory,
} }
trait CustomWidget { trait CustomWidget {
@ -114,6 +124,19 @@ fn try_get_orientation(orientation: &str) -> Result<Orientation> {
} }
} }
impl WidgetOrModule {
fn add_to(self, parent: &gtk::Box, context: &CustomWidgetContext, common: CommonConfig) {
match self {
WidgetOrModule::Widget(widget) => widget.add_to(parent, context, common),
WidgetOrModule::Module(config) => {
if let Err(err) = config.create(&context.module_factory, parent, context.info) {
error!("{err:?}");
}
}
}
}
}
impl Widget { impl Widget {
/// Creates this widget and adds it to the parent container /// 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) {
@ -151,9 +174,7 @@ impl Module<gtk::Box> for CustomModule {
type SendMessage = (); type SendMessage = ();
type ReceiveMessage = ExecEvent; type ReceiveMessage = ExecEvent;
fn name() -> &'static str { module_impl!("custom");
"custom"
}
fn spawn_controller( fn spawn_controller(
&self, &self,
@ -191,7 +212,7 @@ impl Module<gtk::Box> for CustomModule {
fn into_widget( fn into_widget(
self, self,
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>, mut context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
info: &ModuleInfo, info: &ModuleInfo,
) -> Result<ModuleParts<gtk::Box>> { ) -> Result<ModuleParts<gtk::Box>> {
let orientation = info.bar_position.orientation(); let orientation = info.bar_position.orientation();
@ -200,10 +221,13 @@ impl Module<gtk::Box> for CustomModule {
let popup_buttons = Rc::new(RefCell::new(Vec::new())); let popup_buttons = Rc::new(RefCell::new(Vec::new()));
let custom_context = CustomWidgetContext { let custom_context = CustomWidgetContext {
info,
tx: &context.controller_tx, tx: &context.controller_tx,
bar_orientation: orientation, bar_orientation: orientation,
icon_theme: info.icon_theme, icon_theme: info.icon_theme,
popup_buttons: popup_buttons.clone(), popup_buttons: popup_buttons.clone(),
module_factory: BarModuleFactory::new(context.ironbar.clone(), context.popup.clone())
.into(),
}; };
self.bar.clone().into_iter().for_each(|widget| { self.bar.clone().into_iter().for_each(|widget| {
@ -212,8 +236,22 @@ impl Module<gtk::Box> for CustomModule {
.add_to(&container, &custom_context, widget.common); .add_to(&container, &custom_context, widget.common);
}); });
for button in popup_buttons.borrow().iter() {
button.ensure_popup_id();
}
context.button_id = popup_buttons
.borrow()
.first()
.map_or(usize::MAX, |button| button.popup_id());
let popup = self let popup = self
.into_popup(context.controller_tx.clone(), context.subscribe(), info) .into_popup(
context.controller_tx.clone(),
context.subscribe(),
context,
info,
)
.into_popup_parts_owned(popup_buttons.take()); .into_popup_parts_owned(popup_buttons.take());
Ok(ModuleParts { Ok(ModuleParts {
@ -226,6 +264,7 @@ impl Module<gtk::Box> for CustomModule {
self, self,
tx: mpsc::Sender<Self::ReceiveMessage>, tx: mpsc::Sender<Self::ReceiveMessage>,
_rx: broadcast::Receiver<Self::SendMessage>, _rx: broadcast::Receiver<Self::SendMessage>,
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
info: &ModuleInfo, info: &ModuleInfo,
) -> Option<gtk::Box> ) -> Option<gtk::Box>
where where
@ -235,10 +274,17 @@ impl Module<gtk::Box> for CustomModule {
if let Some(popup) = self.popup { if let Some(popup) = self.popup {
let custom_context = CustomWidgetContext { let custom_context = CustomWidgetContext {
info,
tx: &tx, tx: &tx,
bar_orientation: info.bar_position.orientation(), bar_orientation: info.bar_position.orientation(),
icon_theme: info.icon_theme, icon_theme: info.icon_theme,
popup_buttons: Rc::new(RefCell::new(vec![])), popup_buttons: Rc::new(RefCell::new(vec![])),
module_factory: PopupModuleFactory::new(
context.ironbar,
context.popup,
context.button_id,
)
.into(),
}; };
for widget in popup { for widget in popup {

View file

@ -3,7 +3,7 @@ use crate::config::{CommonConfig, TruncateMode};
use crate::gtk_helpers::IronbarGtkExt; use crate::gtk_helpers::IronbarGtkExt;
use crate::image::ImageProvider; use crate::image::ImageProvider;
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext}; use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
use crate::{glib_recv, send_async, spawn, try_send}; use crate::{glib_recv, module_impl, send_async, spawn, try_send};
use color_eyre::Result; use color_eyre::Result;
use gtk::prelude::*; use gtk::prelude::*;
use gtk::Label; use gtk::Label;
@ -50,9 +50,7 @@ impl Module<gtk::Box> for FocusedModule {
type SendMessage = Option<(String, String)>; type SendMessage = Option<(String, String)>;
type ReceiveMessage = (); type ReceiveMessage = ();
fn name() -> &'static str { module_impl!("focused");
"focused"
}
fn spawn_controller( fn spawn_controller(
&self, &self,

View file

@ -1,7 +1,7 @@
use crate::config::CommonConfig; use crate::config::CommonConfig;
use crate::dynamic_value::dynamic_string; use crate::dynamic_value::dynamic_string;
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext}; use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
use crate::{glib_recv, try_send}; use crate::{glib_recv, module_impl, try_send};
use color_eyre::Result; use color_eyre::Result;
use gtk::prelude::*; use gtk::prelude::*;
use gtk::Label; use gtk::Label;
@ -29,9 +29,7 @@ impl Module<Label> for LabelModule {
type SendMessage = String; type SendMessage = String;
type ReceiveMessage = (); type ReceiveMessage = ();
fn name() -> &'static str { module_impl!("label");
"label"
}
fn spawn_controller( fn spawn_controller(
&self, &self,

View file

@ -7,7 +7,7 @@ use super::{Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, Wid
use crate::clients::wayland::{self, ToplevelEvent}; use crate::clients::wayland::{self, ToplevelEvent};
use crate::config::CommonConfig; use crate::config::CommonConfig;
use crate::desktop_file::find_desktop_file; use crate::desktop_file::find_desktop_file;
use crate::{arc_mut, glib_recv, lock, send_async, spawn, try_send, write_lock}; use crate::{arc_mut, glib_recv, lock, module_impl, send_async, spawn, try_send, write_lock};
use color_eyre::{Help, Report}; use color_eyre::{Help, Report};
use gtk::prelude::*; use gtk::prelude::*;
use gtk::{Button, Orientation}; use gtk::{Button, Orientation};
@ -80,9 +80,7 @@ impl Module<gtk::Box> for LauncherModule {
type SendMessage = LauncherUpdate; type SendMessage = LauncherUpdate;
type ReceiveMessage = ItemEvent; type ReceiveMessage = ItemEvent;
fn name() -> &'static str { module_impl!("launcher");
"launcher"
}
fn spawn_controller( fn spawn_controller(
&self, &self,
@ -401,7 +399,7 @@ impl Module<gtk::Box> for LauncherModule {
let rx = context.subscribe(); let rx = context.subscribe();
let popup = self let popup = self
.into_popup(context.controller_tx, rx, info) .into_popup(context.controller_tx.clone(), rx, context, info)
.into_popup_parts(vec![]); // since item buttons are dynamic, they pass their geometry directly .into_popup_parts(vec![]); // since item buttons are dynamic, they pass their geometry directly
Ok(ModuleParts { Ok(ModuleParts {
@ -414,6 +412,7 @@ impl Module<gtk::Box> for LauncherModule {
self, self,
controller_tx: mpsc::Sender<Self::ReceiveMessage>, controller_tx: mpsc::Sender<Self::ReceiveMessage>,
rx: broadcast::Receiver<Self::SendMessage>, rx: broadcast::Receiver<Self::SendMessage>,
_context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
_info: &ModuleInfo, _info: &ModuleInfo,
) -> Option<gtk::Box> { ) -> Option<gtk::Box> {
const MAX_WIDTH: i32 = 250; const MAX_WIDTH: i32 = 250;

View file

@ -54,6 +54,8 @@ pub enum ModuleLocation {
Center, Center,
Right, Right,
} }
#[derive(Clone)]
pub struct ModuleInfo<'a> { pub struct ModuleInfo<'a> {
pub app: &'a Application, pub app: &'a Application,
pub location: ModuleLocation, pub location: ModuleLocation,
@ -85,10 +87,16 @@ where
{ {
pub id: usize, pub id: usize,
pub ironbar: Rc<Ironbar>, pub ironbar: Rc<Ironbar>,
pub popup: Rc<Popup>,
pub tx: mpsc::Sender<ModuleUpdateEvent<TSend>>, pub tx: mpsc::Sender<ModuleUpdateEvent<TSend>>,
pub update_tx: broadcast::Sender<TSend>, pub update_tx: broadcast::Sender<TSend>,
pub controller_tx: mpsc::Sender<TReceive>, pub controller_tx: mpsc::Sender<TReceive>,
// TODO: Don't like this - need some serious refactoring to deal with it
// This is a hack to be able to pass data from module -> popup creation
// for custom widget only.
pub button_id: usize,
_update_rx: broadcast::Receiver<TSend>, _update_rx: broadcast::Receiver<TSend>,
} }
@ -122,6 +130,32 @@ impl<W: IsA<Widget>> ModuleParts<W> {
fn new(widget: W, popup: Option<ModulePopupParts>) -> Self { fn new(widget: W, popup: Option<ModulePopupParts>) -> Self {
Self { widget, popup } Self { widget, popup }
} }
pub fn setup_identifiers(&self, common: &CommonConfig) {
if let Some(ref name) = common.name {
self.widget.set_widget_name(name);
if let Some(ref popup) = self.popup {
popup.container.set_widget_name(&format!("popup-{name}"));
}
}
if let Some(ref class) = common.class {
// gtk counts classes with spaces as the same class
for part in class.split(' ') {
self.widget.style_context().add_class(part);
}
if let Some(ref popup) = self.popup {
for part in class.split(' ') {
popup
.container
.style_context()
.add_class(&format!("popup-{part}"));
}
}
}
}
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -150,11 +184,24 @@ impl ModulePopup for Option<gtk::Box> {
} }
pub trait PopupButton { pub trait PopupButton {
fn ensure_popup_id(&self) -> usize;
fn try_popup_id(&self) -> Option<usize>; fn try_popup_id(&self) -> Option<usize>;
fn popup_id(&self) -> usize; fn popup_id(&self) -> usize;
} }
impl PopupButton for Button { impl PopupButton for Button {
/// Gets the popup ID associated with this button,
/// or creates a new one if it does not exist.
fn ensure_popup_id(&self) -> usize {
if let Some(id) = self.try_popup_id() {
id
} else {
let id = Ironbar::unique_id();
self.set_tag("popup-id", id);
id
}
}
/// Gets the popup ID associated with this button, if there is one. /// Gets the popup ID associated with this button, if there is one.
/// Will return `None` if this is not a popup button. /// Will return `None` if this is not a popup button.
fn try_popup_id(&self) -> Option<usize> { fn try_popup_id(&self) -> Option<usize> {
@ -201,48 +248,57 @@ where
self, self,
_tx: mpsc::Sender<Self::ReceiveMessage>, _tx: mpsc::Sender<Self::ReceiveMessage>,
_rx: broadcast::Receiver<Self::SendMessage>, _rx: broadcast::Receiver<Self::SendMessage>,
_context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
_info: &ModuleInfo, _info: &ModuleInfo,
) -> Option<gtk::Box> ) -> Option<gtk::Box>
where where
Self: Sized, Self: Sized,
<Self as Module<W>>::SendMessage: Clone,
{ {
None None
} }
fn take_common(&mut self) -> CommonConfig;
} }
/// Creates a module and sets it up. pub trait ModuleFactory {
/// This setup includes widget/popup content and event channels. fn create<TModule, TWidget, TSend, TRev>(
pub fn create_module<TModule, TWidget, TSend, TRec>( &self,
module: TModule, mut module: TModule,
id: usize, container: &gtk::Box,
ironbar: Rc<Ironbar>,
name: Option<String>,
info: &ModuleInfo, info: &ModuleInfo,
popup: &Rc<Popup>, ) -> Result<()>
) -> Result<ModuleParts<TWidget>> where
where TModule: Module<TWidget, SendMessage = TSend, ReceiveMessage = TRev>,
TModule: Module<TWidget, SendMessage = TSend, ReceiveMessage = TRec>,
TWidget: IsA<Widget>, TWidget: IsA<Widget>,
TSend: Debug + Clone + Send + 'static, TSend: Debug + Clone + Send + 'static,
{ {
let id = Ironbar::unique_id();
let common = module.take_common();
let (ui_tx, ui_rx) = mpsc::channel::<ModuleUpdateEvent<TSend>>(64); let (ui_tx, ui_rx) = mpsc::channel::<ModuleUpdateEvent<TSend>>(64);
let (controller_tx, controller_rx) = mpsc::channel::<TRec>(64); let (controller_tx, controller_rx) = mpsc::channel::<TRev>(64);
let (tx, rx) = broadcast::channel(64); let (tx, rx) = broadcast::channel(64);
let context = WidgetContext { let context = WidgetContext {
id, id,
ironbar, ironbar: self.ironbar().clone(),
popup: self.popup().clone(),
tx: ui_tx, tx: ui_tx,
update_tx: tx.clone(), update_tx: tx.clone(),
controller_tx, controller_tx,
_update_rx: rx, _update_rx: rx,
button_id: usize::MAX, // hack :(
}; };
module.spawn_controller(info, &context, controller_rx)?; module.spawn_controller(info, &context, controller_rx)?;
let module_name = TModule::name(); let module_name = TModule::name();
let instance_name = name.unwrap_or_else(|| module_name.to_string()); let instance_name = common
.name
.clone()
.unwrap_or_else(|| module_name.to_string());
let module_parts = module.into_widget(context, info)?; let module_parts = module.into_widget(context, info)?;
module_parts.widget.add_class("widget"); module_parts.widget.add_class("widget");
@ -254,61 +310,77 @@ where
.style_context() .style_context()
.add_class(&format!("popup-{module_name}")); .add_class(&format!("popup-{module_name}"));
popup.register_content(id, instance_name, popup_content); self.popup()
.register_content(id, instance_name, popup_content);
} }
setup_receiver(tx, ui_rx, popup.clone(), module_name, id); self.setup_receiver(tx, ui_rx, module_name, id);
Ok(module_parts) module_parts.setup_identifiers(&common);
}
/// Sets up the bridge channel receiver let ev_container = wrap_widget(
/// to pick up events from the controller, widget or popup. &module_parts.widget,
/// common,
/// Handles opening/closing popups info.bar_position.orientation(),
/// and communicating update messages between controllers and widgets/popups. );
fn setup_receiver<TSend>( container.add(&ev_container);
Ok(())
}
fn setup_receiver<TSend>(
&self,
tx: broadcast::Sender<TSend>, tx: broadcast::Sender<TSend>,
rx: mpsc::Receiver<ModuleUpdateEvent<TSend>>, rx: mpsc::Receiver<ModuleUpdateEvent<TSend>>,
popup: Rc<Popup>,
name: &'static str, name: &'static str,
id: usize, id: usize,
) where ) where
TSend: Debug + Clone + Send + 'static, TSend: Debug + Clone + Send + 'static;
{
// some rare cases can cause the popup to incorrectly calculate its size on first open.
// we can fix that by just force re-rendering it on its first open.
let mut has_popup_opened = false;
fn ironbar(&self) -> &Rc<Ironbar>;
fn popup(&self) -> &Rc<Popup>;
}
#[derive(Clone)]
pub struct BarModuleFactory {
ironbar: Rc<Ironbar>,
popup: Rc<Popup>,
}
impl BarModuleFactory {
pub fn new(ironbar: Rc<Ironbar>, popup: Rc<Popup>) -> Self {
Self { ironbar, popup }
}
}
impl ModuleFactory for BarModuleFactory {
fn setup_receiver<TSend>(
&self,
tx: broadcast::Sender<TSend>,
rx: mpsc::Receiver<ModuleUpdateEvent<TSend>>,
name: &'static str,
id: usize,
) where
TSend: Debug + Clone + Send + 'static,
{
let popup = self.popup.clone();
glib_recv_mpsc!(rx, ev => { glib_recv_mpsc!(rx, ev => {
match ev { match ev {
ModuleUpdateEvent::Update(update) => { ModuleUpdateEvent::Update(update) => {
send!(tx, update); send!(tx, update);
} }
ModuleUpdateEvent::TogglePopup(button_id) => { ModuleUpdateEvent::TogglePopup(button_id) => {
debug!("Toggling popup for {} [#{}]", name, id); debug!("Toggling popup for {} [#{}] (button id: {button_id})", name, id);
if popup.is_visible() { if popup.is_visible() && popup.current_widget().unwrap_or_default() == id {
popup.hide(); popup.hide();
} else { } else {
popup.show(id, button_id); popup.show(id, button_id);
// force re-render on initial open to try and fix size issue
if !has_popup_opened {
popup.show(id, button_id);
has_popup_opened = true;
}
} }
} }
ModuleUpdateEvent::OpenPopup(button_id) => { ModuleUpdateEvent::OpenPopup(button_id) => {
debug!("Opening popup for {} [#{}]", name, id); debug!("Opening popup for {} [#{}] (button id: {button_id})", name, id);
popup.hide(); popup.hide();
popup.show(id, button_id); popup.show(id, button_id);
// force re-render on initial open to try and fix size issue
if !has_popup_opened {
popup.show(id, button_id);
has_popup_opened = true;
}
} }
#[cfg(feature = "launcher")] #[cfg(feature = "launcher")]
ModuleUpdateEvent::OpenPopupAt(geometry) => { ModuleUpdateEvent::OpenPopupAt(geometry) => {
@ -316,12 +388,6 @@ fn setup_receiver<TSend>(
popup.hide(); popup.hide();
popup.show_at(id, geometry); 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 => { ModuleUpdateEvent::ClosePopup => {
debug!("Closing popup for {} [#{}]", name, id); debug!("Closing popup for {} [#{}]", name, id);
@ -329,34 +395,134 @@ fn setup_receiver<TSend>(
} }
} }
}); });
}
fn ironbar(&self) -> &Rc<Ironbar> {
&self.ironbar
}
fn popup(&self) -> &Rc<Popup> {
&self.popup
}
} }
pub fn set_widget_identifiers<TWidget: IsA<Widget>>( #[derive(Clone)]
widget_parts: &ModuleParts<TWidget>, pub struct PopupModuleFactory {
common: &CommonConfig, ironbar: Rc<Ironbar>,
) { popup: Rc<Popup>,
if let Some(ref name) = common.name { button_id: usize,
widget_parts.widget.set_widget_name(name); }
if let Some(ref popup) = widget_parts.popup { impl PopupModuleFactory {
popup.container.set_widget_name(&format!("popup-{name}")); pub fn new(ironbar: Rc<Ironbar>, popup: Rc<Popup>, button_id: usize) -> Self {
Self {
ironbar,
popup,
button_id,
}
}
}
impl ModuleFactory for PopupModuleFactory {
fn setup_receiver<TSend>(
&self,
tx: broadcast::Sender<TSend>,
rx: mpsc::Receiver<ModuleUpdateEvent<TSend>>,
name: &'static str,
id: usize,
) where
TSend: Debug + Clone + Send + 'static,
{
let popup = self.popup.clone();
let button_id = self.button_id;
glib_recv_mpsc!(rx, ev => {
match ev {
ModuleUpdateEvent::Update(update) => {
send!(tx, update);
}
ModuleUpdateEvent::TogglePopup(_) => {
debug!("Toggling popup for {} [#{}] (button id: {button_id})", name, id);
if popup.is_visible() && popup.current_widget().unwrap_or_default() == id {
popup.hide();
} else {
popup.show(id, button_id);
}
}
ModuleUpdateEvent::OpenPopup(_) => {
debug!("Opening popup for {} [#{}] (button id: {button_id})", name, id);
popup.hide();
popup.show(id, button_id);
}
#[cfg(feature = "launcher")]
ModuleUpdateEvent::OpenPopupAt(geometry) => {
debug!("Opening popup for {} [#{}]", name, id);
popup.hide();
popup.show_at(id, geometry);
}
ModuleUpdateEvent::ClosePopup => {
debug!("Closing popup for {} [#{}]", name, id);
popup.hide();
}
}
});
}
fn ironbar(&self) -> &Rc<Ironbar> {
&self.ironbar
}
fn popup(&self) -> &Rc<Popup> {
&self.popup
}
}
#[derive(Clone)]
pub enum AnyModuleFactory {
Bar(BarModuleFactory),
Popup(PopupModuleFactory),
}
impl ModuleFactory for AnyModuleFactory {
fn setup_receiver<TSend>(
&self,
tx: broadcast::Sender<TSend>,
rx: mpsc::Receiver<ModuleUpdateEvent<TSend>>,
name: &'static str,
id: usize,
) where
TSend: Debug + Clone + Send + 'static,
{
match self {
AnyModuleFactory::Bar(bar) => bar.setup_receiver(tx, rx, name, id),
AnyModuleFactory::Popup(popup) => popup.setup_receiver(tx, rx, name, id),
} }
} }
if let Some(ref class) = common.class { fn ironbar(&self) -> &Rc<Ironbar> {
// gtk counts classes with spaces as the same class match self {
for part in class.split(' ') { AnyModuleFactory::Bar(bar) => bar.ironbar(),
widget_parts.widget.style_context().add_class(part); AnyModuleFactory::Popup(popup) => popup.ironbar(),
}
} }
if let Some(ref popup) = widget_parts.popup { fn popup(&self) -> &Rc<Popup> {
for part in class.split(' ') { match self {
popup AnyModuleFactory::Bar(bar) => bar.popup(),
.container AnyModuleFactory::Popup(popup) => popup.popup(),
.style_context()
.add_class(&format!("popup-{part}"));
} }
} }
}
impl From<BarModuleFactory> for AnyModuleFactory {
fn from(value: BarModuleFactory) -> Self {
Self::Bar(value)
}
}
impl From<PopupModuleFactory> for AnyModuleFactory {
fn from(value: PopupModuleFactory) -> Self {
Self::Popup(value)
} }
} }

View file

@ -22,7 +22,7 @@ use crate::modules::PopupButton;
use crate::modules::{ use crate::modules::{
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, WidgetContext, Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, WidgetContext,
}; };
use crate::{glib_recv, send_async, spawn, try_send}; use crate::{glib_recv, module_impl, send_async, spawn, try_send};
pub use self::config::MusicModule; pub use self::config::MusicModule;
use self::config::PlayerType; use self::config::PlayerType;
@ -87,9 +87,7 @@ impl Module<Button> for MusicModule {
type SendMessage = ControllerEvent; type SendMessage = ControllerEvent;
type ReceiveMessage = PlayerCommand; type ReceiveMessage = PlayerCommand;
fn name() -> &'static str { module_impl!("music");
"music"
}
fn spawn_controller( fn spawn_controller(
&self, &self,
@ -255,7 +253,7 @@ impl Module<Button> for MusicModule {
let rx = context.subscribe(); let rx = context.subscribe();
let popup = self let popup = self
.into_popup(context.controller_tx, rx, info) .into_popup(context.controller_tx.clone(), rx, context, info)
.into_popup_parts(vec![&button]); .into_popup_parts(vec![&button]);
Ok(ModuleParts::new(button, popup)) Ok(ModuleParts::new(button, popup))
@ -265,6 +263,7 @@ impl Module<Button> for MusicModule {
self, self,
tx: mpsc::Sender<Self::ReceiveMessage>, tx: mpsc::Sender<Self::ReceiveMessage>,
rx: broadcast::Receiver<Self::SendMessage>, rx: broadcast::Receiver<Self::SendMessage>,
_context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
info: &ModuleInfo, info: &ModuleInfo,
) -> Option<gtk::Box> { ) -> Option<gtk::Box> {
let icon_theme = info.icon_theme; let icon_theme = info.icon_theme;

View file

@ -2,7 +2,7 @@ use crate::clients::swaync;
use crate::config::CommonConfig; use crate::config::CommonConfig;
use crate::gtk_helpers::IronbarGtkExt; use crate::gtk_helpers::IronbarGtkExt;
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext}; use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
use crate::{glib_recv, send_async, spawn, try_send}; use crate::{glib_recv, module_impl, send_async, spawn, try_send};
use gtk::prelude::*; use gtk::prelude::*;
use gtk::{Align, Button, Label, Overlay}; use gtk::{Align, Button, Label, Overlay};
use serde::Deserialize; use serde::Deserialize;
@ -97,9 +97,7 @@ impl Module<Overlay> for NotificationsModule {
type SendMessage = swaync::Event; type SendMessage = swaync::Event;
type ReceiveMessage = UiEvent; type ReceiveMessage = UiEvent;
fn name() -> &'static str { module_impl!("notifications");
"notifications"
}
fn spawn_controller( fn spawn_controller(
&self, &self,

View file

@ -1,7 +1,7 @@
use crate::config::CommonConfig; use crate::config::CommonConfig;
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext}; use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
use crate::script::{OutputStream, Script, ScriptMode}; use crate::script::{OutputStream, Script, ScriptMode};
use crate::{glib_recv, spawn, try_send}; use crate::{glib_recv, module_impl, spawn, try_send};
use color_eyre::{Help, Report, Result}; use color_eyre::{Help, Report, Result};
use gtk::prelude::*; use gtk::prelude::*;
use gtk::Label; use gtk::Label;
@ -48,9 +48,7 @@ impl Module<Label> for ScriptModule {
type SendMessage = String; type SendMessage = String;
type ReceiveMessage = (); type ReceiveMessage = ();
fn name() -> &'static str { module_impl!("script");
"script"
}
fn spawn_controller( fn spawn_controller(
&self, &self,

View file

@ -1,7 +1,7 @@
use crate::config::CommonConfig; use crate::config::CommonConfig;
use crate::gtk_helpers::IronbarGtkExt; use crate::gtk_helpers::IronbarGtkExt;
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext}; use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
use crate::{glib_recv, send_async, spawn}; use crate::{glib_recv, module_impl, send_async, spawn};
use color_eyre::Result; use color_eyre::Result;
use gtk::prelude::*; use gtk::prelude::*;
use gtk::Label; use gtk::Label;
@ -116,9 +116,7 @@ impl Module<gtk::Box> for SysInfoModule {
type SendMessage = HashMap<String, String>; type SendMessage = HashMap<String, String>;
type ReceiveMessage = (); type ReceiveMessage = ();
fn name() -> &'static str { module_impl!("sysinfo");
"sysinfo"
}
fn spawn_controller( fn spawn_controller(
&self, &self,

View file

@ -6,7 +6,7 @@ use crate::clients::tray;
use crate::config::CommonConfig; use crate::config::CommonConfig;
use crate::modules::tray::diff::get_diffs; use crate::modules::tray::diff::get_diffs;
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext}; use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
use crate::{glib_recv, lock, send_async, spawn}; use crate::{glib_recv, lock, module_impl, send_async, spawn};
use color_eyre::{Report, Result}; use color_eyre::{Report, Result};
use gtk::{prelude::*, PackDirection}; use gtk::{prelude::*, PackDirection};
use gtk::{IconTheme, MenuBar}; use gtk::{IconTheme, MenuBar};
@ -57,9 +57,7 @@ impl Module<MenuBar> for TrayModule {
type SendMessage = Event; type SendMessage = Event;
type ReceiveMessage = ActivateRequest; type ReceiveMessage = ActivateRequest;
fn name() -> &'static str { module_impl!("tray");
"tray"
}
fn spawn_controller( fn spawn_controller(
&self, &self,

View file

@ -15,7 +15,7 @@ use crate::modules::PopupButton;
use crate::modules::{ use crate::modules::{
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, WidgetContext, Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, WidgetContext,
}; };
use crate::{glib_recv, send_async, spawn, try_send}; use crate::{glib_recv, module_impl, send_async, spawn, try_send};
const DAY: i64 = 24 * 60 * 60; const DAY: i64 = 24 * 60 * 60;
const HOUR: i64 = 60 * 60; const HOUR: i64 = 60 * 60;
@ -54,9 +54,7 @@ impl Module<gtk::Button> for UpowerModule {
type SendMessage = UpowerProperties; type SendMessage = UpowerProperties;
type ReceiveMessage = (); type ReceiveMessage = ();
fn name() -> &'static str { module_impl!("upower");
"upower"
}
fn spawn_controller( fn spawn_controller(
&self, &self,
@ -211,7 +209,7 @@ impl Module<gtk::Button> for UpowerModule {
let rx = context.subscribe(); let rx = context.subscribe();
let popup = self let popup = self
.into_popup(context.controller_tx, rx, info) .into_popup(context.controller_tx.clone(), rx, context, info)
.into_popup_parts(vec![&button]); .into_popup_parts(vec![&button]);
Ok(ModuleParts::new(button, popup)) Ok(ModuleParts::new(button, popup))
@ -221,6 +219,7 @@ impl Module<gtk::Button> for UpowerModule {
self, self,
_tx: mpsc::Sender<Self::ReceiveMessage>, _tx: mpsc::Sender<Self::ReceiveMessage>,
rx: broadcast::Receiver<Self::SendMessage>, rx: broadcast::Receiver<Self::SendMessage>,
_context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
_info: &ModuleInfo, _info: &ModuleInfo,
) -> Option<gtk::Box> ) -> Option<gtk::Box>
where where

View file

@ -4,7 +4,7 @@ use crate::gtk_helpers::IronbarGtkExt;
use crate::modules::{ use crate::modules::{
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, PopupButton, WidgetContext, Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, PopupButton, WidgetContext,
}; };
use crate::{glib_recv, lock, send_async, spawn, try_send}; 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::*;
@ -99,9 +99,7 @@ impl Module<Button> for VolumeModule {
type SendMessage = Event; type SendMessage = Event;
type ReceiveMessage = Update; type ReceiveMessage = Update;
fn name() -> &'static str { module_impl!("volume");
"volume"
}
fn spawn_controller( fn spawn_controller(
&self, &self,
@ -208,7 +206,12 @@ impl Module<Button> for VolumeModule {
} }
let popup = self let popup = self
.into_popup(context.controller_tx.clone(), context.subscribe(), info) .into_popup(
context.controller_tx.clone(),
context.subscribe(),
context,
info,
)
.into_popup_parts(vec![&button]); .into_popup_parts(vec![&button]);
Ok(ModuleParts::new(button, popup)) Ok(ModuleParts::new(button, popup))
@ -218,6 +221,7 @@ impl Module<Button> for VolumeModule {
self, self,
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>,
_info: &ModuleInfo, _info: &ModuleInfo,
) -> Option<gtk::Box> ) -> Option<gtk::Box>
where where

View file

@ -2,7 +2,7 @@ use crate::clients::compositor::{Visibility, Workspace, WorkspaceClient, Workspa
use crate::config::CommonConfig; use crate::config::CommonConfig;
use crate::image::new_icon_button; use crate::image::new_icon_button;
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext}; use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
use crate::{glib_recv, send_async, spawn, try_send}; use crate::{glib_recv, module_impl, send_async, spawn, try_send};
use color_eyre::{Report, Result}; use color_eyre::{Report, Result};
use gtk::prelude::*; use gtk::prelude::*;
use gtk::{Button, IconTheme}; use gtk::{Button, IconTheme};
@ -144,9 +144,7 @@ impl Module<gtk::Box> for WorkspacesModule {
type SendMessage = WorkspaceUpdate; type SendMessage = WorkspaceUpdate;
type ReceiveMessage = String; type ReceiveMessage = String;
fn name() -> &'static str { module_impl!("workspaces");
"workspaces"
}
fn spawn_controller( fn spawn_controller(
&self, &self,

View file

@ -12,7 +12,7 @@ use tracing::{debug, trace};
use crate::config::BarPosition; use crate::config::BarPosition;
use crate::gtk_helpers::{IronbarGtkExt, WidgetGeometry}; use crate::gtk_helpers::{IronbarGtkExt, WidgetGeometry};
use crate::modules::{ModuleInfo, ModulePopupParts, PopupButton}; use crate::modules::{ModuleInfo, ModulePopupParts, PopupButton};
use crate::Ironbar; use crate::rc_mut;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct PopupCacheValue { pub struct PopupCacheValue {
@ -23,7 +23,8 @@ pub struct PopupCacheValue {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Popup { pub struct Popup {
pub window: ApplicationWindow, pub window: ApplicationWindow,
pub cache: Rc<RefCell<HashMap<usize, PopupCacheValue>>>, pub container_cache: Rc<RefCell<HashMap<usize, PopupCacheValue>>>,
pub button_cache: Rc<RefCell<Vec<Button>>>,
monitor: Monitor, monitor: Monitor,
pos: BarPosition, pos: BarPosition,
current_widget: Rc<RefCell<Option<(usize, usize)>>>, current_widget: Rc<RefCell<Option<(usize, usize)>>>,
@ -106,10 +107,11 @@ impl Popup {
Self { Self {
window: win, window: win,
cache: Rc::new(RefCell::new(HashMap::new())), container_cache: rc_mut!(HashMap::new()),
button_cache: rc_mut!(vec![]),
monitor: module_info.monitor.clone(), monitor: module_info.monitor.clone(),
pos, pos,
current_widget: Rc::new(RefCell::new(None)), current_widget: rc_mut!(None),
} }
} }
@ -117,8 +119,7 @@ impl Popup {
debug!("Registered popup content for #{}", key); debug!("Registered popup content for #{}", key);
for button in &content.buttons { for button in &content.buttons {
let id = Ironbar::unique_id(); button.ensure_popup_id();
button.set_tag("popup-id", id);
} }
let orientation = self.pos.orientation(); let orientation = self.pos.orientation();
@ -126,7 +127,8 @@ impl Popup {
let window = self.window.clone(); let window = self.window.clone();
let current_widget = self.current_widget.clone(); let current_widget = self.current_widget.clone();
let cache = self.cache.clone(); let cache = self.container_cache.clone();
let button_cache = self.button_cache.clone();
content content
.container .container
@ -135,11 +137,9 @@ impl Popup {
trace!("Resized: {}x{}", rect.width(), rect.height()); trace!("Resized: {}x{}", rect.width(), rect.height());
if let Some((widget_id, button_id)) = *current_widget.borrow() { if let Some((widget_id, button_id)) = *current_widget.borrow() {
if let Some(PopupCacheValue { content, .. }) = if let Some(PopupCacheValue { .. }) = cache.borrow().get(&widget_id) {
cache.borrow().get(&widget_id)
{
Self::set_position( Self::set_position(
&content.buttons, &button_cache.borrow(),
button_id, button_id,
orientation, orientation,
&monitor, &monitor,
@ -150,7 +150,11 @@ impl Popup {
} }
}); });
self.cache self.button_cache
.borrow_mut()
.append(&mut content.buttons.clone());
self.container_cache
.borrow_mut() .borrow_mut()
.insert(key, PopupCacheValue { name, content }); .insert(key, PopupCacheValue { name, content });
} }
@ -158,16 +162,17 @@ impl Popup {
pub fn show(&self, widget_id: usize, button_id: usize) { pub fn show(&self, widget_id: usize, button_id: usize) {
self.clear_window(); self.clear_window();
if let Some(PopupCacheValue { content, .. }) = self.cache.borrow().get(&widget_id) { if let Some(PopupCacheValue { content, .. }) = self.container_cache.borrow().get(&widget_id)
{
*self.current_widget.borrow_mut() = Some((widget_id, button_id)); *self.current_widget.borrow_mut() = Some((widget_id, button_id));
content.container.style_context().add_class("popup"); content.container.add_class("popup");
self.window.add(&content.container); self.window.add(&content.container);
self.window.show(); self.window.show();
Self::set_position( Self::set_position(
&content.buttons, &self.button_cache.borrow(),
button_id, button_id,
self.pos.orientation(), self.pos.orientation(),
&self.monitor, &self.monitor,
@ -179,8 +184,9 @@ impl Popup {
pub fn show_at(&self, widget_id: usize, geometry: WidgetGeometry) { pub fn show_at(&self, widget_id: usize, geometry: WidgetGeometry) {
self.clear_window(); self.clear_window();
if let Some(PopupCacheValue { content, .. }) = self.cache.borrow().get(&widget_id) { if let Some(PopupCacheValue { content, .. }) = self.container_cache.borrow().get(&widget_id)
content.container.style_context().add_class("popup"); {
content.container.add_class("popup");
self.window.add(&content.container); self.window.add(&content.container);
self.window.show(); self.window.show();