1
0
Fork 0
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:
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 ] $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
} }

View file

@ -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"
}, },

View file

@ -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(&center)); 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.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);
} }

View file

@ -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>>,
} }

View file

@ -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)
}); });
} }

View file

@ -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();

View file

@ -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");

View file

@ -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,

View file

@ -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,
} }

View file

@ -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();

View file

@ -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();

View file

@ -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);
} }

View file

@ -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();

View file

@ -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,
}