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 ]
|
||||
}
|
||||
in {
|
||||
left = $left right = $right
|
||||
anchor_to_edges = true
|
||||
position = "top"
|
||||
start = $left end = $right
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
|
|
75
src/bar.rs
75
src/bar.rs
|
@ -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(¢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.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);
|
||||
}
|
||||
|
|
|
@ -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>>,
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,7 +209,10 @@ impl ItemButton {
|
|||
)))
|
||||
.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(
|
||||
button,
|
||||
orientation,
|
||||
)))
|
||||
.expect("Failed to send item open popup event");
|
||||
} else {
|
||||
tx.try_send(ModuleUpdateEvent::ClosePopup)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
147
src/popup.rs
147
src/popup.rs
|
@ -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,
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue