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

feat: fully implement orientation/justify options

Adds `orientation` and `justify` options to all modules and custom
widgets where it makes sense to do so.

Any modules without support document this. Widgets fully document the
options inline where present for now.

Resolves #296
This commit is contained in:
Jake Stanger 2025-03-22 19:19:47 +00:00
parent 0855039bce
commit c20feb77b7
No known key found for this signature in database
GPG key ID: C51FC8F9CB0BEA61
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();