1
0
Fork 0
mirror of https://github.com/Zedfrigg/ironbar.git synced 2025-04-19 11:24:24 +02:00

feat: more positioning options (#23)

* feat: more positioning options

Can now display the bar on the left/right, and avoid anchoring to edges to centre the bar.

BREAKING CHANGE: The `left` and `right` config options have been renamed to `start` and `end`
This commit is contained in:
Jake Stanger 2022-10-15 16:27:25 +01:00 committed by GitHub
parent 1b853bcb71
commit 06cfad62e2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 254 additions and 83 deletions

View file

@ -39,5 +39,7 @@ let {
$right = [ $mpd_local $mpd_server $phone_battery $sys_info $clock ]
}
in {
left = $left right = $right
anchor_to_edges = true
position = "top"
start = $left end = $right
}

View file

@ -1,16 +1,20 @@
{
"left": [
"start": [
{
"type": "workspaces"
},
{
"type": "launcher",
"icon_theme": "Paper",
"favorites": ["firefox", "discord", "Steam"],
"favorites": [
"firefox",
"discord",
"Steam"
],
"show_names": false
}
],
"right": [
"end": [
{
"type": "mpd"
},

View file

@ -28,30 +28,42 @@ pub fn create_bar(
) -> Result<()> {
let win = ApplicationWindow::builder().application(app).build();
setup_layer_shell(&win, monitor, &config.position);
setup_layer_shell(&win, monitor, config.position, config.anchor_to_edges);
let content = gtk::Box::builder()
.orientation(Orientation::Horizontal)
.orientation(config.position.get_orientation())
.spacing(0)
.hexpand(false)
.height_request(config.height)
.name("bar")
.build();
let left = gtk::Box::builder().spacing(0).name("left").build();
let center = gtk::Box::builder().spacing(0).name("center").build();
let right = gtk::Box::builder().spacing(0).name("right").build();
let start = gtk::Box::builder()
.orientation(config.position.get_orientation())
.spacing(0)
.name("start")
.build();
let center = gtk::Box::builder()
.orientation(config.position.get_orientation())
.spacing(0)
.name("center")
.build();
let end = gtk::Box::builder()
.orientation(config.position.get_orientation())
.spacing(0)
.name("end")
.build();
content.style_context().add_class("container");
left.style_context().add_class("container");
start.style_context().add_class("container");
center.style_context().add_class("container");
right.style_context().add_class("container");
end.style_context().add_class("container");
content.add(&left);
content.add(&start);
content.set_center_widget(Some(&center));
content.pack_end(&right, false, false, 0);
content.pack_end(&end, false, false, 0);
load_modules(&left, &center, &right, app, config, monitor, monitor_name)?;
load_modules(&start, &center, &end, app, config, monitor, monitor_name)?;
win.add(&content);
win.connect_destroy_event(|_, _| {
@ -79,11 +91,11 @@ fn load_modules(
let mut info_builder = ModuleInfoBuilder::default();
let info_builder = info_builder
.app(app)
.bar_position(&config.position)
.bar_position(config.position)
.monitor(monitor)
.output_name(output_name);
if let Some(modules) = config.left {
if let Some(modules) = config.start {
let info_builder = info_builder.location(ModuleLocation::Left);
add_modules(left, modules, info_builder)?;
@ -95,7 +107,7 @@ fn load_modules(
add_modules(center, modules, info_builder)?;
}
if let Some(modules) = config.right {
if let Some(modules) = config.end {
let info_builder = info_builder.location(ModuleLocation::Right);
add_modules(right, modules, info_builder)?;
@ -160,23 +172,23 @@ fn add_modules(
w_tx.send(update).expect("Failed to send update to module");
}
ModuleUpdateEvent::TogglePopup((x, w)) => {
ModuleUpdateEvent::TogglePopup(geometry) => {
debug!("Toggling popup for {} [#{}]", $name, $id);
let popup = popup.read().expect("Failed to get read lock on popup");
if popup.is_visible() {
popup.hide()
} else {
popup.show_content($id);
popup.show(x, w);
popup.show(geometry);
}
}
ModuleUpdateEvent::OpenPopup((x, w)) => {
ModuleUpdateEvent::OpenPopup(geometry) => {
debug!("Opening popup for {} [#{}]", $name, $id);
let popup = popup.read().expect("Failed to get read lock on popup");
popup.hide();
popup.show_content($id);
popup.show(x, w);
popup.show(geometry);
}
ModuleUpdateEvent::ClosePopup => {
debug!("Closing popup for {} [#{}]", $name, $id);
@ -224,7 +236,12 @@ fn add_modules(
}
/// Sets up GTK layer shell for a provided application window.
fn setup_layer_shell(win: &ApplicationWindow, monitor: &Monitor, position: &BarPosition) {
fn setup_layer_shell(
win: &ApplicationWindow,
monitor: &Monitor,
position: BarPosition,
anchor_to_edges: bool,
) {
gtk_layer_shell::init_for_window(win);
gtk_layer_shell::set_monitor(win, monitor);
gtk_layer_shell::set_layer(win, gtk_layer_shell::Layer::Top);
@ -235,16 +252,30 @@ fn setup_layer_shell(win: &ApplicationWindow, monitor: &Monitor, position: &BarP
gtk_layer_shell::set_margin(win, gtk_layer_shell::Edge::Left, 0);
gtk_layer_shell::set_margin(win, gtk_layer_shell::Edge::Right, 0);
let bar_orientation = position.get_orientation();
gtk_layer_shell::set_anchor(
win,
gtk_layer_shell::Edge::Top,
position == &BarPosition::Top,
position == BarPosition::Top
|| (bar_orientation == Orientation::Vertical && anchor_to_edges),
);
gtk_layer_shell::set_anchor(
win,
gtk_layer_shell::Edge::Bottom,
position == &BarPosition::Bottom,
position == BarPosition::Bottom
|| (bar_orientation == Orientation::Vertical && anchor_to_edges),
);
gtk_layer_shell::set_anchor(
win,
gtk_layer_shell::Edge::Left,
position == BarPosition::Left
|| (bar_orientation == Orientation::Horizontal && anchor_to_edges),
);
gtk_layer_shell::set_anchor(
win,
gtk_layer_shell::Edge::Right,
position == BarPosition::Right
|| (bar_orientation == Orientation::Horizontal && anchor_to_edges),
);
gtk_layer_shell::set_anchor(win, gtk_layer_shell::Edge::Left, true);
gtk_layer_shell::set_anchor(win, gtk_layer_shell::Edge::Right, true);
}

View file

@ -10,6 +10,7 @@ use color_eyre::eyre::{Context, ContextCompat};
use color_eyre::{eyre, Help, Report};
use dirs::config_dir;
use eyre::Result;
use gtk::Orientation;
use serde::Deserialize;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
@ -35,11 +36,13 @@ pub enum MonitorConfig {
Multiple(Vec<Config>),
}
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
#[derive(Debug, Deserialize, Copy, Clone, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum BarPosition {
Top,
Bottom,
Left,
Right,
}
impl Default for BarPosition {
@ -48,16 +51,36 @@ impl Default for BarPosition {
}
}
impl BarPosition {
pub fn get_orientation(self) -> Orientation {
if self == Self::Top || self == Self::Bottom {
Orientation::Horizontal
} else {
Orientation::Vertical
}
}
pub const fn get_angle(self) -> f64 {
match self {
Self::Top | Self::Bottom => 0.0,
Self::Left => 90.0,
Self::Right => 270.0,
}
}
}
#[derive(Debug, Deserialize, Clone, Default)]
pub struct Config {
#[serde(default = "default_bar_position")]
pub position: BarPosition,
#[serde(default = "default_true")]
pub anchor_to_edges: bool,
#[serde(default = "default_bar_height")]
pub height: i32,
pub left: Option<Vec<ModuleConfig>>,
pub start: Option<Vec<ModuleConfig>>,
pub center: Option<Vec<ModuleConfig>>,
pub right: Option<Vec<ModuleConfig>>,
pub end: Option<Vec<ModuleConfig>>,
pub monitors: Option<HashMap<String, MonitorConfig>>,
}

View file

@ -51,23 +51,29 @@ impl Module<Button> for ClockModule {
fn into_widget(
self,
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
_info: &ModuleInfo,
info: &ModuleInfo,
) -> Result<ModuleWidget<Button>> {
let button = Button::new();
let label = Label::new(None);
label.set_angle(info.bar_position.get_angle());
button.add(&label);
let orientation = info.bar_position.get_orientation();
button.connect_clicked(move |button| {
context
.tx
.try_send(ModuleUpdateEvent::TogglePopup(Popup::button_pos(button)))
.try_send(ModuleUpdateEvent::TogglePopup(Popup::button_pos(
button,
orientation,
)))
.expect("Failed to toggle popup");
});
let format = self.format.clone();
{
let button = button.clone();
context.widget_rx.attach(None, move |date| {
let date_string = format!("{}", date.format(&format));
button.set_label(&date_string);
label.set_label(&date_string);
Continue(true)
});
}

View file

@ -4,7 +4,7 @@ use crate::{await_sync, icon, wayland};
use color_eyre::Result;
use glib::Continue;
use gtk::prelude::*;
use gtk::{IconTheme, Image, Label, Orientation};
use gtk::{IconTheme, Image, Label};
use serde::Deserialize;
use tokio::spawn;
use tokio::sync::mpsc::{Receiver, Sender};
@ -84,7 +84,7 @@ impl Module<gtk::Box> for FocusedModule {
fn into_widget(
self,
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
_info: &ModuleInfo,
info: &ModuleInfo,
) -> Result<ModuleWidget<gtk::Box>> {
let icon_theme = IconTheme::new();
@ -92,7 +92,7 @@ impl Module<gtk::Box> for FocusedModule {
icon_theme.set_custom_theme(Some(&theme));
}
let container = gtk::Box::new(Orientation::Horizontal, 5);
let container = gtk::Box::new(info.bar_position.get_orientation(), 5);
let icon = Image::builder().name("icon").build();
let label = Label::builder().name("label").build();

View file

@ -6,7 +6,7 @@ use crate::modules::ModuleUpdateEvent;
use crate::popup::Popup;
use crate::wayland::ToplevelInfo;
use gtk::prelude::*;
use gtk::{Button, IconTheme, Image};
use gtk::{Button, IconTheme, Image, Orientation};
use std::rc::Rc;
use std::sync::RwLock;
use tokio::sync::mpsc::Sender;
@ -139,6 +139,7 @@ impl ItemButton {
item: &Item,
show_names: bool,
show_icons: bool,
orientation: Orientation,
icon_theme: &IconTheme,
tx: &Sender<ModuleUpdateEvent<LauncherUpdate>>,
controller_tx: &Sender<ItemEvent>,
@ -208,8 +209,11 @@ impl ItemButton {
)))
.expect("Failed to send item open popup event");
tx.try_send(ModuleUpdateEvent::OpenPopup(Popup::button_pos(button)))
.expect("Failed to send item open popup event");
tx.try_send(ModuleUpdateEvent::OpenPopup(Popup::button_pos(
button,
orientation,
)))
.expect("Failed to send item open popup event");
} else {
tx.try_send(ModuleUpdateEvent::ClosePopup)
.expect("Failed to send item close popup event");

View file

@ -309,20 +309,21 @@ impl Module<gtk::Box> for LauncherModule {
fn into_widget(
self,
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
_info: &ModuleInfo,
info: &ModuleInfo,
) -> crate::Result<ModuleWidget<gtk::Box>> {
let icon_theme = IconTheme::new();
if let Some(ref theme) = self.icon_theme {
icon_theme.set_custom_theme(Some(theme));
}
let container = gtk::Box::new(Orientation::Horizontal, 0);
let container = gtk::Box::new(info.bar_position.get_orientation(), 0);
{
let container = container.clone();
let show_names = self.show_names;
let show_icons = self.show_icons;
let orientation = info.bar_position.get_orientation();
let mut buttons = Collection::<String, ItemButton>::new();
@ -339,6 +340,7 @@ impl Module<gtk::Box> for LauncherModule {
&item,
show_names,
show_icons,
orientation,
&icon_theme,
&context.tx,
&controller_tx2,

View file

@ -14,6 +14,7 @@ pub mod tray;
pub mod workspaces;
use crate::config::BarPosition;
use crate::popup::ButtonGeometry;
use color_eyre::Result;
use derive_builder::Builder;
use glib::IsA;
@ -32,7 +33,7 @@ pub enum ModuleLocation {
pub struct ModuleInfo<'a> {
pub app: &'a Application,
pub location: ModuleLocation,
pub bar_position: &'a BarPosition,
pub bar_position: BarPosition,
pub monitor: &'a Monitor,
pub output_name: &'a str,
pub module_name: &'a str,
@ -43,11 +44,10 @@ pub enum ModuleUpdateEvent<T> {
/// Sends an update to the module UI
Update(T),
/// Toggles the open state of the popup.
/// Takes the button X position and width.
TogglePopup((i32, i32)),
TogglePopup(ButtonGeometry),
/// Force sets the popup open.
/// Takes the button X position and width.
OpenPopup((i32, i32)),
OpenPopup(ButtonGeometry),
/// Force sets the popup closed.
ClosePopup,
}

View file

@ -202,14 +202,22 @@ impl Module<Button> for MpdModule {
fn into_widget(
self,
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
_info: &ModuleInfo,
info: &ModuleInfo,
) -> Result<ModuleWidget<Button>> {
let button = Button::new();
let label = Label::new(None);
label.set_angle(info.bar_position.get_angle());
button.add(&label);
let orientation = info.bar_position.get_orientation();
button.connect_clicked(move |button| {
context
.tx
.try_send(ModuleUpdateEvent::TogglePopup(Popup::button_pos(button)))
.try_send(ModuleUpdateEvent::TogglePopup(Popup::button_pos(
button,
orientation,
)))
.expect("Failed to send MPD popup open event");
});
@ -218,7 +226,7 @@ impl Module<Button> for MpdModule {
context.widget_rx.attach(None, move |mut event| {
if let Some(event) = event.take() {
button.set_label(&event.display_string);
label.set_label(&event.display_string);
button.show();
} else {
button.hide();

View file

@ -55,9 +55,10 @@ impl Module<Label> for ScriptModule {
fn into_widget(
self,
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
_info: &ModuleInfo,
info: &ModuleInfo,
) -> Result<ModuleWidget<Label>> {
let label = Label::builder().use_markup(true).build();
label.set_angle(info.bar_position.get_angle());
{
let label = label.clone();

View file

@ -1,7 +1,7 @@
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
use color_eyre::Result;
use gtk::prelude::*;
use gtk::{Label, Orientation};
use gtk::Label;
use regex::{Captures, Regex};
use serde::Deserialize;
use std::collections::HashMap;
@ -64,16 +64,17 @@ impl Module<gtk::Box> for SysInfoModule {
fn into_widget(
self,
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
_info: &ModuleInfo,
info: &ModuleInfo,
) -> Result<ModuleWidget<gtk::Box>> {
let re = Regex::new(r"\{([\w-]+)}")?;
let container = gtk::Box::new(Orientation::Horizontal, 10);
let container = gtk::Box::new(info.bar_position.get_orientation(), 10);
let mut labels = Vec::new();
for format in &self.format {
let label = Label::builder().label(format).name("item").build();
label.set_angle(info.bar_position.get_angle());
container.add(&label);
labels.push(label);
}

View file

@ -3,7 +3,7 @@ use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, Widget
use crate::sway::{get_client, get_sub_client};
use color_eyre::{Report, Result};
use gtk::prelude::*;
use gtk::{Button, Orientation};
use gtk::Button;
use serde::Deserialize;
use std::collections::HashMap;
use swayipc_async::{Workspace, WorkspaceChange, WorkspaceEvent};
@ -125,7 +125,7 @@ impl Module<gtk::Box> for WorkspacesModule {
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
info: &ModuleInfo,
) -> Result<ModuleWidget<gtk::Box>> {
let container = gtk::Box::new(Orientation::Horizontal, 0);
let container = gtk::Box::new(info.bar_position.get_orientation(), 0);
let name_map = self.name_map.unwrap_or_default();

View file

@ -4,7 +4,7 @@ use crate::config::BarPosition;
use crate::modules::ModuleInfo;
use gtk::gdk::Monitor;
use gtk::prelude::*;
use gtk::{ApplicationWindow, Button};
use gtk::{ApplicationWindow, Button, Orientation};
use tracing::debug;
#[derive(Debug, Clone)]
@ -12,6 +12,7 @@ pub struct Popup {
pub window: ApplicationWindow,
pub cache: HashMap<usize, gtk::Box>,
monitor: Monitor,
pos: BarPosition,
}
impl Popup {
@ -20,6 +21,8 @@ impl Popup {
/// and an empty `gtk::Box` container.
pub fn new(module_info: &ModuleInfo) -> Self {
let pos = module_info.bar_position;
let orientation = pos.get_orientation();
let win = ApplicationWindow::builder()
.application(module_info.app)
.build();
@ -30,34 +33,69 @@ impl Popup {
gtk_layer_shell::set_margin(
&win,
gtk_layer_shell::Edge::Top,
if pos == &BarPosition::Top { 5 } else { 0 },
if pos == BarPosition::Top { 5 } else { 0 },
);
gtk_layer_shell::set_margin(
&win,
gtk_layer_shell::Edge::Bottom,
if pos == &BarPosition::Bottom { 5 } else { 0 },
if pos == BarPosition::Bottom { 5 } else { 0 },
);
gtk_layer_shell::set_margin(
&win,
gtk_layer_shell::Edge::Left,
if pos == BarPosition::Left { 5 } else { 0 },
);
gtk_layer_shell::set_margin(
&win,
gtk_layer_shell::Edge::Right,
if pos == BarPosition::Right { 5 } else { 0 },
);
gtk_layer_shell::set_margin(&win, gtk_layer_shell::Edge::Left, 0);
gtk_layer_shell::set_margin(&win, gtk_layer_shell::Edge::Right, 0);
gtk_layer_shell::set_anchor(&win, gtk_layer_shell::Edge::Top, pos == &BarPosition::Top);
gtk_layer_shell::set_anchor(
&win,
gtk_layer_shell::Edge::Top,
pos == BarPosition::Top || orientation == Orientation::Vertical,
);
gtk_layer_shell::set_anchor(
&win,
gtk_layer_shell::Edge::Bottom,
pos == &BarPosition::Bottom,
pos == BarPosition::Bottom,
);
gtk_layer_shell::set_anchor(
&win,
gtk_layer_shell::Edge::Left,
pos == BarPosition::Left || orientation == Orientation::Horizontal,
);
gtk_layer_shell::set_anchor(
&win,
gtk_layer_shell::Edge::Right,
pos == BarPosition::Right,
);
gtk_layer_shell::set_anchor(&win, gtk_layer_shell::Edge::Left, true);
gtk_layer_shell::set_anchor(&win, gtk_layer_shell::Edge::Right, false);
win.connect_leave_notify_event(|win, ev| {
win.connect_leave_notify_event(move |win, ev| {
const THRESHOLD: f64 = 3.0;
let (w, _h) = win.size();
let (w, h) = win.size();
let (x, y) = ev.position();
// some child widgets trigger this event
// so check we're actually outside the window
if x < THRESHOLD || y < THRESHOLD || x > f64::from(w) - THRESHOLD {
let hide = match pos {
BarPosition::Top => {
x < THRESHOLD || y > f64::from(h) - THRESHOLD || x > f64::from(w) - THRESHOLD
}
BarPosition::Bottom => {
x < THRESHOLD || y < THRESHOLD || x > f64::from(w) - THRESHOLD
}
BarPosition::Left => {
y < THRESHOLD || x > f64::from(w) - THRESHOLD || y > f64::from(h) - THRESHOLD
}
BarPosition::Right => {
y < THRESHOLD || x < THRESHOLD || y > f64::from(h) - THRESHOLD
}
};
if hide {
win.hide();
}
@ -68,6 +106,7 @@ impl Popup {
window: win,
cache: HashMap::new(),
monitor: module_info.monitor.clone(),
pos,
}
}
@ -92,10 +131,10 @@ impl Popup {
}
}
/// Shows the popover
pub fn show(&self, button_x: i32, button_width: i32) {
/// Shows the popup
pub fn show(&self, geometry: ButtonGeometry) {
self.window.show_all();
self.set_pos(button_x, button_width);
self.set_pos(geometry);
}
/// Hides the popover
@ -108,34 +147,84 @@ impl Popup {
self.window.is_visible()
}
/// Sets the popover's X position relative to the left border of the screen
fn set_pos(&self, button_x: i32, button_width: i32) {
let screen_width = self.monitor.workarea().width();
let (popup_width, _popup_height) = self.window.size();
/// Sets the popup's X/Y position relative to the left or border of the screen
/// (depending on orientation).
fn set_pos(&self, geometry: ButtonGeometry) {
let orientation = self.pos.get_orientation();
let widget_center = f64::from(button_x) + f64::from(button_width) / 2.0;
let mon_workarea = self.monitor.workarea();
let screen_size = if orientation == Orientation::Horizontal {
mon_workarea.width()
} else {
mon_workarea.height()
};
let mut offset = (widget_center - (f64::from(popup_width) / 2.0)).round();
let (popup_width, popup_height) = self.window.size();
let popup_size = if orientation == Orientation::Horizontal {
popup_width
} else {
popup_height
};
let widget_center = f64::from(geometry.position) + f64::from(geometry.size) / 2.0;
let bar_offset = (f64::from(screen_size) - f64::from(geometry.bar_size)) / 2.0;
let mut offset = bar_offset + (widget_center - (f64::from(popup_size) / 2.0)).round();
if offset < 5.0 {
offset = 5.0;
} else if offset > f64::from(screen_width - popup_width) - 5.0 {
offset = f64::from(screen_width - popup_width) - 5.0;
} else if offset > f64::from(screen_size - popup_size) - 5.0 {
offset = f64::from(screen_size - popup_size) - 5.0;
}
gtk_layer_shell::set_margin(&self.window, gtk_layer_shell::Edge::Left, offset as i32);
let edge = if orientation == Orientation::Horizontal {
gtk_layer_shell::Edge::Left
} else {
gtk_layer_shell::Edge::Top
};
gtk_layer_shell::set_margin(&self.window, edge, offset as i32);
}
/// Gets the absolute X position of the button
/// and its width.
pub fn button_pos(button: &Button) -> (i32, i32) {
let button_width = button.allocation().width();
/// and its width / height (depending on orientation).
pub fn button_pos(button: &Button, orientation: Orientation) -> ButtonGeometry {
let button_size = if orientation == Orientation::Horizontal {
button.allocation().width()
} else {
button.allocation().height()
};
let top_level = button.toplevel().expect("Failed to get top-level widget");
let (button_x, _) = button
let bar_size = if orientation == Orientation::Horizontal {
top_level.allocation().width()
} else {
top_level.allocation().height()
};
let (button_x, button_y) = button
.translate_coordinates(&top_level, 0, 0)
.unwrap_or((0, 0));
(button_x, button_width)
let button_pos = if orientation == Orientation::Horizontal {
button_x
} else {
button_y
};
ButtonGeometry {
position: button_pos,
size: button_size,
bar_size,
}
}
}
#[derive(Debug, Copy, Clone)]
pub struct ButtonGeometry {
position: i32,
size: i32,
bar_size: i32,
}