Grab-fria menyer överallt, popover-stack, radmeny i fönsterlistan
All checks were successful
check / check (push) Successful in 1m34s
All checks were successful
check / check (push) Successful in 1m34s
- ALLA popovers är nu grab-fria (autohide av) med gemensam
auto-stängning: pekar-leave + delay, watchdog om pekaren aldrig når
popovern, och skroll-stegning räknas som aktivitet. Grabbande menyer
krockade med tooltips ('grabbing popup with non-top most parent') —
det var därför högerklicksmenyn ibland inte öppnades.
- Popover-stack ersätter en-åt-gången: en popover vars ankare sitter i
en öppen popover nästlas i stället för att stänga föräldern, och
förälderns auto-stängning pausas medan ett barn är överst.
- Högerklick på en rad i fönsterlistan öppnar den rutans egen meny
(nästlad); högerklick på gruppikonen gäller som förut alla fönster
i gruppen.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -45,8 +45,8 @@ pub struct Ctx {
|
||||
/// (att riva en öppen popovers förälder låser GTK:s pekar-grab)
|
||||
pub open_popovers: Cell<u32>,
|
||||
pub refresh_pending: Cell<bool>,
|
||||
/// den enda popover som får vara öppen åt gången
|
||||
pub current_popover: RefCell<Option<gtk::Popover>>,
|
||||
/// öppna popovers, nästlade i ordning (sist = överst)
|
||||
pub popover_stack: RefCell<Vec<gtk::Popover>>,
|
||||
/// öppen fönsterlista (för skroll-stegning från gruppikonen)
|
||||
pub group_list: RefCell<Option<menu::GroupList>>,
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ fn activate(app: >k::Application, cfg_path: PathBuf) {
|
||||
hotspot_windows: RefCell::new(Vec::new()),
|
||||
open_popovers: Cell::new(0),
|
||||
refresh_pending: Cell::new(false),
|
||||
current_popover: RefCell::new(None),
|
||||
popover_stack: RefCell::new(Vec::new()),
|
||||
group_list: RefCell::new(None),
|
||||
});
|
||||
|
||||
|
||||
160
src/menu.rs
160
src/menu.rs
@@ -159,27 +159,38 @@ fn toggle_pin(ctx: &Rc<Ctx>, connector: &str) {
|
||||
crate::dock::refresh_items(ctx);
|
||||
}
|
||||
|
||||
/// Registrera en popover: stänger ev. redan öppen (bara en åt gången —
|
||||
/// två grabbing-popups samtidigt låser GTK:s pekar-grab på Wayland),
|
||||
/// bokför öppen-räknaren, kör uppskjuten ikon-refresh när den stängs
|
||||
/// och städar bort sig själv.
|
||||
/// Registrera en popover i stacken. Alla våra popovers är GRAB-FRIA
|
||||
/// (autohide av) — grabbande popups låser GTK:s pekar-grab på Wayland
|
||||
/// och krockar med tooltips. En popover vars ankare sitter i en redan
|
||||
/// öppen popover nästlas (t.ex. radmeny i fönsterlistan); annars stängs
|
||||
/// allt gammalt först. Uppskjuten ikon-refresh körs när allt stängts.
|
||||
fn register_popover(ctx: &Rc<Ctx>, popover: &impl IsA<gtk::Popover>) {
|
||||
if let Some(old) = ctx.current_popover.borrow_mut().take() {
|
||||
old.popdown();
|
||||
}
|
||||
let p = popover.as_ref().clone();
|
||||
*ctx.current_popover.borrow_mut() = Some(p.clone());
|
||||
p.set_autohide(false);
|
||||
|
||||
// nästlad om ankaret sitter inne i någon redan öppen popover
|
||||
let nested = p
|
||||
.parent()
|
||||
.map(|anchor| {
|
||||
ctx.popover_stack
|
||||
.borrow()
|
||||
.iter()
|
||||
.any(|open| anchor.is_ancestor(open))
|
||||
})
|
||||
.unwrap_or(false);
|
||||
if !nested {
|
||||
for old in ctx.popover_stack.borrow_mut().drain(..) {
|
||||
old.popdown();
|
||||
}
|
||||
}
|
||||
ctx.popover_stack.borrow_mut().push(p.clone());
|
||||
ctx.open_popovers.set(ctx.open_popovers.get() + 1);
|
||||
|
||||
let ctx = ctx.clone();
|
||||
p.connect_closed(move |p| {
|
||||
ctx.open_popovers
|
||||
.set(ctx.open_popovers.get().saturating_sub(1));
|
||||
{
|
||||
let mut cur = ctx.current_popover.borrow_mut();
|
||||
if cur.as_ref() == Some(p) {
|
||||
*cur = None;
|
||||
}
|
||||
}
|
||||
ctx.popover_stack.borrow_mut().retain(|x| x != p);
|
||||
let p = p.clone();
|
||||
let ctx2 = ctx.clone();
|
||||
glib::idle_add_local_once(move || {
|
||||
@@ -191,6 +202,62 @@ fn register_popover(ctx: &Rc<Ctx>, popover: &impl IsA<gtk::Popover>) {
|
||||
});
|
||||
}
|
||||
|
||||
/// Är popovern överst i stacken? (bara då får den auto-stängas — annars
|
||||
/// stänger förälderns leave-timer den medan en nästlad meny är öppen)
|
||||
fn is_topmost(ctx: &Ctx, p: >k::Popover) -> bool {
|
||||
ctx.popover_stack.borrow().last() == Some(p)
|
||||
}
|
||||
|
||||
/// Gemensam auto-stängning: pekaren lämnar → stäng efter delay;
|
||||
/// watchdog om pekaren aldrig når popovern.
|
||||
fn setup_autoclose(
|
||||
ctx: &Rc<Ctx>,
|
||||
popover: &impl IsA<gtk::Popover>,
|
||||
leave_delay_ms: u64,
|
||||
keep_open: Option<Rc<dyn Fn() -> bool>>,
|
||||
) {
|
||||
let p = popover.as_ref().clone();
|
||||
let mc = gtk::EventControllerMotion::new();
|
||||
{
|
||||
let p2 = p.clone();
|
||||
let ctx2 = ctx.clone();
|
||||
mc.connect_leave(move |ctl| {
|
||||
let p3 = p2.clone();
|
||||
let ctl2 = ctl.clone();
|
||||
let ctx3 = ctx2.clone();
|
||||
glib::timeout_add_local_once(
|
||||
std::time::Duration::from_millis(leave_delay_ms),
|
||||
move || {
|
||||
if !ctl2.contains_pointer() && is_topmost(&ctx3, &p3) {
|
||||
p3.popdown();
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
p.add_controller(mc.clone());
|
||||
// watchdog: aldrig besökt + överst + inget händer → stäng
|
||||
{
|
||||
let p2 = p.clone();
|
||||
let ctx2 = ctx.clone();
|
||||
let visited = std::cell::Cell::new(false);
|
||||
glib::timeout_add_local(std::time::Duration::from_millis(1800), move || {
|
||||
if mc.contains_pointer() {
|
||||
visited.set(true);
|
||||
}
|
||||
if visited.get() || !is_topmost(&ctx2, &p2) {
|
||||
return glib::ControlFlow::Break; // besökt/inte överst: leave-logiken tar över
|
||||
}
|
||||
// aktivitet utan pekare (t.ex. skroll-stegning från ikonen)
|
||||
if keep_open.as_ref().map(|f| f()).unwrap_or(false) {
|
||||
return glib::ControlFlow::Continue;
|
||||
}
|
||||
p2.popdown();
|
||||
glib::ControlFlow::Break
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Popover-riktning bort från dockans kant (så listan inte öppnas rakt
|
||||
/// under muspekaren och råkar hovra första raden direkt).
|
||||
fn popover_position(ctx: &Rc<Ctx>, anchor: >k::Button) -> gtk::PositionType {
|
||||
@@ -281,6 +348,7 @@ pub fn popup_item_menu(ctx: &Rc<Ctx>, item: &Rc<Item>, anchor: >k::Button) {
|
||||
popover.set_position(popover_position(ctx, anchor));
|
||||
popover.set_parent(anchor);
|
||||
register_popover(ctx, &popover);
|
||||
setup_autoclose(ctx, &popover, 700, None);
|
||||
popover.popup();
|
||||
}
|
||||
|
||||
@@ -447,6 +515,26 @@ pub fn popup_group_list(ctx: &Rc<Ctx>, item: &Rc<Item>, anchor: >k::Button) {
|
||||
});
|
||||
row.add_controller(mc);
|
||||
}
|
||||
// högerklick på raden: den rutans egen meny (nästlad popover)
|
||||
{
|
||||
let rc = gtk::GestureClick::new();
|
||||
rc.set_button(3);
|
||||
let ctx3 = ctx.clone();
|
||||
let w3 = w.clone();
|
||||
let meta3 = item.meta.clone();
|
||||
let pinned3 = item.pinned;
|
||||
let row3 = row.clone();
|
||||
rc.connect_released(move |gesture, _, _, _| {
|
||||
gesture.set_state(gtk::EventSequenceState::Claimed);
|
||||
let single = Rc::new(Item {
|
||||
meta: meta3.clone(),
|
||||
windows: vec![w3.clone()],
|
||||
pinned: pinned3,
|
||||
});
|
||||
popup_item_menu(&ctx3, &single, &row3);
|
||||
});
|
||||
row.add_controller(rc);
|
||||
}
|
||||
}
|
||||
|
||||
// skroll över själva listan stegar också
|
||||
@@ -489,43 +577,14 @@ pub fn popup_group_list(ctx: &Rc<Ctx>, item: &Rc<Item>, anchor: >k::Button) {
|
||||
});
|
||||
}
|
||||
|
||||
// stäng när pekaren lämnar listan (snabbare i hover-läge)
|
||||
let leave_delay = if hover_mode { 400 } else { 700 };
|
||||
let mc = gtk::EventControllerMotion::new();
|
||||
{
|
||||
let pop3 = popover.clone();
|
||||
mc.connect_leave(move |ctl| {
|
||||
let pop4 = pop3.clone();
|
||||
let ctl2 = ctl.clone();
|
||||
glib::timeout_add_local_once(
|
||||
std::time::Duration::from_millis(leave_delay),
|
||||
move || {
|
||||
if !ctl2.contains_pointer() {
|
||||
pop4.popdown();
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
popover.add_controller(mc.clone());
|
||||
|
||||
// watchdog: om pekaren aldrig når listan OCH ingen skroll-stegning
|
||||
// sker, stäng efter en stund (upprepas; aktivitet = markerad rad)
|
||||
{
|
||||
let pop3 = popover.clone();
|
||||
let selected2 = selected.clone();
|
||||
let last_seen = std::cell::Cell::new(selected.get());
|
||||
glib::timeout_add_local(std::time::Duration::from_millis(1500), move || {
|
||||
if mc.contains_pointer() || selected2.get() != last_seen.get() {
|
||||
last_seen.set(selected2.get());
|
||||
return glib::ControlFlow::Continue;
|
||||
}
|
||||
pop3.popdown();
|
||||
glib::ControlFlow::Break
|
||||
});
|
||||
}
|
||||
|
||||
register_popover(ctx, &popover);
|
||||
// skroll-stegning från ikonen räknas som aktivitet — markerad rad
|
||||
// håller listan öppen även om pekaren aldrig går in i den
|
||||
let keep = {
|
||||
let selected = selected.clone();
|
||||
Rc::new(move || selected.get().is_some()) as Rc<dyn Fn() -> bool>
|
||||
};
|
||||
setup_autoclose(ctx, &popover, if hover_mode { 400 } else { 700 }, Some(keep));
|
||||
popover.popup();
|
||||
}
|
||||
|
||||
@@ -571,6 +630,7 @@ pub fn popup_panel_menu(ctx: &Rc<Ctx>, panel: >k::Box, x: f64, y: f64) {
|
||||
popover.set_parent(panel);
|
||||
popover.set_pointing_to(Some(&gdk::Rectangle::new(x as i32, y as i32, 1, 1)));
|
||||
register_popover(ctx, &popover);
|
||||
setup_autoclose(ctx, &popover, 700, None);
|
||||
popover.popup();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user