2022-09-25 22:49:00 +01:00
|
|
|
use std::collections::HashMap;
|
|
|
|
|
2022-08-14 15:56:21 +01:00
|
|
|
use crate::config::BarPosition;
|
2022-09-25 22:49:00 +01:00
|
|
|
use crate::modules::ModuleInfo;
|
2022-08-15 21:11:00 +01:00
|
|
|
use gtk::gdk::Monitor;
|
2022-08-14 14:30:13 +01:00
|
|
|
use gtk::prelude::*;
|
2023-04-09 22:42:35 +01:00
|
|
|
use gtk::{ApplicationWindow, Orientation};
|
2022-09-25 22:49:00 +01:00
|
|
|
use tracing::debug;
|
2022-08-14 14:30:13 +01:00
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub struct Popup {
|
|
|
|
pub window: ApplicationWindow,
|
2022-09-25 22:49:00 +01:00
|
|
|
pub cache: HashMap<usize, gtk::Box>,
|
2022-08-15 21:11:00 +01:00
|
|
|
monitor: Monitor,
|
2022-10-15 16:27:25 +01:00
|
|
|
pos: BarPosition,
|
2022-08-14 14:30:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Popup {
|
2022-08-28 16:57:41 +01:00
|
|
|
/// Creates a new popup window.
|
|
|
|
/// This includes setting up gtk-layer-shell
|
|
|
|
/// and an empty `gtk::Box` container.
|
2023-04-07 14:36:12 +01:00
|
|
|
pub fn new(module_info: &ModuleInfo, gap: i32) -> Self {
|
2022-09-25 22:49:00 +01:00
|
|
|
let pos = module_info.bar_position;
|
2022-10-15 16:27:25 +01:00
|
|
|
let orientation = pos.get_orientation();
|
|
|
|
|
2022-09-25 22:49:00 +01:00
|
|
|
let win = ApplicationWindow::builder()
|
|
|
|
.application(module_info.app)
|
|
|
|
.build();
|
2022-08-14 14:30:13 +01:00
|
|
|
|
|
|
|
gtk_layer_shell::init_for_window(&win);
|
|
|
|
gtk_layer_shell::set_layer(&win, gtk_layer_shell::Layer::Overlay);
|
2023-02-08 17:30:09 +00:00
|
|
|
gtk_layer_shell::set_namespace(&win, env!("CARGO_PKG_NAME"));
|
2022-08-14 14:30:13 +01:00
|
|
|
|
2022-08-14 15:56:21 +01:00
|
|
|
gtk_layer_shell::set_margin(
|
|
|
|
&win,
|
|
|
|
gtk_layer_shell::Edge::Top,
|
2023-04-07 14:36:12 +01:00
|
|
|
if pos == BarPosition::Top { gap } else { 0 },
|
2022-08-14 15:56:21 +01:00
|
|
|
);
|
|
|
|
gtk_layer_shell::set_margin(
|
|
|
|
&win,
|
|
|
|
gtk_layer_shell::Edge::Bottom,
|
2023-04-07 14:36:12 +01:00
|
|
|
if pos == BarPosition::Bottom { gap } else { 0 },
|
2022-10-15 16:27:25 +01:00
|
|
|
);
|
|
|
|
gtk_layer_shell::set_margin(
|
|
|
|
&win,
|
|
|
|
gtk_layer_shell::Edge::Left,
|
2023-04-07 14:36:12 +01:00
|
|
|
if pos == BarPosition::Left { gap } else { 0 },
|
2022-10-15 16:27:25 +01:00
|
|
|
);
|
|
|
|
gtk_layer_shell::set_margin(
|
|
|
|
&win,
|
|
|
|
gtk_layer_shell::Edge::Right,
|
2023-04-07 14:36:12 +01:00
|
|
|
if pos == BarPosition::Right { gap } else { 0 },
|
2022-08-14 15:56:21 +01:00
|
|
|
);
|
2022-08-14 14:30:13 +01:00
|
|
|
|
2022-10-15 16:27:25 +01:00
|
|
|
gtk_layer_shell::set_anchor(
|
|
|
|
&win,
|
|
|
|
gtk_layer_shell::Edge::Top,
|
|
|
|
pos == BarPosition::Top || orientation == Orientation::Vertical,
|
|
|
|
);
|
2022-08-14 15:56:21 +01:00
|
|
|
gtk_layer_shell::set_anchor(
|
|
|
|
&win,
|
|
|
|
gtk_layer_shell::Edge::Bottom,
|
2022-10-15 16:27:25 +01:00
|
|
|
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,
|
2022-08-14 15:56:21 +01:00
|
|
|
);
|
2022-08-14 14:30:13 +01:00
|
|
|
|
2022-10-15 16:27:25 +01:00
|
|
|
win.connect_leave_notify_event(move |win, ev| {
|
2022-08-15 21:11:00 +01:00
|
|
|
const THRESHOLD: f64 = 3.0;
|
|
|
|
|
2022-10-15 16:27:25 +01:00
|
|
|
let (w, h) = win.size();
|
2022-08-14 14:30:13 +01:00
|
|
|
let (x, y) = ev.position();
|
|
|
|
|
|
|
|
// some child widgets trigger this event
|
|
|
|
// so check we're actually outside the window
|
2022-10-15 16:27:25 +01:00
|
|
|
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 {
|
2022-08-14 14:30:13 +01:00
|
|
|
win.hide();
|
|
|
|
}
|
|
|
|
|
|
|
|
Inhibit(false)
|
|
|
|
});
|
|
|
|
|
|
|
|
Self {
|
|
|
|
window: win,
|
2022-09-25 22:49:00 +01:00
|
|
|
cache: HashMap::new(),
|
|
|
|
monitor: module_info.monitor.clone(),
|
2022-10-15 16:27:25 +01:00
|
|
|
pos,
|
2022-09-25 22:49:00 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn register_content(&mut self, key: usize, content: gtk::Box) {
|
|
|
|
debug!("Registered popup content for #{}", key);
|
|
|
|
self.cache.insert(key, content);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn show_content(&self, key: usize) {
|
|
|
|
self.clear_window();
|
|
|
|
|
|
|
|
if let Some(content) = self.cache.get(&key) {
|
|
|
|
content.style_context().add_class("popup");
|
|
|
|
self.window.add(content);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn clear_window(&self) {
|
|
|
|
let children = self.window.children();
|
|
|
|
for child in children {
|
|
|
|
self.window.remove(&child);
|
2022-08-14 14:30:13 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-15 16:27:25 +01:00
|
|
|
/// Shows the popup
|
2023-04-09 22:42:35 +01:00
|
|
|
pub fn show(&self, geometry: WidgetGeometry) {
|
2023-01-29 22:48:42 +00:00
|
|
|
self.window.show();
|
2022-10-15 16:27:25 +01:00
|
|
|
self.set_pos(geometry);
|
2022-08-14 14:30:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Hides the popover
|
|
|
|
pub fn hide(&self) {
|
|
|
|
self.window.hide();
|
|
|
|
}
|
2022-08-15 21:11:00 +01:00
|
|
|
|
2022-09-25 22:49:00 +01:00
|
|
|
/// Checks if the popup is currently visible
|
|
|
|
pub fn is_visible(&self) -> bool {
|
|
|
|
self.window.is_visible()
|
|
|
|
}
|
|
|
|
|
2022-10-15 16:27:25 +01:00
|
|
|
/// Sets the popup's X/Y position relative to the left or border of the screen
|
|
|
|
/// (depending on orientation).
|
2023-04-09 22:42:35 +01:00
|
|
|
fn set_pos(&self, geometry: WidgetGeometry) {
|
2022-10-15 16:27:25 +01:00
|
|
|
let orientation = self.pos.get_orientation();
|
|
|
|
|
|
|
|
let mon_workarea = self.monitor.workarea();
|
|
|
|
let screen_size = if orientation == Orientation::Horizontal {
|
|
|
|
mon_workarea.width()
|
|
|
|
} else {
|
|
|
|
mon_workarea.height()
|
|
|
|
};
|
2022-08-15 21:11:00 +01:00
|
|
|
|
2022-10-15 16:27:25 +01:00
|
|
|
let (popup_width, popup_height) = self.window.size();
|
|
|
|
let popup_size = if orientation == Orientation::Horizontal {
|
|
|
|
popup_width
|
|
|
|
} else {
|
|
|
|
popup_height
|
|
|
|
};
|
2022-08-15 21:11:00 +01:00
|
|
|
|
2022-10-15 16:27:25 +01:00
|
|
|
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();
|
2022-08-15 21:11:00 +01:00
|
|
|
|
|
|
|
if offset < 5.0 {
|
|
|
|
offset = 5.0;
|
2022-10-15 16:27:25 +01:00
|
|
|
} else if offset > f64::from(screen_size - popup_size) - 5.0 {
|
|
|
|
offset = f64::from(screen_size - popup_size) - 5.0;
|
2022-08-15 21:11:00 +01:00
|
|
|
}
|
|
|
|
|
2022-10-15 16:27:25 +01:00
|
|
|
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);
|
2022-08-15 21:11:00 +01:00
|
|
|
}
|
2022-09-25 22:49:00 +01:00
|
|
|
|
|
|
|
/// Gets the absolute X position of the button
|
2022-10-15 16:27:25 +01:00
|
|
|
/// and its width / height (depending on orientation).
|
2023-04-09 22:42:35 +01:00
|
|
|
pub fn widget_geometry<W>(widget: &W, orientation: Orientation) -> WidgetGeometry
|
|
|
|
where
|
|
|
|
W: IsA<gtk::Widget>,
|
|
|
|
{
|
|
|
|
let widget_size = if orientation == Orientation::Horizontal {
|
|
|
|
widget.allocation().width()
|
2022-10-15 16:27:25 +01:00
|
|
|
} else {
|
2023-04-09 22:42:35 +01:00
|
|
|
widget.allocation().height()
|
2022-10-15 16:27:25 +01:00
|
|
|
};
|
2022-09-25 22:49:00 +01:00
|
|
|
|
2023-04-09 22:42:35 +01:00
|
|
|
let top_level = widget.toplevel().expect("Failed to get top-level widget");
|
2022-10-15 16:27:25 +01:00
|
|
|
|
|
|
|
let bar_size = if orientation == Orientation::Horizontal {
|
|
|
|
top_level.allocation().width()
|
|
|
|
} else {
|
|
|
|
top_level.allocation().height()
|
|
|
|
};
|
|
|
|
|
2023-04-09 22:42:35 +01:00
|
|
|
let (widget_x, widget_y) = widget
|
2022-09-25 22:49:00 +01:00
|
|
|
.translate_coordinates(&top_level, 0, 0)
|
|
|
|
.unwrap_or((0, 0));
|
|
|
|
|
2023-04-09 22:42:35 +01:00
|
|
|
let widget_pos = if orientation == Orientation::Horizontal {
|
|
|
|
widget_x
|
2022-10-15 16:27:25 +01:00
|
|
|
} else {
|
2023-04-09 22:42:35 +01:00
|
|
|
widget_y
|
2022-10-15 16:27:25 +01:00
|
|
|
};
|
|
|
|
|
2023-04-09 22:42:35 +01:00
|
|
|
WidgetGeometry {
|
|
|
|
position: widget_pos,
|
|
|
|
size: widget_size,
|
2022-10-15 16:27:25 +01:00
|
|
|
bar_size,
|
|
|
|
}
|
2022-09-25 22:49:00 +01:00
|
|
|
}
|
2022-08-14 14:30:13 +01:00
|
|
|
}
|
2022-10-15 16:27:25 +01:00
|
|
|
|
|
|
|
#[derive(Debug, Copy, Clone)]
|
2023-04-09 22:42:35 +01:00
|
|
|
pub struct WidgetGeometry {
|
2022-10-15 16:27:25 +01:00
|
|
|
position: i32,
|
|
|
|
size: i32,
|
|
|
|
bar_size: i32,
|
|
|
|
}
|