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)
|
/// (att riva en öppen popovers förälder låser GTK:s pekar-grab)
|
||||||
pub open_popovers: Cell<u32>,
|
pub open_popovers: Cell<u32>,
|
||||||
pub refresh_pending: Cell<bool>,
|
pub refresh_pending: Cell<bool>,
|
||||||
/// den enda popover som får vara öppen åt gången
|
/// öppna popovers, nästlade i ordning (sist = överst)
|
||||||
pub current_popover: RefCell<Option<gtk::Popover>>,
|
pub popover_stack: RefCell<Vec<gtk::Popover>>,
|
||||||
/// öppen fönsterlista (för skroll-stegning från gruppikonen)
|
/// öppen fönsterlista (för skroll-stegning från gruppikonen)
|
||||||
pub group_list: RefCell<Option<menu::GroupList>>,
|
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()),
|
hotspot_windows: RefCell::new(Vec::new()),
|
||||||
open_popovers: Cell::new(0),
|
open_popovers: Cell::new(0),
|
||||||
refresh_pending: Cell::new(false),
|
refresh_pending: Cell::new(false),
|
||||||
current_popover: RefCell::new(None),
|
popover_stack: RefCell::new(Vec::new()),
|
||||||
group_list: RefCell::new(None),
|
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);
|
crate::dock::refresh_items(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Registrera en popover: stänger ev. redan öppen (bara en åt gången —
|
/// Registrera en popover i stacken. Alla våra popovers är GRAB-FRIA
|
||||||
/// två grabbing-popups samtidigt låser GTK:s pekar-grab på Wayland),
|
/// (autohide av) — grabbande popups låser GTK:s pekar-grab på Wayland
|
||||||
/// bokför öppen-räknaren, kör uppskjuten ikon-refresh när den stängs
|
/// och krockar med tooltips. En popover vars ankare sitter i en redan
|
||||||
/// och städar bort sig själv.
|
/// ö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>) {
|
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();
|
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);
|
ctx.open_popovers.set(ctx.open_popovers.get() + 1);
|
||||||
|
|
||||||
let ctx = ctx.clone();
|
let ctx = ctx.clone();
|
||||||
p.connect_closed(move |p| {
|
p.connect_closed(move |p| {
|
||||||
ctx.open_popovers
|
ctx.open_popovers
|
||||||
.set(ctx.open_popovers.get().saturating_sub(1));
|
.set(ctx.open_popovers.get().saturating_sub(1));
|
||||||
{
|
ctx.popover_stack.borrow_mut().retain(|x| x != p);
|
||||||
let mut cur = ctx.current_popover.borrow_mut();
|
|
||||||
if cur.as_ref() == Some(p) {
|
|
||||||
*cur = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let p = p.clone();
|
let p = p.clone();
|
||||||
let ctx2 = ctx.clone();
|
let ctx2 = ctx.clone();
|
||||||
glib::idle_add_local_once(move || {
|
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
|
/// Popover-riktning bort från dockans kant (så listan inte öppnas rakt
|
||||||
/// under muspekaren och råkar hovra första raden direkt).
|
/// under muspekaren och råkar hovra första raden direkt).
|
||||||
fn popover_position(ctx: &Rc<Ctx>, anchor: >k::Button) -> gtk::PositionType {
|
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_position(popover_position(ctx, anchor));
|
||||||
popover.set_parent(anchor);
|
popover.set_parent(anchor);
|
||||||
register_popover(ctx, &popover);
|
register_popover(ctx, &popover);
|
||||||
|
setup_autoclose(ctx, &popover, 700, None);
|
||||||
popover.popup();
|
popover.popup();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -447,6 +515,26 @@ pub fn popup_group_list(ctx: &Rc<Ctx>, item: &Rc<Item>, anchor: >k::Button) {
|
|||||||
});
|
});
|
||||||
row.add_controller(mc);
|
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å
|
// 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);
|
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();
|
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_parent(panel);
|
||||||
popover.set_pointing_to(Some(&gdk::Rectangle::new(x as i32, y as i32, 1, 1)));
|
popover.set_pointing_to(Some(&gdk::Rectangle::new(x as i32, y as i32, 1, 1)));
|
||||||
register_popover(ctx, &popover);
|
register_popover(ctx, &popover);
|
||||||
|
setup_autoclose(ctx, &popover, 700, None);
|
||||||
popover.popup();
|
popover.popup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user