feat: module-level name and class options

BREAKING CHANGE: To allow for the `name` property, any widgets that were previously targeted by name should be targeted by class instead. This affects **all modules and all popups**, as well as several widgets inside modules. **This will break a lot of rules in your stylesheet**. To attempt to mitigate the damage, a migration script can be found [here](https://raw.githubusercontent.com/JakeStanger/ironbar/master/scripts/migrate-styles.sh) that should get you most of the way.

Resolves #75.
This commit is contained in:
Jake Stanger 2023-05-06 00:40:06 +01:00
parent 528a8d6dd6
commit dea66415c2
No known key found for this signature in database
GPG Key ID: C51FC8F9CB0BEA61
30 changed files with 352 additions and 215 deletions

View File

@ -310,9 +310,12 @@ For information on the `Script` type, and embedding scripts in strings, see [her
| `transition_type` | `slide_start` or `slide_end` or `crossfade` or `none` | `slide_start` | The transition animation to use when showing/hiding the widget. | | `transition_type` | `slide_start` or `slide_end` or `crossfade` or `none` | `slide_start` | The transition animation to use when showing/hiding the widget. |
| `transition_duration` | `Integer` | `250` | The length of the transition animation to use when showing/hiding the widget. | | `transition_duration` | `Integer` | `250` | The length of the transition animation to use when showing/hiding the widget. |
#### Other #### Appearance
| Name | Type | Default | Description | | Name | Type | Default | Description |
|-------------------|--------------------|---------|--------------------------------------------------------------------------------------------------------------------| |-----------|--------------------|---------|-----------------------------------------------------------------------------------|
| `tooltip` | `string` | `null` | Shows this text on hover. Supports embedding scripts between `{{double braces}}`. | | `tooltip` | `string` | `null` | Shows this text on hover. Supports embedding scripts between `{{double braces}}`. |
| `name` | `string` | `null` | Sets the unique widget name, allowing you to style it using `#name`. |
| `class` | `string` | `null` | Sets one or more CSS classes, allowing you to style it using `.class`. |
For more information on styling, please see the [styling guide](styling-guide).

View File

@ -4,17 +4,37 @@ To style the bar, create a file at `~/.config/ironbar/style.css`.
Style changes are hot-loaded so there is no need to reload the bar. Style changes are hot-loaded so there is no need to reload the bar.
A reminder: since the bar is GTK-based, it uses GTK's implementation of CSS, Since the bar is GTK-based, it uses [GTK's implementation of CSS](https://docs.gtk.org/gtk3/css-overview.html),
which only includes a subset of the full web spec (plus a few non-standard properties). which only includes a subset of the full web spec (plus a few non-standard properties).
The below table describes the selectors provided by the bar itself. The below table describes the selectors provided by the bar itself.
Information on styling individual modules can be found on their pages in the sidebar. Information on styling individual modules can be found on their pages in the sidebar.
| Selector | Description | | Selector | Description |
|----------------|-------------------------------------------| |----------------|--------------------------------------------|
| `.background` | Top-level window | | `.background` | Top-level window. |
| `#bar` | Bar root box | | `#bar` | Bar root box. |
| `#bar #start` | Bar left or top modules container box | | `#bar #start` | Bar left or top modules container box. |
| `#bar #center` | Bar center modules container box | | `#bar #center` | Bar center modules container box. |
| `#bar #end` | Bar right or bottom modules container box | | `#bar #end` | Bar right or bottom modules container box. |
| `.container` | All of the above | | `.container` | All of the above. |
| `.popup` | Any popup box. |
Every widget can be selected using a `kebab-case` class name matching its name.
You can also target popups by prefixing `popup-` to the name. For example, you can use `.clock` and `.popup-clock` respectively.
Setting the `name` option on a widget allows you to target that specific instance using `#name`.
You can also add additional classes to re-use styles. In both cases, `popup-` is automatically prefixed to the popup (`#popup-name` or `.popup-my-class`).
You can also target all GTK widgets of a certain type directly using their name. For example, `button:hover` will select the hover state on *all* buttons.
These names are all lower case with no separator, so `MenuBar` -> `menubar`.
GTK CSS does not support custom properties, but it does have its own custom `@define-color` syntax which you can use for re-using colours:
```css
@define-color color_bg #2d2d2d;
box, menubar {
background-color: @color_bg;
}
```

View File

@ -84,11 +84,13 @@ end:
| Selector | Description | | Selector | Description |
|--------------------------------------|------------------------------------------------------| |--------------------------------------|------------------------------------------------------|
| `#clipboard` | Clipboard widget. | | `.clipboard` | Clipboard widget. |
| `#clipboard .btn` | Clipboard widget button. | | `.clipboard .btn` | Clipboard widget button. |
| `#popup-clipboard` | Clipboard popup box. | | `.popup-clipboard` | Clipboard popup box. |
| `#popup-clipboard .item` | Clipboard row item inside the popup. | | `.popup-clipboard .item` | Clipboard row item inside the popup. |
| `#popup-clipboard .item .btn` | Clipboard row item radio button. | | `.popup-clipboard .item .btn` | Clipboard row item radio button. |
| `#popup-clipboard .item .btn.text` | Clipboard row item radio button (text values only). | | `.popup-clipboard .item .btn.text` | Clipboard row item radio button (text values only). |
| `#popup-clipboard .item .btn.image` | Clipboard row item radio button (image values only). | | `.popup-clipboard .item .btn.image` | Clipboard row item radio button (image values only). |
| `#popup-clipboard .item .btn-remove` | Clipboard row item remove button. | | `.popup-clipboard .item .btn-remove` | Clipboard row item remove button. |
For more information on styling, please see the [styling guide](styling-guide).

View File

@ -71,7 +71,9 @@ end:
| Selector | Description | | Selector | Description |
|--------------------------------|------------------------------------------------------------------------------------| |--------------------------------|------------------------------------------------------------------------------------|
| `#clock` | Clock widget button | | `.clock` | Clock widget button |
| `#popup-clock` | Clock popup box | | `.popup-clock` | Clock popup box |
| `#popup-clock #calendar-clock` | Clock inside the popup | | `.popup-clock .calendar-clock` | Clock inside the popup |
| `#popup-clock #calendar` | Calendar widget inside the popup. GTK provides some OOTB styling options for this. | | `.popup-clock .calendar` | Calendar widget inside the popup. GTK provides some OOTB styling options for this. |
For more information on styling, please see the [styling guide](styling-guide).

View File

@ -392,10 +392,13 @@ let {
## Styling ## Styling
Since the widgets are all custom, you can use the `name` and `class` attributes, then target them using `#name` and `.class`. Since the widgets are all custom, you can use their `name` and `class` attributes, then target them using `#name` and `.class`.
The following top-level selector is always available: The following top-level selectors are always available:
| Selector | Description | | Selector | Description |
|-----------|-------------------------| |-----------------|--------------------------------|
| `#custom` | Custom widget container | | `.custom` | Custom widget container. |
| `.popup-custom` | Custom widget popup container. |
For more information on styling, please see the [styling guide](styling-guide).

View File

@ -87,7 +87,9 @@ end:
## Styling ## Styling
| Selector | Description | | Selector | Description |
|--------------------------|--------------------| |-------------------|--------------------|
| `#focused` | Focused widget box | | `.focused` | Focused widget box |
| `#focused #icon` | App icon | | `.focused .icon` | App icon |
| `#focused #label` | App name | | `.focused .label` | App name |
For more information on styling, please see the [styling guide](styling-guide).

View File

@ -66,5 +66,7 @@ end:
## Styling ## Styling
| Selector | Description | | Selector | Description |
|--------------------------------|------------------------------------------------------------------------------------| |----------|------------------------------------------------------------------------------------|
| `#label` | Label widget | | `.label` | Label widget |
For more information on styling, please see the [styling guide](styling-guide).

View File

@ -90,10 +90,12 @@ start:
| Selector | Description | | Selector | Description |
|-------------------------------|--------------------------| |-------------------------------|--------------------------|
| `#launcher` | Launcher widget box | | `.launcher` | Launcher widget box |
| `#launcher .item` | App button | | `.launcher .item` | App button |
| `#launcher .item.open` | App button (open app) | | `.launcher .item.open` | App button (open app) |
| `#launcher .item.focused` | App button (focused app) | | `.launcher .item.focused` | App button (focused app) |
| `#launcher .item.urgent` | App button (urgent app) | | `.launcher .item.urgent` | App button (urgent app) |
| `#launcher-popup` | Popup container | | `.popup-launcher` | Popup container |
| `#launcher-popup .popup-item` | Window button in popup | | `.popup-launcher .popup-item` | Window button in popup |
For more information on styling, please see the [styling guide](styling-guide).

View File

@ -135,24 +135,26 @@ and will be replaced with values from the currently playing track:
| Selector | Description | | Selector | Description |
|-------------------------------------|------------------------------------------| |-------------------------------------|------------------------------------------|
| `#music` | Tray widget button | | `.music` | Tray widget button |
| `#music #contents` | Tray widget button contents box | | `.music .contents` | Tray widget button contents box |
| `#popup-music` | Popup box | | `.popup-music` | Popup box |
| `#popup-music #album-art` | Album art image inside popup box | | `.popup-music .album-art` | Album art image inside popup box |
| `#popup-music #title` | Track title container inside popup box | | `.popup-music .title` | Track title container inside popup box |
| `#popup-music #title .icon` | Track title icon label inside popup box | | `.popup-music .title .icon` | Track title icon label inside popup box |
| `#popup-music #title .label` | Track title label inside popup box | | `.popup-music .title .label` | Track title label inside popup box |
| `#popup-music #album` | Track album container inside popup box | | `.popup-music .album` | Track album container inside popup box |
| `#popup-music #album .icon` | Track album icon label inside popup box | | `.popup-music .album .icon` | Track album icon label inside popup box |
| `#popup-music #album .label` | Track album label inside popup box | | `.popup-music .album .label` | Track album label inside popup box |
| `#popup-music #artist` | Track artist container inside popup box | | `.popup-music .artist` | Track artist container inside popup box |
| `#popup-music #artist .icon` | Track artist icon label inside popup box | | `.popup-music .artist .icon` | Track artist icon label inside popup box |
| `#popup-music #artist .label` | Track artist label inside popup box | | `.popup-music .artist .label` | Track artist label inside popup box |
| `#popup-music #controls` | Controls container inside popup box | | `.popup-music .controls` | Controls container inside popup box |
| `#popup-music #controls #btn-prev` | Previous button inside popup box | | `.popup-music .controls .btn-prev` | Previous button inside popup box |
| `#popup-music #controls #btn-play` | Play button inside popup box | | `.popup-music .controls .btn-play` | Play button inside popup box |
| `#popup-music #controls #btn-pause` | Pause button inside popup box | | `.popup-music .controls .btn-pause` | Pause button inside popup box |
| `#popup-music #controls #btn-next` | Next button inside popup box | | `.popup-music .controls .btn-next` | Next button inside popup box |
| `#popup-music #volume` | Volume container inside popup box | | `.popup-music .volume` | Volume container inside popup box |
| `#popup-music #volume #slider` | Volume slider popup box | | `.popup-music .volume .slider` | Volume slider popup box |
| `#popup-music #volume .icon` | Volume icon label inside popup box | | `.popup-music .volume .icon` | Volume icon label inside popup box |
For more information on styling, please see the [styling guide](styling-guide).

View File

@ -83,5 +83,7 @@ end:
## Styling ## Styling
| Selector | Description | | Selector | Description |
|---------------|---------------------| |-----------|---------------------|
| `#script` | Script widget label | | `.script` | Script widget label |
For more information on styling, please see the [styling guide](styling-guide).

View File

@ -172,5 +172,7 @@ The following tokens can be used in the `format` configuration option:
| Selector | Description | | Selector | Description |
|------------------|------------------------------| |------------------|------------------------------|
| `#sysinfo` | Sysinfo widget box | | `.sysinfo` | Sysinfo widget box |
| `#sysinfo #item` | Individual information label | | `.sysinfo .item` | Individual information label |
For more information on styling, please see the [styling guide](styling-guide).

View File

@ -60,5 +60,7 @@ end:
| Selector | Description | | Selector | Description |
|---------------|------------------| |---------------|------------------|
| `#tray` | Tray widget box | | `.tray` | Tray widget box |
| `#tray .item` | Tray icon button | | `.tray .item` | Tray icon button |
For more information on styling, please see the [styling guide](styling-guide).

View File

@ -72,9 +72,11 @@ end:
| Selector | Description | | Selector | Description |
|---------------------------------|-----------------------------| |---------------------------------|-----------------------------|
| `#upower` | Upower widget container. | | `.upower` | Upower widget container. |
| `#upower #icon` | Upower widget battery icon. | | `.upower .icon` | Upower widget battery icon. |
| `#upower #button` | Upower widget button. | | `.upower .button` | Upower widget button. |
| `#upower #button #label` | Upower widget button label. | | `.upower .button .label` | Upower widget button label. |
| `#popup-upower` | Upower popup box. | | `.popup-upower` | Upower popup box. |
| `#popup-upower #upower-details` | Label inside the popup. | | `.popup-upower .upower-details` | Label inside the popup. |
For more information on styling, please see the [styling guide](styling-guide).

View File

@ -91,6 +91,8 @@ end:
| Selector | Description | | Selector | Description |
|-----------------------------|--------------------------------------| |-----------------------------|--------------------------------------|
| `#workspaces` | Workspaces widget box | | `.workspaces` | Workspaces widget box |
| `#workspaces .item` | Workspace button | | `.workspaces .item` | Workspace button |
| `#workspaces .item.focused` | Workspace button (workspace focused) | | `.workspaces .item.focused` | Workspace button (workspace focused) |
For more information on styling, please see the [styling guide](styling-guide).

View File

@ -14,16 +14,11 @@
border-radius: 0; border-radius: 0;
} }
box, menubar { box, menubar, button {
background-color: @color_bg; background-color: @color_bg;
} }
button { button, label {
color: @color_text;
background-color: @color_bg;
}
label {
color: @color_text; color: @color_text;
} }
@ -43,12 +38,12 @@ button:hover {
/* -- clipboard -- */ /* -- clipboard -- */
#clipboard { .clipboard {
margin-left: 5px; margin-left: 5px;
font-size: 1.1em; font-size: 1.1em;
} }
#popup-clipboard .item { .popup-clipboard .item {
padding-bottom: 0.3em; padding-bottom: 0.3em;
border-bottom: 1px solid @color_border; border-bottom: 1px solid @color_border;
} }
@ -56,125 +51,125 @@ button:hover {
/* -- clock -- */ /* -- clock -- */
#clock { .clock {
font-weight: bold; font-weight: bold;
margin-left: 5px; margin-left: 5px;
} }
#calendar-clock { .popup-clock .calendar-clock {
color: @color_text; color: @color_text;
font-size: 2.5em; font-size: 2.5em;
padding-bottom: 0.1em; padding-bottom: 0.1em;
} }
#popup-clock #calendar { .popup-clock .calendar {
background-color: @color_bg; background-color: @color_bg;
color: @color_text; color: @color_text;
} }
#popup-clock #calendar .header { .popup-clock .calendar .header {
padding-top: 1em; padding-top: 1em;
border-top: 1px solid @color_border; border-top: 1px solid @color_border;
font-size: 1.5em; font-size: 1.5em;
} }
#popup-clock #calendar:selected { .popup-clock .calendar:selected {
background-color: @color_border_active; background-color: @color_border_active;
} }
/* -- launcher -- */ /* -- launcher -- */
#launcher .item { .launcher .item {
margin-right: 4px; margin-right: 4px;
} }
#launcher .item:not(.focused):hover { .launcher .item:not(.focused):hover {
background-color: @color_bg_dark; background-color: @color_bg_dark;
} }
#launcher .open { .launcher .open {
border-bottom: 2px solid @color_text; border-bottom: 1px solid @color_text;
} }
#launcher .focused { .launcher .focused {
border-bottom-color: @color_border_active; border-bottom: 2px solid @color_border_active;
} }
#launcher .urgent { .launcher .urgent {
border-bottom-color: @color_urgent; border-bottom-color: @color_urgent;
} }
#popup-launcher { .popup-launcher {
padding: 0; padding: 0;
} }
#popup-launcher .popup-item:not(:first-child) { .popup-launcher .popup-item:not(:first-child) {
border-top: 1px solid @color_border; border-top: 1px solid @color_border;
} }
/* -- music -- */ /* -- music -- */
#music:hover * { .music:hover * {
background-color: @color_bg_dark; background-color: @color_bg_dark;
} }
#popup-music #album-art { .popup-music .album-art {
margin-right: 1em; margin-right: 1em;
} }
#popup-music #title .icon *, #popup-music #title .label { .popup-music .title .icon *, .popup-music .title .label {
font-size: 1.7em; font-size: 1.7em;
} }
#popup-music #controls *:disabled { .popup-music .controls *:disabled {
color: @color_border; color: @color_border;
} }
#popup-music #volume scale slider { .popup-music .volume scale slider {
border-radius: 100%; border-radius: 100%;
} }
/* volume icon */ /* volume icon */
#popup-music #volume > box:last-child label { .popup-music .volume > box:last-child label {
margin-left: 6px; margin-left: 6px;
} }
/* -- script -- */ /* -- script -- */
#script { .script {
padding-left: 10px; padding-left: 10px;
} }
/* -- sys_info -- */ /* -- sys_info -- */
#sysinfo { .sysinfo {
margin-left: 10px; margin-left: 10px;
} }
#sysinfo #item { .sysinfo .item {
margin-left: 5px; margin-left: 5px;
} }
/* -- tray -- */ /* -- tray -- */
#tray { .tray {
margin-left: 10px; margin-left: 10px;
} }
/* -- workspaces -- */ /* -- workspaces -- */
#workspaces .item.focused { .workspaces .item.focused {
box-shadow: inset 0 -3px; box-shadow: inset 0 -3px;
background-color: @color_bg_dark; background-color: @color_bg_dark;
} }
#workspaces .item:hover { .workspaces .item:hover {
box-shadow: inset 0 -3px; box-shadow: inset 0 -3px;
} }
@ -185,7 +180,7 @@ button:hover {
font-size: 1.4em; font-size: 1.4em;
padding-bottom: 0.4em; padding-bottom: 0.4em;
margin-bottom: 0.6em; margin-bottom: 0.6em;
border-bottom: 1px solid @color_border border-bottom: 1px solid @color_border;
} }
.popup-power-menu .power-btn { .popup-power-menu .power-btn {
@ -196,3 +191,4 @@ button:hover {
.popup-power-menu #buttons > *:nth-child(1) .power-btn { .popup-power-menu #buttons > *:nth-child(1) .power-btn {
margin-right: 1em; margin-right: 1em;
} }

72
scripts/migrate-styles.sh Executable file
View File

@ -0,0 +1,72 @@
#!/usr/bin/env bash
# Migrates CSS selectors from widget names to CSS classes.
# These changed as part of the 0.12 release.
# ⚠ This script will **NOT** check for custom styles and may mangle them!
# ⚠ It is *highly recommended* that you back up your existing styles before running this!
style_path="$HOME/.config/ironbar/style.css"
# general
sed -i 's/#icon/.icon/g' "$style_path"
sed -i 's/#label/.label/g' "$style_path"
sed -i 's/#image/.image/g' "$style_path"
# clipboard
sed -i 's/#clipboard/.clipboard/g' "$style_path"
sed -i 's/#popup-clipboard/.popup-clipboard/g' "$style_path"
# clock
sed -i 's/#clock/.clock/g' "$style_path"
sed -i 's/#popup-clock/.popup-clock/g' "$style_path"
sed -i 's/#calendar-clock/.calendar-clock/g' "$style_path"
sed -i 's/#calendar/.calendar/g' "$style_path"
# custom
sed -i 's/#custom/.custom/g' "$style_path"
sed -i 's/#popup-custom/.popup-custom/g' "$style_path"
# focused
sed -i 's/#focused/.focused/g' "$style_path"
# launcher
sed -i 's/#launcher/.launcher/g' "$style_path"
sed -i 's/#popup-launcher/.popup-launcher/g' "$style_path"
sed -i 's/#launcher-popup/.popup-launcher/g' "$style_path" # was incorrect in docs
# music
sed -i 's/#music/.music/g' "$style_path"
sed -i 's/#contents/.contents/g' "$style_path"
sed -i 's/#popup-music/.popup-music/g' "$style_path"
sed -i 's/#album-art/.album-art/g' "$style_path"
sed -i 's/#title/.title/g' "$style_path"
sed -i 's/#album/.album/g' "$style_path"
sed -i 's/#artist/.artist/g' "$style_path"
sed -i 's/#controls/.controls/g' "$style_path"
sed -i 's/#btn-prev/.btn-prev/g' "$style_path"
sed -i 's/#btn-play/.btn-play/g' "$style_path"
sed -i 's/#btn-pause/.btn-pause/g' "$style_path"
sed -i 's/#btn-next/.btn-next/g' "$style_path"
sed -i 's/#volume/.volume/g' "$style_path"
sed -i 's/#slider/.slider/g' "$style_path"
# script
sed -i 's/#script/.script/g' "$style_path"
# sys_info
sed -i 's/#sysinfo/.sysinfo/g' "$style_path"
sed -i 's/#item/.item/g' "$style_path"
# tray
sed -i 's/#tray/.tray/g' "$style_path"
# upower
sed -i 's/#upower/.upower/g' "$style_path"
sed -i 's/#button/.button/g' "$style_path"
sed -i 's/#popup-upower/.popup-upower/g' "$style_path"
sed -i 's/#upower-details/.upower-details/g' "$style_path"
# workspaces
sed -i 's/#workspaces/.workspaces/g' "$style_path"
sed -i 's/#item/.item/g' "$style_path"

View File

@ -1,5 +1,7 @@
use crate::config::{BarPosition, MarginConfig, ModuleConfig}; use crate::config::{BarPosition, MarginConfig, ModuleConfig};
use crate::modules::{create_module, wrap_widget, ModuleInfo, ModuleLocation}; use crate::modules::{
create_module, set_widget_identifiers, wrap_widget, ModuleInfo, ModuleLocation,
};
use crate::popup::Popup; use crate::popup::Popup;
use crate::Config; use crate::Config;
use color_eyre::Result; use color_eyre::Result;
@ -195,8 +197,10 @@ fn add_modules(
macro_rules! add_module { macro_rules! add_module {
($module:expr, $id:expr) => {{ ($module:expr, $id:expr) => {{
let common = $module.common.take().expect("Common config did not exist"); let common = $module.common.take().expect("Common config did not exist");
let widget = create_module(*$module, $id, &info, &Arc::clone(&popup))?; let widget_parts = create_module(*$module, $id, &info, &Arc::clone(&popup))?;
let container = wrap_widget(&widget, common, orientation); set_widget_identifiers(&widget_parts, &common);
let container = wrap_widget(&widget_parts.widget, common, orientation);
content.add(&container); content.add(&container);
}}; }};
} }

View File

@ -10,8 +10,11 @@ use tracing::trace;
/// Common configuration options /// Common configuration options
/// which can be set on every module. /// which can be set on every module.
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Default, Deserialize, Clone)]
pub struct CommonConfig { pub struct CommonConfig {
pub class: Option<String>,
pub name: Option<String>,
pub show_if: Option<ScriptInput>, pub show_if: Option<ScriptInput>,
pub transition_type: Option<TransitionType>, pub transition_type: Option<TransitionType>,
pub transition_duration: Option<u32>, pub transition_duration: Option<u32>,
@ -54,7 +57,7 @@ impl TransitionType {
impl CommonConfig { impl CommonConfig {
/// Configures the module's container according to the common config options. /// Configures the module's container according to the common config options.
pub fn install(mut self, container: &EventBox, revealer: &Revealer) { pub fn install_events(mut self, container: &EventBox, revealer: &Revealer) {
self.install_show_if(container, revealer); self.install_show_if(container, revealer);
let left_click_script = self.on_click_left.map(Script::new_polling); let left_click_script = self.on_click_left.map(Script::new_polling);

8
src/gtk_helpers.rs Normal file
View File

@ -0,0 +1,8 @@
use glib::IsA;
use gtk::prelude::*;
use gtk::Widget;
/// Adds a new CSS class to a widget.
pub fn add_class<W: IsA<Widget>>(widget: &W, class: &str) {
widget.style_context().add_class(class);
}

View File

@ -1,4 +1,5 @@
use super::ImageProvider; use super::ImageProvider;
use crate::gtk_helpers::add_class;
use gtk::prelude::*; use gtk::prelude::*;
use gtk::{Button, IconTheme, Image, Label, Orientation}; use gtk::{Button, IconTheme, Image, Label, Orientation};
use tracing::error; use tracing::error;
@ -9,7 +10,7 @@ pub fn new_icon_button(input: &str, icon_theme: &IconTheme, size: i32) -> Button
if ImageProvider::is_definitely_image_input(input) { if ImageProvider::is_definitely_image_input(input) {
let image = Image::new(); let image = Image::new();
image.set_widget_name("image"); add_class(&image, "image");
match ImageProvider::parse(input, icon_theme, size) match ImageProvider::parse(input, icon_theme, size)
.and_then(|provider| provider.load_into_image(image.clone())) .and_then(|provider| provider.load_into_image(image.clone()))
@ -36,7 +37,7 @@ pub fn new_icon_label(input: &str, icon_theme: &IconTheme, size: i32) -> gtk::Bo
if ImageProvider::is_definitely_image_input(input) { if ImageProvider::is_definitely_image_input(input) {
let image = Image::new(); let image = Image::new();
image.set_widget_name("image"); add_class(&image, "image");
container.add(&image); container.add(&image);
@ -47,7 +48,7 @@ pub fn new_icon_label(input: &str, icon_theme: &IconTheme, size: i32) -> gtk::Bo
} }
} else { } else {
let label = Label::new(Some(input)); let label = Label::new(Some(input));
label.set_widget_name("label"); add_class(&label, "label");
container.add(&label); container.add(&label);
} }

View File

@ -7,6 +7,7 @@ mod config;
mod desktop_file; mod desktop_file;
mod dynamic_string; mod dynamic_string;
mod error; mod error;
mod gtk_helpers;
mod image; mod image;
mod logging; mod logging;
mod macros; mod macros;

View File

@ -154,11 +154,7 @@ impl Module<Button> for ClipboardModule {
where where
Self: Sized, Self: Sized,
{ {
let container = gtk::Box::builder() let container = gtk::Box::new(Orientation::Vertical, 10);
.orientation(Orientation::Vertical)
.spacing(10)
.name("popup-clipboard")
.build();
let entries = gtk::Box::new(Orientation::Vertical, 5); let entries = gtk::Box::new(Orientation::Vertical, 5);
container.add(&entries); container.add(&entries);

View File

@ -1,4 +1,5 @@
use crate::config::CommonConfig; use crate::config::CommonConfig;
use crate::gtk_helpers::add_class;
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext}; use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
use crate::popup::Popup; use crate::popup::Popup;
use crate::{send_async, try_send}; use crate::{send_async, try_send};
@ -96,20 +97,16 @@ impl Module<Button> for ClockModule {
rx: glib::Receiver<Self::SendMessage>, rx: glib::Receiver<Self::SendMessage>,
_info: &ModuleInfo, _info: &ModuleInfo,
) -> Option<gtk::Box> { ) -> Option<gtk::Box> {
let container = gtk::Box::builder() let container = gtk::Box::new(Orientation::Vertical, 0);
.orientation(Orientation::Vertical)
.name("popup-clock")
.build();
let clock = Label::builder() let clock = Label::builder().halign(Align::Center).build();
.name("calendar-clock") add_class(&clock, "calendar-clock");
.halign(Align::Center)
.build();
let format = "%H:%M:%S"; let format = "%H:%M:%S";
container.add(&clock); container.add(&clock);
let calendar = Calendar::builder().name("calendar").build(); let calendar = Calendar::new();
add_class(&calendar, "calendar");
container.add(&calendar); container.add(&calendar);
{ {

View File

@ -28,8 +28,6 @@ use tracing::{debug, error};
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
pub struct CustomModule { pub struct CustomModule {
/// Container class name
class: Option<String>,
/// Widgets to add to the bar container /// Widgets to add to the bar container
bar: Vec<WidgetConfig>, bar: Vec<WidgetConfig>,
/// Widgets to add to the popup container /// Widgets to add to the popup container
@ -197,10 +195,6 @@ impl Module<gtk::Box> for CustomModule {
let orientation = info.bar_position.get_orientation(); let orientation = info.bar_position.get_orientation();
let container = gtk::Box::builder().orientation(orientation).build(); let container = gtk::Box::builder().orientation(orientation).build();
if let Some(ref class) = self.class {
container.style_context().add_class(class);
}
let custom_context = CustomWidgetContext { let custom_context = CustomWidgetContext {
tx: &context.controller_tx, tx: &context.controller_tx,
bar_orientation: orientation, bar_orientation: orientation,
@ -230,13 +224,7 @@ impl Module<gtk::Box> for CustomModule {
where where
Self: Sized, Self: Sized,
{ {
let container = gtk::Box::builder().name("popup-custom").build(); let container = gtk::Box::new(Orientation::Horizontal, 0);
if let Some(class) = self.class {
container
.style_context()
.add_class(format!("popup-{class}").as_str());
}
if let Some(popup) = self.popup { if let Some(popup) = self.popup {
let custom_context = CustomWidgetContext { let custom_context = CustomWidgetContext {

View File

@ -1,5 +1,6 @@
use crate::clients::wayland::{self, ToplevelEvent}; use crate::clients::wayland::{self, ToplevelEvent};
use crate::config::{CommonConfig, TruncateMode}; use crate::config::{CommonConfig, TruncateMode};
use crate::gtk_helpers::add_class;
use crate::image::ImageProvider; use crate::image::ImageProvider;
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext}; use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
use crate::{send_async, try_send}; use crate::{send_async, try_send};
@ -95,8 +96,11 @@ impl Module<gtk::Box> for FocusedModule {
let container = gtk::Box::new(info.bar_position.get_orientation(), 5); let container = gtk::Box::new(info.bar_position.get_orientation(), 5);
let icon = gtk::Image::builder().name("icon").build(); let icon = gtk::Image::new();
let label = Label::builder().name("label").build(); add_class(&icon, "icon");
let label = Label::new(None);
add_class(&label, "label");
if let Some(truncate) = self.truncate { if let Some(truncate) = self.truncate {
truncate.truncate_label(&label); truncate.truncate_label(&label);

View File

@ -413,10 +413,7 @@ impl Module<gtk::Box> for LauncherModule {
) -> Option<gtk::Box> { ) -> Option<gtk::Box> {
const MAX_WIDTH: i32 = 250; const MAX_WIDTH: i32 = 250;
let container = gtk::Box::builder() let container = gtk::Box::new(Orientation::Vertical, 0);
.orientation(Orientation::Vertical)
.name("popup-launcher")
.build();
// we need some content to force the container to have a size // we need some content to force the container to have a size
let placeholder = Button::with_label("PLACEHOLDER"); let placeholder = Button::with_label("PLACEHOLDER");

View File

@ -120,7 +120,7 @@ pub fn create_module<TModule, TWidget, TSend, TRec>(
id: usize, id: usize,
info: &ModuleInfo, info: &ModuleInfo,
popup: &Arc<RwLock<Popup>>, popup: &Arc<RwLock<Popup>>,
) -> Result<TWidget> ) -> Result<ModuleWidget<TWidget>>
where where
TModule: Module<TWidget, SendMessage = TSend, ReceiveMessage = TRec>, TModule: Module<TWidget, SendMessage = TSend, ReceiveMessage = TRec>,
TWidget: IsA<Widget>, TWidget: IsA<Widget>,
@ -145,17 +145,21 @@ where
let name = TModule::name(); let name = TModule::name();
let module_parts = module.into_widget(context, info)?; let module_parts = module.into_widget(context, info)?;
module_parts.widget.set_widget_name(name); module_parts.widget.style_context().add_class(name);
let mut has_popup = false; let mut has_popup = false;
if let Some(popup_content) = module_parts.popup { if let Some(popup_content) = module_parts.popup.clone() {
popup_content
.style_context()
.add_class(&format!("popup-{name}"));
register_popup_content(popup, id, popup_content); register_popup_content(popup, id, popup_content);
has_popup = true; has_popup = true;
} }
setup_receiver(channel, w_tx, p_tx, popup.clone(), name, id, has_popup); setup_receiver(channel, w_tx, p_tx, popup.clone(), name, id, has_popup);
Ok(module_parts.widget) Ok(module_parts)
} }
/// Registers the popup content with the popup. /// Registers the popup content with the popup.
@ -234,6 +238,32 @@ fn setup_receiver<TSend>(
}); });
} }
pub fn set_widget_identifiers<TWidget: IsA<Widget>>(
widget_parts: &ModuleWidget<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}"));
}
}
if let Some(ref class) = common.class {
// gtk counts classes with spaces as the same class
for part in class.split(' ') {
widget_parts.widget.style_context().add_class(part);
}
if let Some(ref popup) = widget_parts.popup {
for part in class.split(' ') {
popup.style_context().add_class(&format!("popup-{part}"));
}
}
}
}
/// Takes a widget and adds it into a new `gtk::EventBox`. /// Takes a widget and adds it into a new `gtk::EventBox`.
/// The event box container is returned. /// The event box container is returned.
pub fn wrap_widget<W: IsA<Widget>>( pub fn wrap_widget<W: IsA<Widget>>(
@ -241,14 +271,14 @@ pub fn wrap_widget<W: IsA<Widget>>(
common: CommonConfig, common: CommonConfig,
orientation: Orientation, orientation: Orientation,
) -> EventBox { ) -> EventBox {
let revealer = Revealer::builder() let transition_type = common
.transition_type(
common
.transition_type .transition_type
.as_ref() .as_ref()
.unwrap_or(&TransitionType::SlideStart) .unwrap_or(&TransitionType::SlideStart)
.to_revealer_transition_type(orientation), .to_revealer_transition_type(orientation);
)
let revealer = Revealer::builder()
.transition_type(transition_type)
.transition_duration(common.transition_duration.unwrap_or(250)) .transition_duration(common.transition_duration.unwrap_or(250))
.build(); .build();
@ -259,7 +289,7 @@ pub fn wrap_widget<W: IsA<Widget>>(
container.add_events(EventMask::SCROLL_MASK); container.add_events(EventMask::SCROLL_MASK);
container.add(&revealer); container.add(&revealer);
common.install(&container, &revealer); common.install_events(&container, &revealer);
container container
} }

View File

@ -1,6 +1,7 @@
mod config; mod config;
use crate::clients::music::{self, MusicClient, PlayerState, PlayerUpdate, Status, Track}; use crate::clients::music::{self, MusicClient, PlayerState, PlayerUpdate, Status, Track};
use crate::gtk_helpers::add_class;
use crate::image::{new_icon_button, new_icon_label, ImageProvider}; use crate::image::{new_icon_button, new_icon_label, ImageProvider};
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext}; use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
use crate::popup::Popup; use crate::popup::Popup;
@ -135,7 +136,7 @@ impl Module<Button> for MusicModule {
PlayerCommand::Play => client.play(), PlayerCommand::Play => client.play(),
PlayerCommand::Pause => client.pause(), PlayerCommand::Pause => client.pause(),
PlayerCommand::Next => client.next(), PlayerCommand::Next => client.next(),
PlayerCommand::Volume(vol) => client.set_volume_percent(vol), // .unwrap_or_else(|_| error!("Failed to update player volume")), PlayerCommand::Volume(vol) => client.set_volume_percent(vol),
}; };
if let Err(err) = res { if let Err(err) = res {
@ -154,11 +155,8 @@ impl Module<Button> for MusicModule {
info: &ModuleInfo, info: &ModuleInfo,
) -> Result<ModuleWidget<Button>> { ) -> Result<ModuleWidget<Button>> {
let button = Button::new(); let button = Button::new();
let button_contents = gtk::Box::builder() let button_contents = gtk::Box::new(Orientation::Horizontal, 5);
.orientation(Orientation::Horizontal) add_class(&button_contents, "contents");
.spacing(5)
.name("contents")
.build();
button.add(&button_contents); button.add(&button_contents);
@ -243,17 +241,13 @@ impl Module<Button> for MusicModule {
) -> Option<gtk::Box> { ) -> Option<gtk::Box> {
let icon_theme = info.icon_theme; let icon_theme = info.icon_theme;
let container = gtk::Box::builder() let container = gtk::Box::new(Orientation::Horizontal, 10);
.orientation(Orientation::Horizontal)
.spacing(10)
.name("popup-music")
.build();
let album_image = gtk::Image::builder() let album_image = gtk::Image::builder()
.width_request(128) .width_request(128)
.height_request(128) .height_request(128)
.name("album-art")
.build(); .build();
add_class(&album_image, "album-art");
let icons = self.icons; let icons = self.icons;
@ -262,27 +256,28 @@ impl Module<Button> for MusicModule {
let album_label = IconLabel::new(&icons.album, None, icon_theme); let album_label = IconLabel::new(&icons.album, None, icon_theme);
let artist_label = IconLabel::new(&icons.artist, None, icon_theme); let artist_label = IconLabel::new(&icons.artist, None, icon_theme);
title_label.container.set_widget_name("title"); add_class(&title_label.container, "title");
album_label.container.set_widget_name("album"); add_class(&album_label.container, "album");
artist_label.container.set_widget_name("artist"); add_class(&artist_label.container, "artist");
info_box.add(&title_label.container); info_box.add(&title_label.container);
info_box.add(&album_label.container); info_box.add(&album_label.container);
info_box.add(&artist_label.container); info_box.add(&artist_label.container);
let controls_box = gtk::Box::builder().name("controls").build(); let controls_box = gtk::Box::new(Orientation::Horizontal, 0);
add_class(&controls_box, "controls");
let btn_prev = new_icon_button(&icons.prev, icon_theme, self.icon_size); let btn_prev = new_icon_button(&icons.prev, icon_theme, self.icon_size);
btn_prev.set_widget_name("btn-prev"); add_class(&btn_prev, "btn-prev");
let btn_play = new_icon_button(&icons.play, icon_theme, self.icon_size); let btn_play = new_icon_button(&icons.play, icon_theme, self.icon_size);
btn_play.set_widget_name("btn-play"); add_class(&btn_play, "btn-play");
let btn_pause = new_icon_button(&icons.pause, icon_theme, self.icon_size); let btn_pause = new_icon_button(&icons.pause, icon_theme, self.icon_size);
btn_pause.set_widget_name("btn-pause"); add_class(&btn_pause, "btn-pause");
let btn_next = new_icon_button(&icons.next, icon_theme, self.icon_size); let btn_next = new_icon_button(&icons.next, icon_theme, self.icon_size);
btn_next.set_widget_name("btn-next"); add_class(&btn_next, "btn-next");
controls_box.add(&btn_prev); controls_box.add(&btn_prev);
controls_box.add(&btn_play); controls_box.add(&btn_play);
@ -291,18 +286,15 @@ impl Module<Button> for MusicModule {
info_box.add(&controls_box); info_box.add(&controls_box);
let volume_box = gtk::Box::builder() let volume_box = gtk::Box::new(Orientation::Vertical, 5);
.orientation(Orientation::Vertical) add_class(&volume_box, "volume");
.spacing(5)
.name("volume")
.build();
let volume_slider = Scale::with_range(Orientation::Vertical, 0.0, 100.0, 5.0); let volume_slider = Scale::with_range(Orientation::Vertical, 0.0, 100.0, 5.0);
volume_slider.set_inverted(true); volume_slider.set_inverted(true);
volume_slider.set_widget_name("slider"); add_class(&volume_slider, "slider");
let volume_icon = new_icon_label(&icons.volume, icon_theme, self.icon_size); let volume_icon = new_icon_label(&icons.volume, icon_theme, self.icon_size);
volume_icon.style_context().add_class("icon"); add_class(&volume_icon, "icon");
volume_box.pack_start(&volume_slider, true, true, 0); volume_box.pack_start(&volume_slider, true, true, 0);
volume_box.pack_end(&volume_icon, false, false, 0); volume_box.pack_end(&volume_icon, false, false, 0);
@ -466,8 +458,8 @@ impl IconLabel {
let icon = new_icon_label(icon_input, icon_theme, 24); let icon = new_icon_label(icon_input, icon_theme, 24);
let label = Label::new(label); let label = Label::new(label);
icon.style_context().add_class("icon"); add_class(&icon, "icon");
label.style_context().add_class("label"); add_class(&label, "label");
container.add(&icon); container.add(&icon);
container.add(&label); container.add(&label);

View File

@ -1,4 +1,5 @@
use crate::config::CommonConfig; use crate::config::CommonConfig;
use crate::gtk_helpers::add_class;
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext}; use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
use crate::send_async; use crate::send_async;
use color_eyre::Result; use color_eyre::Result;
@ -193,12 +194,11 @@ impl Module<gtk::Box> for SysInfoModule {
let mut labels = Vec::new(); let mut labels = Vec::new();
for format in &self.format { for format in &self.format {
let label = Label::builder() let label = Label::builder().label(format).use_markup(true).build();
.label(format)
.use_markup(true) add_class(&label, "item");
.name("item")
.build();
label.set_angle(info.bar_position.get_angle()); label.set_angle(info.bar_position.get_angle());
container.add(&label); container.add(&label);
labels.push(label); labels.push(label);
} }

View File

@ -1,5 +1,6 @@
use crate::clients::upower::get_display_proxy; use crate::clients::upower::get_display_proxy;
use crate::config::CommonConfig; use crate::config::CommonConfig;
use crate::gtk_helpers::add_class;
use crate::image::ImageProvider; use crate::image::ImageProvider;
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext}; use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
use crate::popup::Popup; use crate::popup::Popup;
@ -144,20 +145,20 @@ impl Module<gtk::Box> for UpowerModule {
info: &ModuleInfo, info: &ModuleInfo,
) -> Result<ModuleWidget<gtk::Box>> { ) -> Result<ModuleWidget<gtk::Box>> {
let icon_theme = info.icon_theme.clone(); let icon_theme = info.icon_theme.clone();
let icon = gtk::Image::builder().name("icon").build(); let icon = gtk::Image::new();
add_class(&icon, "icon");
let label = Label::builder() let label = Label::builder()
.label(&self.format) .label(&self.format)
.use_markup(true) .use_markup(true)
.name("label")
.build(); .build();
add_class(&label, "label");
let container = gtk::Box::builder() let container = gtk::Box::new(Orientation::Horizontal, 0);
.orientation(Orientation::Horizontal) add_class(&container, "upower");
.name("upower")
.build();
let button = Button::builder().name("button").build(); let button = Button::new();
add_class(&button, "button");
button.add(&label); button.add(&label);
container.add(&button); container.add(&button);
@ -207,11 +208,10 @@ impl Module<gtk::Box> for UpowerModule {
{ {
let container = gtk::Box::builder() let container = gtk::Box::builder()
.orientation(Orientation::Horizontal) .orientation(Orientation::Horizontal)
.name("popup-upower")
.build(); .build();
let label = Label::builder().name("upower-details").build(); let label = Label::new(None);
container.add(&label); add_class(&label, "upower-details");
rx.attach(None, move |properties| { rx.attach(None, move |properties| {
let mut format = String::new(); let mut format = String::new();