From 570ff085bb7b47a121a57c03482f316e8a10d722 Mon Sep 17 00:00:00 2001 From: claude Date: Sun, 30 Aug 2026 00:58:32 +0200 Subject: [PATCH] =?UTF-8?q?Grab-fria=20menyer=20=C3=B6verallt,=20popover-s?= =?UTF-8?q?tack,=20radmeny=20i=20f=C3=B6nsterlistan?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- src/dock.rs | 4 +- src/main.rs | 2 +- src/menu.rs | 160 ++++++++++++++++++++++++++++++++++++---------------- 3 files changed, 113 insertions(+), 53 deletions(-) diff --git a/src/dock.rs b/src/dock.rs index 5fd1520..5713671 100644 --- a/src/dock.rs +++ b/src/dock.rs @@ -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, pub refresh_pending: Cell, - /// den enda popover som får vara öppen åt gången - pub current_popover: RefCell>, + /// öppna popovers, nästlade i ordning (sist = överst) + pub popover_stack: RefCell>, /// öppen fönsterlista (för skroll-stegning från gruppikonen) pub group_list: RefCell>, } diff --git a/src/main.rs b/src/main.rs index d133e01..0d38dc4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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), }); diff --git a/src/menu.rs b/src/menu.rs index 252d9ce..9375633 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -159,27 +159,38 @@ fn toggle_pin(ctx: &Rc, 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, popover: &impl IsA) { - 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, popover: &impl IsA) { }); } +/// Ä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, + popover: &impl IsA, + leave_delay_ms: u64, + keep_open: Option 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, anchor: >k::Button) -> gtk::PositionType { @@ -281,6 +348,7 @@ pub fn popup_item_menu(ctx: &Rc, item: &Rc, 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, item: &Rc, 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, item: &Rc, 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 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, 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(); }