Skip to content

Commit 5a7ad56

Browse files
authored
Merge pull request #907 from JakeStanger/feat/widget-rotation
feat: fully implement orientation/justify options
2 parents 0855039 + c20feb7 commit 5a7ad56

29 files changed

+317
-161
lines changed

Diff for: docs/Configuration guide.md

+9-2
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,14 @@ For information on the `Script` type, and embedding scripts in strings, see [her
351351
| Name | Type | Default | Description |
352352
|-----------|----------|---------|-----------------------------------------------------------------------------------|
353353
| `tooltip` | `string` | `null` | Shows this text on hover. Supports embedding scripts between `{{double braces}}`. |
354-
| `name` | `string` | `null` | Sets the unique widget name, allowing you to style it using `#name`. |
355-
| `class` | `string` | `null` | Sets one or more CSS classes, allowing you to style it using `.class`. |
354+
| `name` | `string` | `null` | The unique widget name, allowing you to style it using `#name`. |
355+
| `class` | `string` | `null` | One or more CSS classes, allowing you to style it using `.class`. |
356356

357357
For more information on styling, please see the [styling guide](styling-guide).
358+
359+
#### Formatting
360+
361+
| Name | Type | Default | Description |
362+
|---------------|--------------------------------------------------------|----------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------|
363+
| `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. |
364+
| `justify` | `left`, `right`, `center`, `fill` | `left` | The justification (alignment) of the widget text shown on the bar. |

Diff for: docs/modules/Custom.md

+7-6
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,13 @@ A clickable button, which can run a command when clicked.
7171

7272
> Type `button`
7373
74-
| Name | Type | Default | Description |
75-
|------------|-------------------------------------------------|---------|--------------------------------------------------------------------------------------------------|
76-
| `label` | [Dynamic String](dynamic-values#dynamic-string) | `null` | Widget text label. Pango markup and embedded scripts are supported. Ignored if `widgets` is set. |
77-
| `widgets` | `(Module or Widget)[]` | `[]` | List of modules/widgets to add to this button. |
78-
| `on_click` | `string [command]` | `null` | Command to execute. More on this [below](#commands). |
79-
| `orientation` | `'horizontal'` or `'vertical'` (shorthand: `'h'` or `'v'`) | `'horizontal'` | Orientation of the button. |
74+
| Name | Type | Default | Description |
75+
|---------------|------------------------------------------------------------|----------------|--------------------------------------------------------------------------------------------------|
76+
| `label` | [Dynamic String](dynamic-values#dynamic-string) | `null` | Widget text label. Pango markup and embedded scripts are supported. Ignored if `widgets` is set. |
77+
| `widgets` | `(Module or Widget)[]` | `[]` | List of modules/widgets to add to this button. |
78+
| `on_click` | `string [command]` | `null` | Command to execute. More on this [below](#commands). |
79+
| `orientation` | `'horizontal'` or `'vertical'` (shorthand: `'h'` or `'v'`) | `'horizontal'` | Orientation of the label text. |
80+
| `justify` | `'left'`, `'right'`, `'center'`, or `'fill'` | `'left'` | Justification (alignment) of the label text. |
8081

8182
#### Image
8283

Diff for: docs/modules/Network-Manager.md

+3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ Supports wired ethernet, wifi, cellular data and VPN connections among others.
1717
|-------------|-----------|---------|-------------------------|
1818
| `icon_size` | `integer` | `24` | Size to render icon at. |
1919

20+
> [!NOTE]
21+
> This module does not support module-level [layout options](module-level-options#layout).
22+
2023
<details>
2124
<summary>JSON</summary>
2225

Diff for: docs/modules/Notifications.md

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ Clicking the widget opens the SwayNC panel.
2121
| `icons.open_some` | `string` | `󱥁` | Icon to show when the panel is open, with notifications. |
2222
| `icons.open_dnd` | `string` | `󱅮` | Icon to show when the panel is open, with DnD enabled. Takes higher priority than count-based icons. |
2323

24+
> [!NOTE]
25+
> This module does not support module-level [layout options](module-level-options#layout).
2426
2527
<details>
2628
<summary>JSON</summary>

Diff for: src/config/impl.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ impl BarPosition {
7777

7878
/// Gets the angle that label text should be displayed at
7979
/// based on this position.
80-
pub const fn get_angle(self) -> f64 {
80+
pub const fn angle(self) -> f64 {
8181
match self {
8282
Self::Top | Self::Bottom => 0.0,
8383
Self::Left => 90.0,

Diff for: src/config/layout.rs

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
use crate::config::{ModuleJustification, ModuleOrientation};
2+
use crate::modules::ModuleInfo;
3+
use serde::Deserialize;
4+
5+
#[derive(Clone, Debug, Deserialize, Default)]
6+
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
7+
pub struct LayoutConfig {
8+
/// The orientation to display the widget contents.
9+
/// Setting to vertical will rotate text 90 degrees.
10+
///
11+
/// **Valid options**: `horizontal`, `vertical`
12+
/// <br>
13+
/// **Default**: `horizontal`
14+
orientation: Option<ModuleOrientation>,
15+
16+
/// The justification (alignment) of the widget text shown on the bar.
17+
///
18+
/// **Valid options**: `left`, `right`, `center`, `fill`
19+
/// <br>
20+
/// **Default**: `left`
21+
#[serde(default)]
22+
pub justify: ModuleJustification,
23+
}
24+
25+
impl LayoutConfig {
26+
pub fn orientation(&self, info: &ModuleInfo) -> gtk::Orientation {
27+
self.orientation
28+
.map(ModuleOrientation::into)
29+
.unwrap_or(info.bar_position.orientation())
30+
}
31+
32+
pub fn angle(&self, info: &ModuleInfo) -> f64 {
33+
self.orientation
34+
.map(ModuleOrientation::to_angle)
35+
.unwrap_or(info.bar_position.angle())
36+
}
37+
}

Diff for: src/config/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
mod common;
22
mod r#impl;
3+
mod layout;
34
mod truncate;
45

56
#[cfg(feature = "cairo")]
@@ -46,6 +47,7 @@ use std::collections::HashMap;
4647
use schemars::JsonSchema;
4748

4849
pub use self::common::{CommonConfig, ModuleJustification, ModuleOrientation, TransitionType};
50+
pub use self::layout::LayoutConfig;
4951
pub use self::truncate::{EllipsizeMode, TruncateMode};
5052

5153
#[derive(Debug, Deserialize, Clone)]

Diff for: src/image/gtk.rs

+45-17
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,54 @@ use gtk::{Button, IconTheme, Image, Label, Orientation};
55
use std::ops::Deref;
66

77
#[cfg(any(feature = "music", feature = "workspaces", feature = "clipboard"))]
8-
pub fn new_icon_button(input: &str, icon_theme: &IconTheme, size: i32) -> Button {
9-
let button = Button::new();
8+
#[derive(Debug, Clone)]
9+
pub struct IconButton {
10+
button: Button,
11+
label: Label,
12+
}
1013

11-
if ImageProvider::is_definitely_image_input(input) {
14+
#[cfg(any(feature = "music", feature = "workspaces", feature = "clipboard"))]
15+
impl IconButton {
16+
pub fn new(input: &str, icon_theme: &IconTheme, size: i32) -> Self {
17+
let button = Button::new();
1218
let image = Image::new();
13-
image.add_class("image");
14-
image.add_class("icon");
19+
let label = Label::new(Some(input));
1520

16-
match ImageProvider::parse(input, icon_theme, false, size)
17-
.map(|provider| provider.load_into_image(&image))
18-
{
19-
Some(_) => {
20-
button.set_image(Some(&image));
21-
button.set_always_show_image(true);
22-
}
23-
None => {
24-
button.set_label(input);
21+
if ImageProvider::is_definitely_image_input(input) {
22+
image.add_class("image");
23+
image.add_class("icon");
24+
25+
match ImageProvider::parse(input, icon_theme, false, size)
26+
.map(|provider| provider.load_into_image(&image))
27+
{
28+
Some(_) => {
29+
button.set_image(Some(&image));
30+
button.set_always_show_image(true);
31+
}
32+
None => {
33+
button.set_child(Some(&label));
34+
label.show();
35+
}
2536
}
37+
} else {
38+
button.set_child(Some(&label));
39+
label.show();
2640
}
27-
} else {
28-
button.set_label(input);
41+
42+
Self { button, label }
2943
}
3044

31-
button
45+
pub fn label(&self) -> &Label {
46+
&self.label
47+
}
48+
}
49+
50+
impl Deref for IconButton {
51+
type Target = Button;
52+
53+
fn deref(&self) -> &Self::Target {
54+
&self.button
55+
}
3256
}
3357

3458
#[cfg(any(feature = "music", feature = "keyboard"))]
@@ -98,6 +122,10 @@ impl IconLabel {
98122
image.hide();
99123
}
100124
}
125+
126+
pub fn label(&self) -> &Label {
127+
&self.label
128+
}
101129
}
102130

103131
impl Deref for IconLabel {

Diff for: src/modules/clipboard.rs

+14-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use crate::clients::clipboard::{self, ClipboardEvent};
22
use crate::clients::wayland::{ClipboardItem, ClipboardValue};
3-
use crate::config::{CommonConfig, TruncateMode};
3+
use crate::config::{CommonConfig, LayoutConfig, TruncateMode};
4+
use crate::gtk_helpers::IronbarGtkExt;
45
use crate::gtk_helpers::IronbarLabelExt;
5-
use crate::image::new_icon_button;
6+
use crate::image::IconButton;
67
use crate::modules::{
78
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, PopupButton, WidgetContext,
89
};
@@ -14,6 +15,7 @@ use gtk::prelude::*;
1415
use gtk::{Button, EventBox, Image, Label, Orientation, RadioButton, Widget};
1516
use serde::Deserialize;
1617
use std::collections::HashMap;
18+
use std::ops::Deref;
1719
use tokio::sync::{broadcast, mpsc};
1820
use tracing::{debug, error};
1921

@@ -47,6 +49,10 @@ pub struct ClipboardModule {
4749
/// **Default**: `null`
4850
truncate: Option<TruncateMode>,
4951

52+
/// See [layout options](module-level-options#layout)
53+
#[serde(default, flatten)]
54+
layout: LayoutConfig,
55+
5056
/// See [common options](module-level-options#common-options).
5157
#[serde(flatten)]
5258
pub common: Option<CommonConfig>,
@@ -142,8 +148,11 @@ impl Module<Button> for ClipboardModule {
142148
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
143149
info: &ModuleInfo,
144150
) -> color_eyre::Result<ModuleParts<Button>> {
145-
let button = new_icon_button(&self.icon, info.icon_theme, self.icon_size);
146-
button.style_context().add_class("btn");
151+
let button = IconButton::new(&self.icon, info.icon_theme, self.icon_size);
152+
button.label().set_angle(self.layout.angle(info));
153+
button.label().set_justify(self.layout.justify.into());
154+
155+
button.add_class("btn");
147156

148157
let tx = context.tx.clone();
149158
button.connect_clicked(move |button| {
@@ -155,7 +164,7 @@ impl Module<Button> for ClipboardModule {
155164
.into_popup(context.controller_tx.clone(), rx, context, info)
156165
.into_popup_parts(vec![&button]);
157166

158-
Ok(ModuleParts::new(button, popup))
167+
Ok(ModuleParts::new(button.deref().clone(), popup))
159168
}
160169

161170
fn into_popup(

Diff for: src/modules/clock.rs

+8-21
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use serde::Deserialize;
88
use tokio::sync::{broadcast, mpsc};
99
use tokio::time::sleep;
1010

11-
use crate::config::{CommonConfig, ModuleJustification, ModuleOrientation};
11+
use crate::config::{CommonConfig, LayoutConfig};
1212
use crate::gtk_helpers::IronbarGtkExt;
1313
use crate::modules::{
1414
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, PopupButton, WidgetContext,
@@ -49,22 +49,9 @@ pub struct ClockModule {
4949
#[serde(default = "default_locale")]
5050
locale: String,
5151

52-
/// The orientation to display the widget contents.
53-
/// Setting to vertical will rotate text 90 degrees.
54-
///
55-
/// **Valid options**: `horizontal`, `vertical`
56-
/// <br>
57-
/// **Default**: `horizontal`
58-
#[serde(default)]
59-
orientation: ModuleOrientation,
60-
61-
/// The justification (alignment) of the date/time shown on the bar.
62-
///
63-
/// **Valid options**: `left`, `right`, `center`, `fill`
64-
/// <br>
65-
/// **Default**: `left`
66-
#[serde(default)]
67-
justify: ModuleJustification,
52+
/// See [layout options](module-level-options#layout)
53+
#[serde(default, flatten)]
54+
layout: LayoutConfig,
6855

6956
/// See [common options](module-level-options#common-options).
7057
#[serde(flatten)]
@@ -77,9 +64,8 @@ impl Default for ClockModule {
7764
format: default_format(),
7865
format_popup: default_popup_format(),
7966
locale: default_locale(),
80-
orientation: ModuleOrientation::Horizontal,
67+
layout: LayoutConfig::default(),
8168
common: Some(CommonConfig::default()),
82-
justify: ModuleJustification::Left,
8369
}
8470
}
8571
}
@@ -136,10 +122,11 @@ impl Module<Button> for ClockModule {
136122
) -> Result<ModuleParts<Button>> {
137123
let button = Button::new();
138124
let label = Label::builder()
139-
.angle(self.orientation.to_angle())
125+
.angle(self.layout.angle(info))
140126
.use_markup(true)
141-
.justify(self.justify.into())
127+
.justify(self.layout.justify.into())
142128
.build();
129+
143130
button.add(&label);
144131

145132
let tx = context.tx.clone();

Diff for: src/modules/custom/button.rs

+9-11
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use gtk::prelude::*;
2-
use gtk::{Button, Label, Orientation};
2+
use gtk::{Button, Label};
33
use serde::Deserialize;
44

55
use super::{CustomWidget, CustomWidgetContext, ExecEvent, WidgetConfig};
6-
use crate::config::ModuleOrientation;
6+
use crate::config::LayoutConfig;
77
use crate::dynamic_value::dynamic_string;
88
use crate::gtk_helpers::IronbarLabelExt;
99
use crate::modules::PopupButton;
@@ -37,13 +37,9 @@ pub struct ButtonWidget {
3737
/// **Default**: `null`
3838
on_click: Option<String>,
3939

40-
/// Orientation of the button.
41-
///
42-
/// **Valid options**: `horizontal`, `vertical`, `h`, `v`
43-
/// <br />
44-
/// **Default**: `horizontal`
45-
#[serde(default)]
46-
orientation: ModuleOrientation,
40+
/// See [layout options](module-level-options#layout)
41+
#[serde(default, flatten)]
42+
layout: LayoutConfig,
4743

4844
/// Modules and widgets to add to this box.
4945
///
@@ -59,7 +55,7 @@ impl CustomWidget for ButtonWidget {
5955
context.popup_buttons.borrow_mut().push(button.clone());
6056

6157
if let Some(widgets) = self.widgets {
62-
let container = gtk::Box::new(Orientation::Horizontal, 0);
58+
let container = gtk::Box::new(self.layout.orientation(context.info), 0);
6359

6460
for widget in widgets {
6561
widget.widget.add_to(&container, &context, widget.common);
@@ -70,7 +66,9 @@ impl CustomWidget for ButtonWidget {
7066
let label = Label::new(None);
7167
label.set_use_markup(true);
7268

73-
label.set_angle(self.orientation.to_angle());
69+
if !context.is_popup {
70+
label.set_angle(self.layout.angle(context.info));
71+
}
7472

7573
button.add(&label);
7674

0 commit comments

Comments
 (0)