mirror of
https://github.com/Zedfrigg/ironbar.git
synced 2025-04-19 19:34: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:
parent
1b853bcb71
commit
06cfad62e2
14 changed files with 254 additions and 83 deletions
|
@ -39,5 +39,7 @@ let {
|
||||||
$right = [ $mpd_local $mpd_server $phone_battery $sys_info $clock ]
|
$right = [ $mpd_local $mpd_server $phone_battery $sys_info $clock ]
|
||||||
}
|
}
|
||||||
in {
|
in {
|
||||||
left = $left right = $right
|
anchor_to_edges = true
|
||||||
|
position = "top"
|
||||||
|
start = $left end = $right
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,20 @@
|
||||||
{
|
{
|
||||||
"left": [
|
"start": [
|
||||||
{
|
{
|
||||||
"type": "workspaces"
|
"type": "workspaces"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "launcher",
|
"type": "launcher",
|
||||||
"icon_theme": "Paper",
|
"icon_theme": "Paper",
|
||||||
"favorites": ["firefox", "discord", "Steam"],
|
"favorites": [
|
||||||
|
"firefox",
|
||||||
|
"discord",
|
||||||
|
"Steam"
|
||||||
|
],
|
||||||
"show_names": false
|
"show_names": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"right": [
|
"end": [
|
||||||
{
|
{
|
||||||
"type": "mpd"
|
"type": "mpd"
|
||||||
},
|
},
|
||||||
|
|
75
src/bar.rs
75
src/bar.rs
|
@ -28,30 +28,42 @@ pub fn create_bar(
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let win = ApplicationWindow::builder().application(app).build();
|
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()
|
let content = gtk::Box::builder()
|
||||||
.orientation(Orientation::Horizontal)
|
.orientation(config.position.get_orientation())
|
||||||
.spacing(0)
|
.spacing(0)
|
||||||
.hexpand(false)
|
.hexpand(false)
|
||||||
.height_request(config.height)
|
.height_request(config.height)
|
||||||
.name("bar")
|
.name("bar")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let left = gtk::Box::builder().spacing(0).name("left").build();
|
let start = gtk::Box::builder()
|
||||||
let center = gtk::Box::builder().spacing(0).name("center").build();
|
.orientation(config.position.get_orientation())
|
||||||
let right = gtk::Box::builder().spacing(0).name("right").build();
|
.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");
|
content.style_context().add_class("container");
|
||||||
left.style_context().add_class("container");
|
start.style_context().add_class("container");
|
||||||
center.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(¢er));
|
content.set_center_widget(Some(¢er));
|
||||||
content.pack_end(&right, false, false, 0);
|
content.pack_end(&end, false, false, 0);
|
||||||
|
|
||||||
load_modules(&left, ¢er, &right, app, config, monitor, monitor_name)?;
|
load_modules(&start, ¢er, &end, app, config, monitor, monitor_name)?;
|
||||||
win.add(&content);
|
win.add(&content);
|
||||||
|
|
||||||
win.connect_destroy_event(|_, _| {
|
win.connect_destroy_event(|_, _| {
|
||||||
|
@ -79,11 +91,11 @@ fn load_modules(
|
||||||
let mut info_builder = ModuleInfoBuilder::default();
|
let mut info_builder = ModuleInfoBuilder::default();
|
||||||
let info_builder = info_builder
|
let info_builder = info_builder
|
||||||
.app(app)
|
.app(app)
|
||||||
.bar_position(&config.position)
|
.bar_position(config.position)
|
||||||
.monitor(monitor)
|
.monitor(monitor)
|
||||||
.output_name(output_name);
|
.output_name(output_name);
|
||||||
|
|
||||||
if let Some(modules) = config.left {
|
if let Some(modules) = config.start {
|
||||||
let info_builder = info_builder.location(ModuleLocation::Left);
|
let info_builder = info_builder.location(ModuleLocation::Left);
|
||||||
|
|
||||||
add_modules(left, modules, info_builder)?;
|
add_modules(left, modules, info_builder)?;
|
||||||
|
@ -95,7 +107,7 @@ fn load_modules(
|
||||||
add_modules(center, modules, info_builder)?;
|
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);
|
let info_builder = info_builder.location(ModuleLocation::Right);
|
||||||
|
|
||||||
add_modules(right, modules, info_builder)?;
|
add_modules(right, modules, info_builder)?;
|
||||||
|
@ -160,23 +172,23 @@ fn add_modules(
|
||||||
|
|
||||||
w_tx.send(update).expect("Failed to send update to module");
|
w_tx.send(update).expect("Failed to send update to module");
|
||||||
}
|
}
|
||||||
ModuleUpdateEvent::TogglePopup((x, w)) => {
|
ModuleUpdateEvent::TogglePopup(geometry) => {
|
||||||
debug!("Toggling popup for {} [#{}]", $name, $id);
|
debug!("Toggling popup for {} [#{}]", $name, $id);
|
||||||
let popup = popup.read().expect("Failed to get read lock on popup");
|
let popup = popup.read().expect("Failed to get read lock on popup");
|
||||||
if popup.is_visible() {
|
if popup.is_visible() {
|
||||||
popup.hide()
|
popup.hide()
|
||||||
} else {
|
} else {
|
||||||
popup.show_content($id);
|
popup.show_content($id);
|
||||||
popup.show(x, w);
|
popup.show(geometry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ModuleUpdateEvent::OpenPopup((x, w)) => {
|
ModuleUpdateEvent::OpenPopup(geometry) => {
|
||||||
debug!("Opening popup for {} [#{}]", $name, $id);
|
debug!("Opening popup for {} [#{}]", $name, $id);
|
||||||
|
|
||||||
let popup = popup.read().expect("Failed to get read lock on popup");
|
let popup = popup.read().expect("Failed to get read lock on popup");
|
||||||
popup.hide();
|
popup.hide();
|
||||||
popup.show_content($id);
|
popup.show_content($id);
|
||||||
popup.show(x, w);
|
popup.show(geometry);
|
||||||
}
|
}
|
||||||
ModuleUpdateEvent::ClosePopup => {
|
ModuleUpdateEvent::ClosePopup => {
|
||||||
debug!("Closing popup for {} [#{}]", $name, $id);
|
debug!("Closing popup for {} [#{}]", $name, $id);
|
||||||
|
@ -224,7 +236,12 @@ fn add_modules(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets up GTK layer shell for a provided application window.
|
/// 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::init_for_window(win);
|
||||||
gtk_layer_shell::set_monitor(win, monitor);
|
gtk_layer_shell::set_monitor(win, monitor);
|
||||||
gtk_layer_shell::set_layer(win, gtk_layer_shell::Layer::Top);
|
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::Left, 0);
|
||||||
gtk_layer_shell::set_margin(win, gtk_layer_shell::Edge::Right, 0);
|
gtk_layer_shell::set_margin(win, gtk_layer_shell::Edge::Right, 0);
|
||||||
|
|
||||||
|
let bar_orientation = position.get_orientation();
|
||||||
|
|
||||||
gtk_layer_shell::set_anchor(
|
gtk_layer_shell::set_anchor(
|
||||||
win,
|
win,
|
||||||
gtk_layer_shell::Edge::Top,
|
gtk_layer_shell::Edge::Top,
|
||||||
position == &BarPosition::Top,
|
position == BarPosition::Top
|
||||||
|
|| (bar_orientation == Orientation::Vertical && anchor_to_edges),
|
||||||
);
|
);
|
||||||
gtk_layer_shell::set_anchor(
|
gtk_layer_shell::set_anchor(
|
||||||
win,
|
win,
|
||||||
gtk_layer_shell::Edge::Bottom,
|
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);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ use color_eyre::eyre::{Context, ContextCompat};
|
||||||
use color_eyre::{eyre, Help, Report};
|
use color_eyre::{eyre, Help, Report};
|
||||||
use dirs::config_dir;
|
use dirs::config_dir;
|
||||||
use eyre::Result;
|
use eyre::Result;
|
||||||
|
use gtk::Orientation;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
@ -35,11 +36,13 @@ pub enum MonitorConfig {
|
||||||
Multiple(Vec<Config>),
|
Multiple(Vec<Config>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
|
#[derive(Debug, Deserialize, Copy, Clone, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub enum BarPosition {
|
pub enum BarPosition {
|
||||||
Top,
|
Top,
|
||||||
Bottom,
|
Bottom,
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for BarPosition {
|
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)]
|
#[derive(Debug, Deserialize, Clone, Default)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
#[serde(default = "default_bar_position")]
|
#[serde(default = "default_bar_position")]
|
||||||
pub position: BarPosition,
|
pub position: BarPosition,
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub anchor_to_edges: bool,
|
||||||
#[serde(default = "default_bar_height")]
|
#[serde(default = "default_bar_height")]
|
||||||
pub height: i32,
|
pub height: i32,
|
||||||
|
|
||||||
pub left: Option<Vec<ModuleConfig>>,
|
pub start: Option<Vec<ModuleConfig>>,
|
||||||
pub center: Option<Vec<ModuleConfig>>,
|
pub center: Option<Vec<ModuleConfig>>,
|
||||||
pub right: Option<Vec<ModuleConfig>>,
|
pub end: Option<Vec<ModuleConfig>>,
|
||||||
|
|
||||||
pub monitors: Option<HashMap<String, MonitorConfig>>,
|
pub monitors: Option<HashMap<String, MonitorConfig>>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,23 +51,29 @@ impl Module<Button> for ClockModule {
|
||||||
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<ModuleWidget<Button>> {
|
) -> Result<ModuleWidget<Button>> {
|
||||||
let button = Button::new();
|
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| {
|
button.connect_clicked(move |button| {
|
||||||
context
|
context
|
||||||
.tx
|
.tx
|
||||||
.try_send(ModuleUpdateEvent::TogglePopup(Popup::button_pos(button)))
|
.try_send(ModuleUpdateEvent::TogglePopup(Popup::button_pos(
|
||||||
|
button,
|
||||||
|
orientation,
|
||||||
|
)))
|
||||||
.expect("Failed to toggle popup");
|
.expect("Failed to toggle popup");
|
||||||
});
|
});
|
||||||
|
|
||||||
let format = self.format.clone();
|
let format = self.format.clone();
|
||||||
{
|
{
|
||||||
let button = button.clone();
|
|
||||||
context.widget_rx.attach(None, move |date| {
|
context.widget_rx.attach(None, move |date| {
|
||||||
let date_string = format!("{}", date.format(&format));
|
let date_string = format!("{}", date.format(&format));
|
||||||
button.set_label(&date_string);
|
label.set_label(&date_string);
|
||||||
Continue(true)
|
Continue(true)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ use crate::{await_sync, icon, wayland};
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use glib::Continue;
|
use glib::Continue;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{IconTheme, Image, Label, Orientation};
|
use gtk::{IconTheme, Image, Label};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tokio::spawn;
|
use tokio::spawn;
|
||||||
use tokio::sync::mpsc::{Receiver, Sender};
|
use tokio::sync::mpsc::{Receiver, Sender};
|
||||||
|
@ -84,7 +84,7 @@ impl Module<gtk::Box> for FocusedModule {
|
||||||
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<ModuleWidget<gtk::Box>> {
|
) -> Result<ModuleWidget<gtk::Box>> {
|
||||||
let icon_theme = IconTheme::new();
|
let icon_theme = IconTheme::new();
|
||||||
|
|
||||||
|
@ -92,7 +92,7 @@ impl Module<gtk::Box> for FocusedModule {
|
||||||
icon_theme.set_custom_theme(Some(&theme));
|
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 icon = Image::builder().name("icon").build();
|
||||||
let label = Label::builder().name("label").build();
|
let label = Label::builder().name("label").build();
|
||||||
|
|
|
@ -6,7 +6,7 @@ use crate::modules::ModuleUpdateEvent;
|
||||||
use crate::popup::Popup;
|
use crate::popup::Popup;
|
||||||
use crate::wayland::ToplevelInfo;
|
use crate::wayland::ToplevelInfo;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{Button, IconTheme, Image};
|
use gtk::{Button, IconTheme, Image, Orientation};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
use tokio::sync::mpsc::Sender;
|
use tokio::sync::mpsc::Sender;
|
||||||
|
@ -139,6 +139,7 @@ impl ItemButton {
|
||||||
item: &Item,
|
item: &Item,
|
||||||
show_names: bool,
|
show_names: bool,
|
||||||
show_icons: bool,
|
show_icons: bool,
|
||||||
|
orientation: Orientation,
|
||||||
icon_theme: &IconTheme,
|
icon_theme: &IconTheme,
|
||||||
tx: &Sender<ModuleUpdateEvent<LauncherUpdate>>,
|
tx: &Sender<ModuleUpdateEvent<LauncherUpdate>>,
|
||||||
controller_tx: &Sender<ItemEvent>,
|
controller_tx: &Sender<ItemEvent>,
|
||||||
|
@ -208,8 +209,11 @@ impl ItemButton {
|
||||||
)))
|
)))
|
||||||
.expect("Failed to send item open popup event");
|
.expect("Failed to send item open popup event");
|
||||||
|
|
||||||
tx.try_send(ModuleUpdateEvent::OpenPopup(Popup::button_pos(button)))
|
tx.try_send(ModuleUpdateEvent::OpenPopup(Popup::button_pos(
|
||||||
.expect("Failed to send item open popup event");
|
button,
|
||||||
|
orientation,
|
||||||
|
)))
|
||||||
|
.expect("Failed to send item open popup event");
|
||||||
} else {
|
} else {
|
||||||
tx.try_send(ModuleUpdateEvent::ClosePopup)
|
tx.try_send(ModuleUpdateEvent::ClosePopup)
|
||||||
.expect("Failed to send item close popup event");
|
.expect("Failed to send item close popup event");
|
||||||
|
|
|
@ -309,20 +309,21 @@ impl Module<gtk::Box> for LauncherModule {
|
||||||
fn into_widget(
|
fn into_widget(
|
||||||
self,
|
self,
|
||||||
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||||
_info: &ModuleInfo,
|
info: &ModuleInfo,
|
||||||
) -> crate::Result<ModuleWidget<gtk::Box>> {
|
) -> crate::Result<ModuleWidget<gtk::Box>> {
|
||||||
let icon_theme = IconTheme::new();
|
let icon_theme = IconTheme::new();
|
||||||
if let Some(ref theme) = self.icon_theme {
|
if let Some(ref theme) = self.icon_theme {
|
||||||
icon_theme.set_custom_theme(Some(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 container = container.clone();
|
||||||
|
|
||||||
let show_names = self.show_names;
|
let show_names = self.show_names;
|
||||||
let show_icons = self.show_icons;
|
let show_icons = self.show_icons;
|
||||||
|
let orientation = info.bar_position.get_orientation();
|
||||||
|
|
||||||
let mut buttons = Collection::<String, ItemButton>::new();
|
let mut buttons = Collection::<String, ItemButton>::new();
|
||||||
|
|
||||||
|
@ -339,6 +340,7 @@ impl Module<gtk::Box> for LauncherModule {
|
||||||
&item,
|
&item,
|
||||||
show_names,
|
show_names,
|
||||||
show_icons,
|
show_icons,
|
||||||
|
orientation,
|
||||||
&icon_theme,
|
&icon_theme,
|
||||||
&context.tx,
|
&context.tx,
|
||||||
&controller_tx2,
|
&controller_tx2,
|
||||||
|
|
|
@ -14,6 +14,7 @@ pub mod tray;
|
||||||
pub mod workspaces;
|
pub mod workspaces;
|
||||||
|
|
||||||
use crate::config::BarPosition;
|
use crate::config::BarPosition;
|
||||||
|
use crate::popup::ButtonGeometry;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use derive_builder::Builder;
|
use derive_builder::Builder;
|
||||||
use glib::IsA;
|
use glib::IsA;
|
||||||
|
@ -32,7 +33,7 @@ pub enum ModuleLocation {
|
||||||
pub struct ModuleInfo<'a> {
|
pub struct ModuleInfo<'a> {
|
||||||
pub app: &'a Application,
|
pub app: &'a Application,
|
||||||
pub location: ModuleLocation,
|
pub location: ModuleLocation,
|
||||||
pub bar_position: &'a BarPosition,
|
pub bar_position: BarPosition,
|
||||||
pub monitor: &'a Monitor,
|
pub monitor: &'a Monitor,
|
||||||
pub output_name: &'a str,
|
pub output_name: &'a str,
|
||||||
pub module_name: &'a str,
|
pub module_name: &'a str,
|
||||||
|
@ -43,11 +44,10 @@ pub enum ModuleUpdateEvent<T> {
|
||||||
/// Sends an update to the module UI
|
/// Sends an update to the module UI
|
||||||
Update(T),
|
Update(T),
|
||||||
/// Toggles the open state of the popup.
|
/// Toggles the open state of the popup.
|
||||||
/// Takes the button X position and width.
|
TogglePopup(ButtonGeometry),
|
||||||
TogglePopup((i32, i32)),
|
|
||||||
/// Force sets the popup open.
|
/// Force sets the popup open.
|
||||||
/// Takes the button X position and width.
|
/// Takes the button X position and width.
|
||||||
OpenPopup((i32, i32)),
|
OpenPopup(ButtonGeometry),
|
||||||
/// Force sets the popup closed.
|
/// Force sets the popup closed.
|
||||||
ClosePopup,
|
ClosePopup,
|
||||||
}
|
}
|
||||||
|
|
|
@ -202,14 +202,22 @@ impl Module<Button> for MpdModule {
|
||||||
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<ModuleWidget<Button>> {
|
) -> Result<ModuleWidget<Button>> {
|
||||||
let button = Button::new();
|
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| {
|
button.connect_clicked(move |button| {
|
||||||
context
|
context
|
||||||
.tx
|
.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");
|
.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| {
|
context.widget_rx.attach(None, move |mut event| {
|
||||||
if let Some(event) = event.take() {
|
if let Some(event) = event.take() {
|
||||||
button.set_label(&event.display_string);
|
label.set_label(&event.display_string);
|
||||||
button.show();
|
button.show();
|
||||||
} else {
|
} else {
|
||||||
button.hide();
|
button.hide();
|
||||||
|
|
|
@ -55,9 +55,10 @@ impl Module<Label> for ScriptModule {
|
||||||
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<ModuleWidget<Label>> {
|
) -> Result<ModuleWidget<Label>> {
|
||||||
let label = Label::builder().use_markup(true).build();
|
let label = Label::builder().use_markup(true).build();
|
||||||
|
label.set_angle(info.bar_position.get_angle());
|
||||||
|
|
||||||
{
|
{
|
||||||
let label = label.clone();
|
let label = label.clone();
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
|
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{Label, Orientation};
|
use gtk::Label;
|
||||||
use regex::{Captures, Regex};
|
use regex::{Captures, Regex};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
@ -64,16 +64,17 @@ 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<ModuleWidget<gtk::Box>> {
|
) -> Result<ModuleWidget<gtk::Box>> {
|
||||||
let re = Regex::new(r"\{([\w-]+)}")?;
|
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();
|
let mut labels = Vec::new();
|
||||||
|
|
||||||
for format in &self.format {
|
for format in &self.format {
|
||||||
let label = Label::builder().label(format).name("item").build();
|
let label = Label::builder().label(format).name("item").build();
|
||||||
|
label.set_angle(info.bar_position.get_angle());
|
||||||
container.add(&label);
|
container.add(&label);
|
||||||
labels.push(label);
|
labels.push(label);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, Widget
|
||||||
use crate::sway::{get_client, get_sub_client};
|
use crate::sway::{get_client, get_sub_client};
|
||||||
use color_eyre::{Report, Result};
|
use color_eyre::{Report, Result};
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{Button, Orientation};
|
use gtk::Button;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use swayipc_async::{Workspace, WorkspaceChange, WorkspaceEvent};
|
use swayipc_async::{Workspace, WorkspaceChange, WorkspaceEvent};
|
||||||
|
@ -125,7 +125,7 @@ impl Module<gtk::Box> for WorkspacesModule {
|
||||||
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||||
info: &ModuleInfo,
|
info: &ModuleInfo,
|
||||||
) -> Result<ModuleWidget<gtk::Box>> {
|
) -> 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();
|
let name_map = self.name_map.unwrap_or_default();
|
||||||
|
|
||||||
|
|
147
src/popup.rs
147
src/popup.rs
|
@ -4,7 +4,7 @@ use crate::config::BarPosition;
|
||||||
use crate::modules::ModuleInfo;
|
use crate::modules::ModuleInfo;
|
||||||
use gtk::gdk::Monitor;
|
use gtk::gdk::Monitor;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{ApplicationWindow, Button};
|
use gtk::{ApplicationWindow, Button, Orientation};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -12,6 +12,7 @@ pub struct Popup {
|
||||||
pub window: ApplicationWindow,
|
pub window: ApplicationWindow,
|
||||||
pub cache: HashMap<usize, gtk::Box>,
|
pub cache: HashMap<usize, gtk::Box>,
|
||||||
monitor: Monitor,
|
monitor: Monitor,
|
||||||
|
pos: BarPosition,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Popup {
|
impl Popup {
|
||||||
|
@ -20,6 +21,8 @@ impl Popup {
|
||||||
/// and an empty `gtk::Box` container.
|
/// and an empty `gtk::Box` container.
|
||||||
pub fn new(module_info: &ModuleInfo) -> Self {
|
pub fn new(module_info: &ModuleInfo) -> Self {
|
||||||
let pos = module_info.bar_position;
|
let pos = module_info.bar_position;
|
||||||
|
let orientation = pos.get_orientation();
|
||||||
|
|
||||||
let win = ApplicationWindow::builder()
|
let win = ApplicationWindow::builder()
|
||||||
.application(module_info.app)
|
.application(module_info.app)
|
||||||
.build();
|
.build();
|
||||||
|
@ -30,34 +33,69 @@ impl Popup {
|
||||||
gtk_layer_shell::set_margin(
|
gtk_layer_shell::set_margin(
|
||||||
&win,
|
&win,
|
||||||
gtk_layer_shell::Edge::Top,
|
gtk_layer_shell::Edge::Top,
|
||||||
if pos == &BarPosition::Top { 5 } else { 0 },
|
if pos == BarPosition::Top { 5 } else { 0 },
|
||||||
);
|
);
|
||||||
gtk_layer_shell::set_margin(
|
gtk_layer_shell::set_margin(
|
||||||
&win,
|
&win,
|
||||||
gtk_layer_shell::Edge::Bottom,
|
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(
|
gtk_layer_shell::set_anchor(
|
||||||
&win,
|
&win,
|
||||||
gtk_layer_shell::Edge::Bottom,
|
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;
|
const THRESHOLD: f64 = 3.0;
|
||||||
|
|
||||||
let (w, _h) = win.size();
|
let (w, h) = win.size();
|
||||||
let (x, y) = ev.position();
|
let (x, y) = ev.position();
|
||||||
|
|
||||||
// some child widgets trigger this event
|
// some child widgets trigger this event
|
||||||
// so check we're actually outside the window
|
// 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();
|
win.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,6 +106,7 @@ impl Popup {
|
||||||
window: win,
|
window: win,
|
||||||
cache: HashMap::new(),
|
cache: HashMap::new(),
|
||||||
monitor: module_info.monitor.clone(),
|
monitor: module_info.monitor.clone(),
|
||||||
|
pos,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,10 +131,10 @@ impl Popup {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shows the popover
|
/// Shows the popup
|
||||||
pub fn show(&self, button_x: i32, button_width: i32) {
|
pub fn show(&self, geometry: ButtonGeometry) {
|
||||||
self.window.show_all();
|
self.window.show_all();
|
||||||
self.set_pos(button_x, button_width);
|
self.set_pos(geometry);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Hides the popover
|
/// Hides the popover
|
||||||
|
@ -108,34 +147,84 @@ impl Popup {
|
||||||
self.window.is_visible()
|
self.window.is_visible()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the popover's X position relative to the left border of the screen
|
/// Sets the popup's X/Y position relative to the left or border of the screen
|
||||||
fn set_pos(&self, button_x: i32, button_width: i32) {
|
/// (depending on orientation).
|
||||||
let screen_width = self.monitor.workarea().width();
|
fn set_pos(&self, geometry: ButtonGeometry) {
|
||||||
let (popup_width, _popup_height) = self.window.size();
|
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 {
|
if offset < 5.0 {
|
||||||
offset = 5.0;
|
offset = 5.0;
|
||||||
} else if offset > f64::from(screen_width - popup_width) - 5.0 {
|
} else if offset > f64::from(screen_size - popup_size) - 5.0 {
|
||||||
offset = f64::from(screen_width - popup_width) - 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
|
/// Gets the absolute X position of the button
|
||||||
/// and its width.
|
/// and its width / height (depending on orientation).
|
||||||
pub fn button_pos(button: &Button) -> (i32, i32) {
|
pub fn button_pos(button: &Button, orientation: Orientation) -> ButtonGeometry {
|
||||||
let button_width = button.allocation().width();
|
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 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)
|
.translate_coordinates(&top_level, 0, 0)
|
||||||
.unwrap_or((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,
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue