1
0
Fork 0
mirror of https://github.com/Zedfrigg/ironbar.git synced 2025-08-17 14:51:04 +02:00

Merge pull request #907 from JakeStanger/feat/widget-rotation

feat: fully implement orientation/justify options
This commit is contained in:
Jake Stanger 2025-03-22 21:47:43 +00:00 committed by GitHub
commit 5a7ad5675d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 317 additions and 161 deletions

View file

@ -351,7 +351,14 @@ For information on the `Script` type, and embedding scripts in strings, see [her
| 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`. | | `name` | `string` | `null` | 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`. | | `class` | `string` | `null` | One or more CSS classes, allowing you to style it using `.class`. |
For more information on styling, please see the [styling guide](styling-guide). For more information on styling, please see the [styling guide](styling-guide).
#### Formatting
| Name | Type | Default | Description |
|---------------|--------------------------------------------------------|----------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------|
| `orientation` | `horizontal` or `vertical` (shorthand: `'h'` or `'v'`) | `horizontal` or `vertical` | The direction in which the widget and its text are laid out. Some modules additionally provide a `direction` option to provide further control. |
| `justify` | `left`, `right`, `center`, `fill` | `left` | The justification (alignment) of the widget text shown on the bar. |

View file

@ -71,12 +71,13 @@ A clickable button, which can run a command when clicked.
> Type `button` > Type `button`
| Name | Type | Default | Description | | Name | Type | Default | Description |
|------------|-------------------------------------------------|---------|--------------------------------------------------------------------------------------------------| |---------------|------------------------------------------------------------|----------------|--------------------------------------------------------------------------------------------------|
| `label` | [Dynamic String](dynamic-values#dynamic-string) | `null` | Widget text label. Pango markup and embedded scripts are supported. Ignored if `widgets` is set. | | `label` | [Dynamic String](dynamic-values#dynamic-string) | `null` | Widget text label. Pango markup and embedded scripts are supported. Ignored if `widgets` is set. |
| `widgets` | `(Module or Widget)[]` | `[]` | List of modules/widgets to add to this button. | | `widgets` | `(Module or Widget)[]` | `[]` | List of modules/widgets to add to this button. |
| `on_click` | `string [command]` | `null` | Command to execute. More on this [below](#commands). | | `on_click` | `string [command]` | `null` | Command to execute. More on this [below](#commands). |
| `orientation` | `'horizontal'` or `'vertical'` (shorthand: `'h'` or `'v'`) | `'horizontal'` | Orientation of the button. | | `orientation` | `'horizontal'` or `'vertical'` (shorthand: `'h'` or `'v'`) | `'horizontal'` | Orientation of the label text. |
| `justify` | `'left'`, `'right'`, `'center'`, or `'fill'` | `'left'` | Justification (alignment) of the label text. |
#### Image #### Image

View file

@ -17,6 +17,9 @@ Supports wired ethernet, wifi, cellular data and VPN connections among others.
|-------------|-----------|---------|-------------------------| |-------------|-----------|---------|-------------------------|
| `icon_size` | `integer` | `24` | Size to render icon at. | | `icon_size` | `integer` | `24` | Size to render icon at. |
> [!NOTE]
> This module does not support module-level [layout options](module-level-options#layout).
<details> <details>
<summary>JSON</summary> <summary>JSON</summary>

View file

@ -21,6 +21,8 @@ Clicking the widget opens the SwayNC panel.
| `icons.open_some` | `string` | `󱥁` | Icon to show when the panel is open, with notifications. | | `icons.open_some` | `string` | `󱥁` | Icon to show when the panel is open, with notifications. |
| `icons.open_dnd` | `string` | `󱅮` | Icon to show when the panel is open, with DnD enabled. Takes higher priority than count-based icons. | | `icons.open_dnd` | `string` | `󱅮` | Icon to show when the panel is open, with DnD enabled. Takes higher priority than count-based icons. |
> [!NOTE]
> This module does not support module-level [layout options](module-level-options#layout).
<details> <details>
<summary>JSON</summary> <summary>JSON</summary>

View file

@ -77,7 +77,7 @@ impl BarPosition {
/// Gets the angle that label text should be displayed at /// Gets the angle that label text should be displayed at
/// based on this position. /// based on this position.
pub const fn get_angle(self) -> f64 { pub const fn angle(self) -> f64 {
match self { match self {
Self::Top | Self::Bottom => 0.0, Self::Top | Self::Bottom => 0.0,
Self::Left => 90.0, Self::Left => 90.0,

37
src/config/layout.rs Normal file
View file

@ -0,0 +1,37 @@
use crate::config::{ModuleJustification, ModuleOrientation};
use crate::modules::ModuleInfo;
use serde::Deserialize;
#[derive(Clone, Debug, Deserialize, Default)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct LayoutConfig {
/// The orientation to display the widget contents.
/// Setting to vertical will rotate text 90 degrees.
///
/// **Valid options**: `horizontal`, `vertical`
/// <br>
/// **Default**: `horizontal`
orientation: Option<ModuleOrientation>,
/// The justification (alignment) of the widget text shown on the bar.
///
/// **Valid options**: `left`, `right`, `center`, `fill`
/// <br>
/// **Default**: `left`
#[serde(default)]
pub justify: ModuleJustification,
}
impl LayoutConfig {
pub fn orientation(&self, info: &ModuleInfo) -> gtk::Orientation {
self.orientation
.map(ModuleOrientation::into)
.unwrap_or(info.bar_position.orientation())
}
pub fn angle(&self, info: &ModuleInfo) -> f64 {
self.orientation
.map(ModuleOrientation::to_angle)
.unwrap_or(info.bar_position.angle())
}
}

View file

@ -1,5 +1,6 @@
mod common; mod common;
mod r#impl; mod r#impl;
mod layout;
mod truncate; mod truncate;
#[cfg(feature = "cairo")] #[cfg(feature = "cairo")]
@ -46,6 +47,7 @@ use std::collections::HashMap;
use schemars::JsonSchema; use schemars::JsonSchema;
pub use self::common::{CommonConfig, ModuleJustification, ModuleOrientation, TransitionType}; pub use self::common::{CommonConfig, ModuleJustification, ModuleOrientation, TransitionType};
pub use self::layout::LayoutConfig;
pub use self::truncate::{EllipsizeMode, TruncateMode}; pub use self::truncate::{EllipsizeMode, TruncateMode};
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]

View file

@ -5,30 +5,54 @@ use gtk::{Button, IconTheme, Image, Label, Orientation};
use std::ops::Deref; use std::ops::Deref;
#[cfg(any(feature = "music", feature = "workspaces", feature = "clipboard"))] #[cfg(any(feature = "music", feature = "workspaces", feature = "clipboard"))]
pub fn new_icon_button(input: &str, icon_theme: &IconTheme, size: i32) -> Button { #[derive(Debug, Clone)]
let button = Button::new(); pub struct IconButton {
button: Button,
label: Label,
}
if ImageProvider::is_definitely_image_input(input) { #[cfg(any(feature = "music", feature = "workspaces", feature = "clipboard"))]
impl IconButton {
pub fn new(input: &str, icon_theme: &IconTheme, size: i32) -> Self {
let button = Button::new();
let image = Image::new(); let image = Image::new();
image.add_class("image"); let label = Label::new(Some(input));
image.add_class("icon");
match ImageProvider::parse(input, icon_theme, false, size) if ImageProvider::is_definitely_image_input(input) {
.map(|provider| provider.load_into_image(&image)) image.add_class("image");
{ image.add_class("icon");
Some(_) => {
button.set_image(Some(&image)); match ImageProvider::parse(input, icon_theme, false, size)
button.set_always_show_image(true); .map(|provider| provider.load_into_image(&image))
} {
None => { Some(_) => {
button.set_label(input); button.set_image(Some(&image));
button.set_always_show_image(true);
}
None => {
button.set_child(Some(&label));
label.show();
}
} }
} else {
button.set_child(Some(&label));
label.show();
} }
} else {
button.set_label(input); Self { button, label }
} }
button pub fn label(&self) -> &Label {
&self.label
}
}
impl Deref for IconButton {
type Target = Button;
fn deref(&self) -> &Self::Target {
&self.button
}
} }
#[cfg(any(feature = "music", feature = "keyboard"))] #[cfg(any(feature = "music", feature = "keyboard"))]
@ -98,6 +122,10 @@ impl IconLabel {
image.hide(); image.hide();
} }
} }
pub fn label(&self) -> &Label {
&self.label
}
} }
impl Deref for IconLabel { impl Deref for IconLabel {

View file

@ -1,8 +1,9 @@
use crate::clients::clipboard::{self, ClipboardEvent}; use crate::clients::clipboard::{self, ClipboardEvent};
use crate::clients::wayland::{ClipboardItem, ClipboardValue}; use crate::clients::wayland::{ClipboardItem, ClipboardValue};
use crate::config::{CommonConfig, TruncateMode}; use crate::config::{CommonConfig, LayoutConfig, TruncateMode};
use crate::gtk_helpers::IronbarGtkExt;
use crate::gtk_helpers::IronbarLabelExt; use crate::gtk_helpers::IronbarLabelExt;
use crate::image::new_icon_button; use crate::image::IconButton;
use crate::modules::{ use crate::modules::{
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, PopupButton, WidgetContext, Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, PopupButton, WidgetContext,
}; };
@ -14,6 +15,7 @@ use gtk::prelude::*;
use gtk::{Button, EventBox, Image, Label, Orientation, RadioButton, Widget}; use gtk::{Button, EventBox, Image, Label, Orientation, RadioButton, Widget};
use serde::Deserialize; use serde::Deserialize;
use std::collections::HashMap; use std::collections::HashMap;
use std::ops::Deref;
use tokio::sync::{broadcast, mpsc}; use tokio::sync::{broadcast, mpsc};
use tracing::{debug, error}; use tracing::{debug, error};
@ -47,6 +49,10 @@ pub struct ClipboardModule {
/// **Default**: `null` /// **Default**: `null`
truncate: Option<TruncateMode>, truncate: Option<TruncateMode>,
/// See [layout options](module-level-options#layout)
#[serde(default, flatten)]
layout: LayoutConfig,
/// See [common options](module-level-options#common-options). /// See [common options](module-level-options#common-options).
#[serde(flatten)] #[serde(flatten)]
pub common: Option<CommonConfig>, pub common: Option<CommonConfig>,
@ -142,8 +148,11 @@ impl Module<Button> for ClipboardModule {
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>, context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
info: &ModuleInfo, info: &ModuleInfo,
) -> color_eyre::Result<ModuleParts<Button>> { ) -> color_eyre::Result<ModuleParts<Button>> {
let button = new_icon_button(&self.icon, info.icon_theme, self.icon_size); let button = IconButton::new(&self.icon, info.icon_theme, self.icon_size);
button.style_context().add_class("btn"); button.label().set_angle(self.layout.angle(info));
button.label().set_justify(self.layout.justify.into());
button.add_class("btn");
let tx = context.tx.clone(); let tx = context.tx.clone();
button.connect_clicked(move |button| { button.connect_clicked(move |button| {
@ -155,7 +164,7 @@ impl Module<Button> for ClipboardModule {
.into_popup(context.controller_tx.clone(), rx, context, 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.deref().clone(), popup))
} }
fn into_popup( fn into_popup(

View file

@ -8,7 +8,7 @@ use serde::Deserialize;
use tokio::sync::{broadcast, mpsc}; use tokio::sync::{broadcast, mpsc};
use tokio::time::sleep; use tokio::time::sleep;
use crate::config::{CommonConfig, ModuleJustification, ModuleOrientation}; use crate::config::{CommonConfig, LayoutConfig};
use crate::gtk_helpers::IronbarGtkExt; 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,
@ -49,22 +49,9 @@ pub struct ClockModule {
#[serde(default = "default_locale")] #[serde(default = "default_locale")]
locale: String, locale: String,
/// The orientation to display the widget contents. /// See [layout options](module-level-options#layout)
/// Setting to vertical will rotate text 90 degrees. #[serde(default, flatten)]
/// layout: LayoutConfig,
/// **Valid options**: `horizontal`, `vertical`
/// <br>
/// **Default**: `horizontal`
#[serde(default)]
orientation: ModuleOrientation,
/// The justification (alignment) of the date/time shown on the bar.
///
/// **Valid options**: `left`, `right`, `center`, `fill`
/// <br>
/// **Default**: `left`
#[serde(default)]
justify: ModuleJustification,
/// See [common options](module-level-options#common-options). /// See [common options](module-level-options#common-options).
#[serde(flatten)] #[serde(flatten)]
@ -77,9 +64,8 @@ impl Default for ClockModule {
format: default_format(), format: default_format(),
format_popup: default_popup_format(), format_popup: default_popup_format(),
locale: default_locale(), locale: default_locale(),
orientation: ModuleOrientation::Horizontal, layout: LayoutConfig::default(),
common: Some(CommonConfig::default()), common: Some(CommonConfig::default()),
justify: ModuleJustification::Left,
} }
} }
} }
@ -136,10 +122,11 @@ impl Module<Button> for ClockModule {
) -> Result<ModuleParts<Button>> { ) -> Result<ModuleParts<Button>> {
let button = Button::new(); let button = Button::new();
let label = Label::builder() let label = Label::builder()
.angle(self.orientation.to_angle()) .angle(self.layout.angle(info))
.use_markup(true) .use_markup(true)
.justify(self.justify.into()) .justify(self.layout.justify.into())
.build(); .build();
button.add(&label); button.add(&label);
let tx = context.tx.clone(); let tx = context.tx.clone();

View file

@ -1,9 +1,9 @@
use gtk::prelude::*; use gtk::prelude::*;
use gtk::{Button, Label, Orientation}; use gtk::{Button, Label};
use serde::Deserialize; use serde::Deserialize;
use super::{CustomWidget, CustomWidgetContext, ExecEvent, WidgetConfig}; use super::{CustomWidget, CustomWidgetContext, ExecEvent, WidgetConfig};
use crate::config::ModuleOrientation; use crate::config::LayoutConfig;
use crate::dynamic_value::dynamic_string; use crate::dynamic_value::dynamic_string;
use crate::gtk_helpers::IronbarLabelExt; use crate::gtk_helpers::IronbarLabelExt;
use crate::modules::PopupButton; use crate::modules::PopupButton;
@ -37,13 +37,9 @@ pub struct ButtonWidget {
/// **Default**: `null` /// **Default**: `null`
on_click: Option<String>, on_click: Option<String>,
/// Orientation of the button. /// See [layout options](module-level-options#layout)
/// #[serde(default, flatten)]
/// **Valid options**: `horizontal`, `vertical`, `h`, `v` layout: LayoutConfig,
/// <br />
/// **Default**: `horizontal`
#[serde(default)]
orientation: ModuleOrientation,
/// Modules and widgets to add to this box. /// Modules and widgets to add to this box.
/// ///
@ -59,7 +55,7 @@ impl CustomWidget for ButtonWidget {
context.popup_buttons.borrow_mut().push(button.clone()); context.popup_buttons.borrow_mut().push(button.clone());
if let Some(widgets) = self.widgets { if let Some(widgets) = self.widgets {
let container = gtk::Box::new(Orientation::Horizontal, 0); let container = gtk::Box::new(self.layout.orientation(context.info), 0);
for widget in widgets { for widget in widgets {
widget.widget.add_to(&container, &context, widget.common); widget.widget.add_to(&container, &context, widget.common);
@ -70,7 +66,9 @@ impl CustomWidget for ButtonWidget {
let label = Label::new(None); let label = Label::new(None);
label.set_use_markup(true); label.set_use_markup(true);
label.set_angle(self.orientation.to_angle()); if !context.is_popup {
label.set_angle(self.layout.angle(context.info));
}
button.add(&label); button.add(&label);

View file

@ -4,7 +4,7 @@ use serde::Deserialize;
use super::{CustomWidget, CustomWidgetContext}; use super::{CustomWidget, CustomWidgetContext};
use crate::build; use crate::build;
use crate::config::{ModuleJustification, ModuleOrientation, TruncateMode}; use crate::config::{LayoutConfig, TruncateMode};
use crate::dynamic_value::dynamic_string; use crate::dynamic_value::dynamic_string;
use crate::gtk_helpers::IronbarLabelExt; use crate::gtk_helpers::IronbarLabelExt;
@ -28,22 +28,9 @@ pub struct LabelWidget {
/// **Required** /// **Required**
label: String, label: String,
/// Orientation of the label. /// See [layout options](module-level-options#layout)
/// Setting to vertical will rotate text 90 degrees. #[serde(default, flatten)]
/// layout: LayoutConfig,
/// **Valid options**: `horizontal`, `vertical`, `h`, `v`
/// <br />
/// **Default**: `horizontal`
#[serde(default)]
orientation: ModuleOrientation,
/// The justification (alignment) of the label text.
///
/// **Valid options**: `left`, `right`, `center`, `fill`
/// <br>
/// **Default**: `left`
#[serde(default)]
justify: ModuleJustification,
/// See [truncate options](module-level-options#truncate-mode). /// See [truncate options](module-level-options#truncate-mode).
/// ///
@ -54,11 +41,14 @@ pub struct LabelWidget {
impl CustomWidget for LabelWidget { impl CustomWidget for LabelWidget {
type Widget = Label; type Widget = Label;
fn into_widget(self, _context: CustomWidgetContext) -> Self::Widget { fn into_widget(self, context: CustomWidgetContext) -> Self::Widget {
let label = build!(self, Self::Widget); let label = build!(self, Self::Widget);
label.set_angle(self.orientation.to_angle()); if !context.is_popup {
label.set_justify(self.justify.into()); label.set_angle(self.layout.angle(context.info));
}
label.set_justify(self.layout.justify.into());
label.set_use_markup(true); label.set_use_markup(true);
if let Some(truncate) = self.truncate { if let Some(truncate) = self.truncate {

View file

@ -91,6 +91,7 @@ struct CustomWidgetContext<'a> {
info: &'a ModuleInfo<'a>, info: &'a ModuleInfo<'a>,
tx: &'a mpsc::Sender<ExecEvent>, tx: &'a mpsc::Sender<ExecEvent>,
bar_orientation: Orientation, bar_orientation: Orientation,
is_popup: bool,
icon_theme: &'a IconTheme, icon_theme: &'a IconTheme,
popup_buttons: Rc<RefCell<Vec<Button>>>, popup_buttons: Rc<RefCell<Vec<Button>>>,
module_factory: AnyModuleFactory, module_factory: AnyModuleFactory,
@ -235,6 +236,7 @@ impl Module<gtk::Box> for CustomModule {
info, info,
tx: &context.controller_tx, tx: &context.controller_tx,
bar_orientation: orientation, bar_orientation: orientation,
is_popup: false,
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()) module_factory: BarModuleFactory::new(context.ironbar.clone(), context.popup.clone())
@ -287,7 +289,8 @@ impl Module<gtk::Box> for CustomModule {
let custom_context = CustomWidgetContext { let custom_context = CustomWidgetContext {
info, info,
tx: &tx, tx: &tx,
bar_orientation: info.bar_position.orientation(), bar_orientation: Orientation::Horizontal,
is_popup: true,
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( module_factory: PopupModuleFactory::new(

View file

@ -1,5 +1,5 @@
use crate::clients::wayland::{self, ToplevelEvent}; use crate::clients::wayland::{self, ToplevelEvent};
use crate::config::{CommonConfig, TruncateMode}; use crate::config::{CommonConfig, LayoutConfig, TruncateMode};
use crate::gtk_helpers::IronbarGtkExt; use crate::gtk_helpers::IronbarGtkExt;
use crate::gtk_helpers::IronbarLabelExt; use crate::gtk_helpers::IronbarLabelExt;
use crate::image::ImageProvider; use crate::image::ImageProvider;
@ -38,6 +38,10 @@ pub struct FocusedModule {
/// **Default**: `null` /// **Default**: `null`
truncate: Option<TruncateMode>, truncate: Option<TruncateMode>,
/// See [layout options](module-level-options#layout)
#[serde(default, flatten)]
layout: LayoutConfig,
/// See [common options](module-level-options#common-options). /// See [common options](module-level-options#common-options).
#[serde(flatten)] #[serde(flatten)]
pub common: Option<CommonConfig>, pub common: Option<CommonConfig>,
@ -50,6 +54,7 @@ impl Default for FocusedModule {
show_title: crate::config::default_true(), show_title: crate::config::default_true(),
icon_size: default_icon_size(), icon_size: default_icon_size(),
truncate: None, truncate: None,
layout: LayoutConfig::default(),
common: Some(CommonConfig::default()), common: Some(CommonConfig::default()),
} }
} }
@ -132,7 +137,7 @@ impl Module<gtk::Box> for FocusedModule {
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>, context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
info: &ModuleInfo, info: &ModuleInfo,
) -> Result<ModuleParts<gtk::Box>> { ) -> Result<ModuleParts<gtk::Box>> {
let container = gtk::Box::new(info.bar_position.orientation(), 5); let container = gtk::Box::new(self.layout.orientation(info), 5);
let icon = gtk::Image::new(); let icon = gtk::Image::new();
if self.show_icon { if self.show_icon {
@ -140,7 +145,11 @@ impl Module<gtk::Box> for FocusedModule {
container.add(&icon); container.add(&icon);
} }
let label = Label::new(None); let label = Label::builder()
.angle(self.layout.angle(info))
.justify(self.layout.justify.into())
.build();
label.add_class("label"); label.add_class("label");
if let Some(truncate) = self.truncate { if let Some(truncate) = self.truncate {

View file

@ -2,7 +2,7 @@ use std::collections::HashMap;
use color_eyre::Result; use color_eyre::Result;
use color_eyre::eyre::Report; use color_eyre::eyre::Report;
use gtk::{Button, prelude::*}; use gtk::prelude::*;
use serde::Deserialize; use serde::Deserialize;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tracing::{debug, trace}; use tracing::{debug, trace};
@ -10,9 +10,9 @@ use tracing::{debug, trace};
use super::{Module, ModuleInfo, ModuleParts, WidgetContext}; use super::{Module, ModuleInfo, ModuleParts, WidgetContext};
use crate::clients::compositor::{self, KeyboardLayoutUpdate}; use crate::clients::compositor::{self, KeyboardLayoutUpdate};
use crate::clients::libinput::{Event, Key, KeyEvent}; use crate::clients::libinput::{Event, Key, KeyEvent};
use crate::config::CommonConfig; use crate::config::{CommonConfig, LayoutConfig};
use crate::gtk_helpers::IronbarGtkExt; use crate::gtk_helpers::IronbarGtkExt;
use crate::image::IconLabel; use crate::image::{IconButton, IconLabel};
use crate::{glib_recv, module_impl, module_update, send_async, spawn, try_send}; use crate::{glib_recv, module_impl, module_update, send_async, spawn, try_send};
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
@ -61,6 +61,11 @@ pub struct KeyboardModule {
#[serde(default = "default_seat")] #[serde(default = "default_seat")]
seat: String, seat: String,
// -- common --
/// See [layout options](module-level-options#layout)
#[serde(default, flatten)]
layout: LayoutConfig,
/// See [common options](module-level-options#common-options). /// See [common options](module-level-options#common-options).
#[serde(flatten)] #[serde(flatten)]
pub common: Option<CommonConfig>, pub common: Option<CommonConfig>,
@ -249,15 +254,22 @@ impl Module<gtk::Box> for KeyboardModule {
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>, context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
info: &ModuleInfo, info: &ModuleInfo,
) -> Result<ModuleParts<gtk::Box>> { ) -> Result<ModuleParts<gtk::Box>> {
let container = gtk::Box::new(info.bar_position.orientation(), 0); let container = gtk::Box::new(self.layout.orientation(info), 0);
let caps = IconLabel::new(&self.icons.caps_off, info.icon_theme, self.icon_size); let caps = IconLabel::new(&self.icons.caps_off, info.icon_theme, self.icon_size);
let num = IconLabel::new(&self.icons.num_off, info.icon_theme, self.icon_size); let num = IconLabel::new(&self.icons.num_off, info.icon_theme, self.icon_size);
let scroll = IconLabel::new(&self.icons.scroll_off, info.icon_theme, self.icon_size); let scroll = IconLabel::new(&self.icons.scroll_off, info.icon_theme, self.icon_size);
let layout_button = Button::new(); caps.label().set_angle(self.layout.angle(info));
let layout = IconLabel::new("", info.icon_theme, self.icon_size); caps.label().set_justify(self.layout.justify.into());
layout_button.add(&*layout);
num.label().set_angle(self.layout.angle(info));
num.label().set_justify(self.layout.justify.into());
scroll.label().set_angle(self.layout.angle(info));
scroll.label().set_justify(self.layout.justify.into());
let layout_button = IconButton::new("", info.icon_theme, self.icon_size);
if self.show_caps { if self.show_caps {
caps.add_class("key"); caps.add_class("key");
@ -278,8 +290,8 @@ impl Module<gtk::Box> for KeyboardModule {
} }
if self.show_layout { if self.show_layout {
layout.add_class("layout"); layout_button.add_class("layout");
container.add(&layout_button); container.add(&*layout_button);
} }
{ {
@ -318,7 +330,7 @@ impl Module<gtk::Box> for KeyboardModule {
} }
KeyboardUpdate::Layout(KeyboardLayoutUpdate(language)) => { KeyboardUpdate::Layout(KeyboardLayoutUpdate(language)) => {
let text = icons.layout_map.get(&language).unwrap_or(&language); let text = icons.layout_map.get(&language).unwrap_or(&language);
layout.set_label(Some(text)); layout_button.set_label(text);
} }
}; };

View file

@ -1,4 +1,4 @@
use crate::config::{CommonConfig, TruncateMode}; use crate::config::{CommonConfig, LayoutConfig, TruncateMode};
use crate::dynamic_value::dynamic_string; use crate::dynamic_value::dynamic_string;
use crate::gtk_helpers::IronbarLabelExt; use crate::gtk_helpers::IronbarLabelExt;
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext}; use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
@ -23,6 +23,10 @@ pub struct LabelModule {
/// **Default**: `null` /// **Default**: `null`
truncate: Option<TruncateMode>, truncate: Option<TruncateMode>,
/// See [layout options](module-level-options#layout)
#[serde(default, flatten)]
layout: LayoutConfig,
/// See [common options](module-level-options#common-options). /// See [common options](module-level-options#common-options).
#[serde(flatten)] #[serde(flatten)]
pub common: Option<CommonConfig>, pub common: Option<CommonConfig>,
@ -33,6 +37,7 @@ impl LabelModule {
Self { Self {
label, label,
truncate: None, truncate: None,
layout: LayoutConfig::default(),
common: Some(CommonConfig::default()), common: Some(CommonConfig::default()),
} }
} }
@ -61,9 +66,13 @@ impl Module<Label> for LabelModule {
fn into_widget( fn into_widget(
self, self,
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>, context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
_info: &ModuleInfo, info: &ModuleInfo,
) -> Result<ModuleParts<Label>> { ) -> Result<ModuleParts<Label>> {
let label = Label::builder().use_markup(true).build(); let label = Label::builder()
.use_markup(true)
.angle(self.layout.angle(info))
.justify(self.layout.justify.into())
.build();
if let Some(truncate) = self.truncate { if let Some(truncate) = self.truncate {
label.truncate(truncate); label.truncate(truncate);

View file

@ -9,7 +9,7 @@ use crate::{read_lock, try_send};
use glib::Propagation; use glib::Propagation;
use gtk::gdk::{BUTTON_MIDDLE, BUTTON_PRIMARY}; use gtk::gdk::{BUTTON_MIDDLE, BUTTON_PRIMARY};
use gtk::prelude::*; use gtk::prelude::*;
use gtk::{Button, IconTheme, Image, Label, Orientation}; use gtk::{Align, Button, IconTheme, Image, Justification, Label, Orientation};
use indexmap::IndexMap; use indexmap::IndexMap;
use std::ops::Deref; use std::ops::Deref;
use std::rc::Rc; use std::rc::Rc;
@ -156,6 +156,9 @@ pub struct AppearanceOptions {
pub show_icons: bool, pub show_icons: bool,
pub icon_size: i32, pub icon_size: i32,
pub truncate: TruncateMode, pub truncate: TruncateMode,
pub orientation: Orientation,
pub angle: f64,
pub justify: Justification,
} }
impl ItemButton { impl ItemButton {
@ -167,11 +170,13 @@ impl ItemButton {
tx: &Sender<ModuleUpdateEvent<LauncherUpdate>>, tx: &Sender<ModuleUpdateEvent<LauncherUpdate>>,
controller_tx: &Sender<ItemEvent>, controller_tx: &Sender<ItemEvent>,
) -> Self { ) -> Self {
let button = ImageTextButton::new(); let button = ImageTextButton::new(appearance.orientation);
if appearance.show_names { if appearance.show_names {
button.label.set_label(&item.name); button.label.set_label(&item.name);
button.label.truncate(appearance.truncate); button.label.truncate(appearance.truncate);
button.label.set_angle(appearance.angle);
button.label.set_justify(appearance.justify);
} }
if appearance.show_icons { if appearance.show_icons {
@ -329,18 +334,20 @@ pub struct ImageTextButton {
} }
impl ImageTextButton { impl ImageTextButton {
pub(crate) fn new() -> Self { pub(crate) fn new(orientation: Orientation) -> Self {
let button = Button::new(); let button = Button::new();
let container = gtk::Box::new(Orientation::Horizontal, 0); let container = gtk::Box::new(orientation, 0);
let label = Label::new(None); let label = Label::new(None);
let image = Image::new(); let image = Image::new();
button.add(&container);
container.add(&image); container.add(&image);
container.add(&label); container.add(&label);
button.add(&container);
container.set_halign(Align::Center);
container.set_valign(Align::Center);
ImageTextButton { ImageTextButton {
button, button,
label, label,

View file

@ -6,7 +6,7 @@ use self::item::{AppearanceOptions, Item, ItemButton, Window};
use self::open_state::OpenState; use self::open_state::OpenState;
use super::{Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, WidgetContext}; use super::{Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, WidgetContext};
use crate::clients::wayland::{self, ToplevelEvent}; use crate::clients::wayland::{self, ToplevelEvent};
use crate::config::{CommonConfig, EllipsizeMode, TruncateMode}; use crate::config::{CommonConfig, EllipsizeMode, LayoutConfig, TruncateMode};
use crate::desktop_file::find_desktop_file; use crate::desktop_file::find_desktop_file;
use crate::gtk_helpers::{IronbarGtkExt, IronbarLabelExt}; use crate::gtk_helpers::{IronbarGtkExt, IronbarLabelExt};
use crate::modules::launcher::item::ImageTextButton; use crate::modules::launcher::item::ImageTextButton;
@ -105,6 +105,10 @@ pub struct LauncherModule {
#[serde(default = "default_truncate_popup")] #[serde(default = "default_truncate_popup")]
truncate_popup: TruncateMode, truncate_popup: TruncateMode,
/// See [layout options](module-level-options#layout)
#[serde(default, flatten)]
layout: LayoutConfig,
/// See [common options](module-level-options#common-options). /// See [common options](module-level-options#common-options).
#[serde(flatten)] #[serde(flatten)]
pub common: Option<CommonConfig>, pub common: Option<CommonConfig>,
@ -451,13 +455,13 @@ impl Module<gtk::Box> for LauncherModule {
) -> crate::Result<ModuleParts<gtk::Box>> { ) -> crate::Result<ModuleParts<gtk::Box>> {
let icon_theme = info.icon_theme; let icon_theme = info.icon_theme;
let container = gtk::Box::new(info.bar_position.orientation(), 0); let container = gtk::Box::new(self.layout.orientation(info), 0);
let page_size = self.page_size; let page_size = self.page_size;
let pagination = Pagination::new( let pagination = Pagination::new(
&container, &container,
self.page_size, self.page_size,
info.bar_position.orientation(), self.layout.orientation(info),
IconContext { IconContext {
icon_back: &self.icons.page_back, icon_back: &self.icons.page_back,
icon_fwd: &self.icons.page_forward, icon_fwd: &self.icons.page_forward,
@ -477,6 +481,9 @@ impl Module<gtk::Box> for LauncherModule {
show_icons: self.show_icons, show_icons: self.show_icons,
icon_size: self.icon_size, icon_size: self.icon_size,
truncate: self.truncate, truncate: self.truncate,
orientation: self.layout.orientation(info),
angle: self.layout.angle(info),
justify: self.layout.justify.into(),
}; };
let show_names = self.show_names; let show_names = self.show_names;
@ -636,7 +643,7 @@ impl Module<gtk::Box> for LauncherModule {
.into_iter() .into_iter()
.map(|(_, win)| { .map(|(_, win)| {
// TODO: Currently has a useless image // TODO: Currently has a useless image
let button = ImageTextButton::new(); let button = ImageTextButton::new(Orientation::Horizontal);
button.set_height_request(40); button.set_height_request(40);
button.label.set_label(&win.name); button.label.set_label(&win.name);
button.label.truncate(self.truncate_popup); button.label.truncate(self.truncate_popup);
@ -662,7 +669,7 @@ impl Module<gtk::Box> for LauncherModule {
if let Some(buttons) = buttons.get_mut(&app_id) { if let Some(buttons) = buttons.get_mut(&app_id) {
// TODO: Currently has a useless image // TODO: Currently has a useless image
let button = ImageTextButton::new(); let button = ImageTextButton::new(Orientation::Horizontal);
button.set_height_request(40); button.set_height_request(40);
button.label.set_label(&win.name); button.label.set_label(&win.name);
button.label.truncate(self.truncate_popup); button.label.truncate(self.truncate_popup);

View file

@ -1,5 +1,5 @@
use crate::gtk_helpers::IronbarGtkExt; use crate::gtk_helpers::IronbarGtkExt;
use crate::image::new_icon_button; use crate::image::IconButton;
use gtk::prelude::*; use gtk::prelude::*;
use gtk::{Button, IconTheme, Orientation}; use gtk::{Button, IconTheme, Orientation};
use std::cell::RefCell; use std::cell::RefCell;
@ -29,13 +29,13 @@ impl Pagination {
) -> Self { ) -> Self {
let scroll_box = gtk::Box::new(orientation, 0); let scroll_box = gtk::Box::new(orientation, 0);
let scroll_back = new_icon_button( let scroll_back = IconButton::new(
icon_context.icon_back, icon_context.icon_back,
icon_context.icon_theme, icon_context.icon_theme,
icon_context.icon_size, icon_context.icon_size,
); );
let scroll_fwd = new_icon_button( let scroll_fwd = IconButton::new(
icon_context.icon_fwd, icon_context.icon_fwd,
icon_context.icon_theme, icon_context.icon_theme,
icon_context.icon_size, icon_context.icon_size,
@ -48,8 +48,8 @@ impl Pagination {
scroll_back.add_class("btn-back"); scroll_back.add_class("btn-back");
scroll_fwd.add_class("btn-forward"); scroll_fwd.add_class("btn-forward");
scroll_box.add(&scroll_back); scroll_box.add(&*scroll_back);
scroll_box.add(&scroll_fwd); scroll_box.add(&*scroll_fwd);
container.add(&scroll_box); container.add(&scroll_box);
let offset = Rc::new(RefCell::new(1)); let offset = Rc::new(RefCell::new(1));
@ -103,7 +103,7 @@ impl Pagination {
offset, offset,
controls_container: scroll_box, controls_container: scroll_box,
btn_fwd: scroll_fwd, btn_fwd: scroll_fwd.deref().clone(),
} }
} }

View file

@ -1,4 +1,4 @@
use crate::config::{CommonConfig, TruncateMode}; use crate::config::{CommonConfig, LayoutConfig, TruncateMode};
use dirs::{audio_dir, home_dir}; use dirs::{audio_dir, home_dir};
use serde::Deserialize; use serde::Deserialize;
use std::path::PathBuf; use std::path::PathBuf;
@ -147,6 +147,10 @@ pub struct MusicModule {
/// **Default**: `null` /// **Default**: `null`
pub(crate) truncate: Option<TruncateMode>, pub(crate) truncate: Option<TruncateMode>,
/// See [layout options](module-level-options#layout)
#[serde(default, flatten)]
pub(crate) layout: LayoutConfig,
/// See [common options](module-level-options#common-options). /// See [common options](module-level-options#common-options).
#[serde(flatten)] #[serde(flatten)]
pub common: Option<CommonConfig>, pub common: Option<CommonConfig>,

View file

@ -17,7 +17,7 @@ use crate::clients::music::{
self, MusicClient, PlayerState, PlayerUpdate, ProgressTick, Status, Track, self, MusicClient, PlayerState, PlayerUpdate, ProgressTick, Status, Track,
}; };
use crate::gtk_helpers::{IronbarGtkExt, IronbarLabelExt}; use crate::gtk_helpers::{IronbarGtkExt, IronbarLabelExt};
use crate::image::{IconLabel, ImageProvider, new_icon_button}; use crate::image::{IconButton, IconLabel, ImageProvider};
use crate::modules::PopupButton; use crate::modules::PopupButton;
use crate::modules::{ use crate::modules::{
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, WidgetContext, Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, WidgetContext,
@ -182,7 +182,7 @@ impl Module<Button> for MusicModule {
info: &ModuleInfo, info: &ModuleInfo,
) -> Result<ModuleParts<Button>> { ) -> Result<ModuleParts<Button>> {
let button = Button::new(); let button = Button::new();
let button_contents = gtk::Box::new(Orientation::Horizontal, 5); let button_contents = gtk::Box::new(self.layout.orientation(info), 5);
button_contents.add_class("contents"); button_contents.add_class("contents");
button.add(&button_contents); button.add(&button_contents);
@ -190,9 +190,16 @@ impl Module<Button> for MusicModule {
let icon_play = IconLabel::new(&self.icons.play, info.icon_theme, self.icon_size); let icon_play = IconLabel::new(&self.icons.play, info.icon_theme, self.icon_size);
let icon_pause = IconLabel::new(&self.icons.pause, info.icon_theme, self.icon_size); let icon_pause = IconLabel::new(&self.icons.pause, info.icon_theme, self.icon_size);
icon_play.label().set_angle(self.layout.angle(info));
icon_play.label().set_justify(self.layout.justify.into());
icon_pause.label().set_angle(self.layout.angle(info));
icon_pause.label().set_justify(self.layout.justify.into());
let label = Label::builder() let label = Label::builder()
.use_markup(true) .use_markup(true)
.angle(info.bar_position.get_angle()) .angle(self.layout.angle(info))
.justify(self.layout.justify.into())
.build(); .build();
if let Some(truncate) = self.truncate { if let Some(truncate) = self.truncate {
@ -297,22 +304,22 @@ impl Module<Button> for MusicModule {
let controls_box = gtk::Box::new(Orientation::Horizontal, 0); let controls_box = gtk::Box::new(Orientation::Horizontal, 0);
controls_box.add_class("controls"); controls_box.add_class("controls");
let btn_prev = new_icon_button(&icons.prev, icon_theme, self.icon_size); let btn_prev = IconButton::new(&icons.prev, icon_theme, self.icon_size);
btn_prev.add_class("btn-prev"); btn_prev.add_class("btn-prev");
let btn_play = new_icon_button(&icons.play, icon_theme, self.icon_size); let btn_play = IconButton::new(&icons.play, icon_theme, self.icon_size);
btn_play.add_class("btn-play"); btn_play.add_class("btn-play");
let btn_pause = new_icon_button(&icons.pause, icon_theme, self.icon_size); let btn_pause = IconButton::new(&icons.pause, icon_theme, self.icon_size);
btn_pause.add_class("btn-pause"); btn_pause.add_class("btn-pause");
let btn_next = new_icon_button(&icons.next, icon_theme, self.icon_size); let btn_next = IconButton::new(&icons.next, icon_theme, self.icon_size);
btn_next.add_class("btn-next"); btn_next.add_class("btn-next");
controls_box.add(&btn_prev); controls_box.add(&*btn_prev);
controls_box.add(&btn_play); controls_box.add(&*btn_play);
controls_box.add(&btn_pause); controls_box.add(&*btn_pause);
controls_box.add(&btn_next); controls_box.add(&*btn_next);
info_box.add(&controls_box); info_box.add(&controls_box);

View file

@ -1,11 +1,10 @@
use crate::config::CommonConfig; use crate::config::{CommonConfig, LayoutConfig};
use crate::gtk_helpers::IronbarLabelExt; use crate::gtk_helpers::IronbarLabelExt;
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, module_impl, 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::Label; use gtk::Label;
use gtk::prelude::*;
use serde::Deserialize; use serde::Deserialize;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tracing::error; use tracing::error;
@ -36,6 +35,11 @@ pub struct ScriptModule {
#[serde(default = "default_interval")] #[serde(default = "default_interval")]
interval: u64, interval: u64,
// -- Common --
/// See [layout options](module-level-options#layout)
#[serde(default, flatten)]
layout: LayoutConfig,
/// See [common options](module-level-options#common-options). /// See [common options](module-level-options#common-options).
#[serde(flatten)] #[serde(flatten)]
pub common: Option<CommonConfig>, pub common: Option<CommonConfig>,
@ -99,8 +103,11 @@ impl Module<Label> for ScriptModule {
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>, context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
info: &ModuleInfo, info: &ModuleInfo,
) -> Result<ModuleParts<Label>> { ) -> Result<ModuleParts<Label>> {
let label = Label::builder().use_markup(true).build(); let label = Label::builder()
label.set_angle(info.bar_position.get_angle()); .use_markup(true)
.angle(self.layout.angle(info))
.justify(self.layout.justify.into())
.build();
{ {
let label = label.clone(); let label = label.clone();

View file

@ -1,4 +1,4 @@
use crate::config::{CommonConfig, TruncateMode}; use crate::config::{CommonConfig, LayoutConfig, TruncateMode};
use crate::gtk_helpers::IronbarLabelExt; use crate::gtk_helpers::IronbarLabelExt;
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext}; use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
use crate::{await_sync, glib_recv, module_impl, try_send}; use crate::{await_sync, glib_recv, module_impl, try_send};
@ -19,6 +19,10 @@ pub struct SwayModeModule {
/// **Default**: `null` /// **Default**: `null`
pub truncate: Option<TruncateMode>, pub truncate: Option<TruncateMode>,
/// See [layout options](module-level-options#layout)
#[serde(default, flatten)]
layout: LayoutConfig,
/// See [common options](module-level-options#common-options). /// See [common options](module-level-options#common-options).
#[serde(flatten)] #[serde(flatten)]
pub common: Option<CommonConfig>, pub common: Option<CommonConfig>,
@ -57,10 +61,13 @@ impl Module<Label> for SwayModeModule {
fn into_widget( fn into_widget(
self, self,
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>, context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
_info: &ModuleInfo, info: &ModuleInfo,
) -> Result<ModuleParts<Label>> { ) -> Result<ModuleParts<Label>> {
let label = Label::new(None); let label = Label::builder()
label.set_use_markup(true); .use_markup(true)
.angle(self.layout.angle(info))
.justify(self.layout.justify.into())
.build();
{ {
let label = label.clone(); let label = label.clone();

View file

@ -3,7 +3,7 @@ mod renderer;
mod token; mod token;
use crate::clients::sysinfo::TokenType; use crate::clients::sysinfo::TokenType;
use crate::config::{CommonConfig, ModuleOrientation}; use crate::config::{CommonConfig, LayoutConfig, ModuleOrientation};
use crate::gtk_helpers::{IronbarGtkExt, IronbarLabelExt}; use crate::gtk_helpers::{IronbarGtkExt, IronbarLabelExt};
use crate::modules::sysinfo::token::Part; use crate::modules::sysinfo::token::Part;
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext}; use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
@ -34,14 +34,6 @@ pub struct SysInfoModule {
#[serde(default = "Interval::default")] #[serde(default = "Interval::default")]
interval: Interval, interval: Interval,
/// The orientation of text for the labels.
///
/// **Valid options**: `horizontal`, `vertical`, `h`, `v`
/// <br>
/// **Default** : `horizontal`
#[serde(default)]
orientation: ModuleOrientation,
/// The orientation by which the labels are laid out. /// The orientation by which the labels are laid out.
/// ///
/// **Valid options**: `horizontal`, `vertical`, `h`, `v` /// **Valid options**: `horizontal`, `vertical`, `h`, `v`
@ -49,6 +41,11 @@ pub struct SysInfoModule {
/// **Default** : `horizontal` /// **Default** : `horizontal`
direction: Option<ModuleOrientation>, direction: Option<ModuleOrientation>,
// -- common --
/// See [layout options](module-level-options#layout)
#[serde(default, flatten)]
layout: LayoutConfig,
/// See [common options](module-level-options#common-options). /// See [common options](module-level-options#common-options).
#[serde(flatten)] #[serde(flatten)]
pub common: Option<CommonConfig>, pub common: Option<CommonConfig>,
@ -281,22 +278,25 @@ impl Module<gtk::Box> for SysInfoModule {
fn into_widget( fn into_widget(
self, self,
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>, context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
_info: &ModuleInfo, info: &ModuleInfo,
) -> Result<ModuleParts<gtk::Box>> { ) -> Result<ModuleParts<gtk::Box>> {
let layout = match self.direction { let layout = match self.direction {
Some(orientation) => orientation, Some(orientation) => orientation.into(),
None => self.orientation, None => self.layout.orientation(info),
}; };
let container = gtk::Box::new(layout.into(), 10); let container = gtk::Box::new(layout, 10);
let mut labels = Vec::new(); let mut labels = Vec::new();
for _ in &self.format { for _ in &self.format {
let label = Label::builder().use_markup(true).build(); let label = Label::builder()
.use_markup(true)
.angle(self.layout.angle(info))
.justify(self.layout.justify.into())
.build();
label.add_class("item"); label.add_class("item");
label.set_angle(self.orientation.to_angle());
container.add(&label); container.add(&label);
labels.push(label); labels.push(label);

View file

@ -39,6 +39,7 @@ pub struct TrayModule {
/// **Default**: `horizontal` for horizontal bars, `vertical` for vertical bars /// **Default**: `horizontal` for horizontal bars, `vertical` for vertical bars
#[serde(default)] #[serde(default)]
direction: Option<ModuleOrientation>, direction: Option<ModuleOrientation>,
/// See [common options](module-level-options#common-options). /// See [common options](module-level-options#common-options).
#[serde(flatten)] #[serde(flatten)]
pub common: Option<CommonConfig>, pub common: Option<CommonConfig>,

View file

@ -8,7 +8,7 @@ use zbus;
use zbus::fdo::PropertiesProxy; use zbus::fdo::PropertiesProxy;
use crate::clients::upower::BatteryState; use crate::clients::upower::BatteryState;
use crate::config::CommonConfig; use crate::config::{CommonConfig, LayoutConfig};
use crate::gtk_helpers::{IronbarGtkExt, IronbarLabelExt}; use crate::gtk_helpers::{IronbarGtkExt, IronbarLabelExt};
use crate::image::ImageProvider; use crate::image::ImageProvider;
use crate::modules::PopupButton; use crate::modules::PopupButton;
@ -37,6 +37,11 @@ pub struct UpowerModule {
#[serde(default = "default_icon_size")] #[serde(default = "default_icon_size")]
icon_size: i32, icon_size: i32,
// -- Common --
/// See [layout options](module-level-options#layout)
#[serde(default, flatten)]
layout: LayoutConfig,
/// See [common options](module-level-options#common-options). /// See [common options](module-level-options#common-options).
#[serde(flatten)] #[serde(flatten)]
pub common: Option<CommonConfig>, pub common: Option<CommonConfig>,
@ -172,10 +177,13 @@ impl Module<Button> for UpowerModule {
let label = Label::builder() let label = Label::builder()
.label(&self.format) .label(&self.format)
.use_markup(true) .use_markup(true)
.angle(self.layout.angle(info))
.justify(self.layout.justify.into())
.build(); .build();
label.add_class("label"); label.add_class("label");
let container = gtk::Box::new(info.bar_position.orientation(), 5); let container = gtk::Box::new(self.layout.orientation(info), 5);
container.add_class("contents"); container.add_class("contents");
let button = Button::new(); let button = Button::new();

View file

@ -1,5 +1,5 @@
use crate::clients::volume::{self, Event}; use crate::clients::volume::{self, Event};
use crate::config::CommonConfig; use crate::config::{CommonConfig, LayoutConfig};
use crate::gtk_helpers::{IronbarGtkExt, IronbarLabelExt}; use crate::gtk_helpers::{IronbarGtkExt, IronbarLabelExt};
use crate::modules::{ use crate::modules::{
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, PopupButton, WidgetContext, Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, PopupButton, WidgetContext,
@ -36,6 +36,11 @@ pub struct VolumeModule {
#[serde(default)] #[serde(default)]
icons: Icons, icons: Icons,
// -- Common --
/// See [layout options](module-level-options#layout)
#[serde(default, flatten)]
layout: LayoutConfig,
/// See [common options](module-level-options#common-options). /// See [common options](module-level-options#common-options).
#[serde(flatten)] #[serde(flatten)]
pub common: Option<CommonConfig>, pub common: Option<CommonConfig>,
@ -204,7 +209,8 @@ impl Module<Button> for VolumeModule {
{ {
let button_label = Label::builder() let button_label = Label::builder()
.use_markup(true) .use_markup(true)
.angle(info.bar_position.get_angle()) .angle(self.layout.angle(info))
.justify(self.layout.justify.into())
.build(); .build();
let button = Button::new(); let button = Button::new();

View file

@ -1,6 +1,6 @@
use super::open_state::OpenState; use super::open_state::OpenState;
use crate::gtk_helpers::IronbarGtkExt; use crate::gtk_helpers::IronbarGtkExt;
use crate::image::new_icon_button; use crate::image::IconButton;
use crate::modules::workspaces::WorkspaceItemContext; use crate::modules::workspaces::WorkspaceItemContext;
use crate::try_send; use crate::try_send;
use gtk::Button as GtkButton; use gtk::Button as GtkButton;
@ -8,7 +8,7 @@ use gtk::prelude::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Button { pub struct Button {
button: GtkButton, button: IconButton,
workspace_id: i64, workspace_id: i64,
} }
@ -16,7 +16,7 @@ impl Button {
pub fn new(id: i64, name: &str, open_state: OpenState, context: &WorkspaceItemContext) -> Self { pub fn new(id: i64, name: &str, open_state: OpenState, context: &WorkspaceItemContext) -> Self {
let label = context.name_map.get(name).map_or(name, String::as_str); let label = context.name_map.get(name).map_or(name, String::as_str);
let button = new_icon_button(label, &context.icon_theme, context.icon_size); let button = IconButton::new(label, &context.icon_theme, context.icon_size);
button.set_widget_name(name); button.set_widget_name(name);
button.add_class("item"); button.add_class("item");

View file

@ -4,7 +4,7 @@ mod open_state;
use self::button::Button; use self::button::Button;
use crate::clients::compositor::{Workspace, WorkspaceClient, WorkspaceUpdate}; use crate::clients::compositor::{Workspace, WorkspaceClient, WorkspaceUpdate};
use crate::config::CommonConfig; use crate::config::{CommonConfig, LayoutConfig};
use crate::modules::workspaces::button_map::{ButtonMap, Identifier}; use crate::modules::workspaces::button_map::{ButtonMap, Identifier};
use crate::modules::workspaces::open_state::OpenState; use crate::modules::workspaces::open_state::OpenState;
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext}; use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
@ -127,6 +127,11 @@ pub struct WorkspacesModule {
#[serde(default = "default_icon_size")] #[serde(default = "default_icon_size")]
icon_size: i32, icon_size: i32,
// -- Common --
/// See [layout options](module-level-options#layout)
#[serde(default, flatten)]
layout: LayoutConfig,
/// See [common options](module-level-options#common-options). /// See [common options](module-level-options#common-options).
#[serde(flatten)] #[serde(flatten)]
pub common: Option<CommonConfig>, pub common: Option<CommonConfig>,
@ -228,7 +233,7 @@ impl Module<gtk::Box> for WorkspacesModule {
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>, context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
info: &ModuleInfo, info: &ModuleInfo,
) -> Result<ModuleParts<gtk::Box>> { ) -> Result<ModuleParts<gtk::Box>> {
let container = gtk::Box::new(info.bar_position.orientation(), 0); let container = gtk::Box::new(self.layout.orientation(info), 0);
let mut button_map = ButtonMap::new(); let mut button_map = ButtonMap::new();