From c68ed0d7e94c03894d6e19d7ce36fc3ce9e04c84 Mon Sep 17 00:00:00 2001 From: Breadway Date: Tue, 25 Aug 2026 11:47:49 +0800 Subject: [PATCH] panel: extend the dismiss-scrim pattern for the capsule's click-away (item B) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PanelSet's shared breadbar-dismiss surface already handles click-away for the wifi/control/media popovers; this adds the pieces the capsule needs to reuse it rather than a second mechanism: an extra on_dismiss callback invoked alongside hide_all() on every dismiss click, and show_capsule_dismiss/hide_dismiss, which show the scrim with its clickable region starting at a caller-given top margin instead of the popover default. That margin matters: breadbar-dismiss's layer (overlay) always renders above the bar's own (top), so if its clickable region ever reached up into where the drawer is actually drawn, it would swallow clicks meant for a result row instead of the click-away. The capsule's own call site (main.rs, not part of this commit) always passes a margin sized to the drawer's maximum possible height, never its live one, so this can never happen regardless of how tall the drawer currently is. Not yet wired into the capsule's open/close — PanelSet's three new methods are unused until that follow-up commit, hence the transient dead_code warnings. --- src/panel.rs | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/src/panel.rs b/src/panel.rs index 9070c63..b9982ed 100644 --- a/src/panel.rs +++ b/src/panel.rs @@ -5,18 +5,31 @@ //! *below* the exclusive zone, and Hyprland slides `breadbar-panel` in from //! the right. +use std::cell::RefCell; +use std::rc::Rc; + use gtk4::gdk::Key; use gtk4::prelude::*; -use gtk4_layer_shell::{KeyboardMode, LayerShell}; +use gtk4_layer_shell::{Edge, KeyboardMode, LayerShell}; use crate::{bind_layer_monitor, theme}; +/// A boxed, ref-counted, optionally-unset click-away callback — see +/// `PanelSet::on_dismiss`'s own doc comment. +type DismissCallback = Rc>>>; + #[derive(Clone)] pub struct PanelSet { pub connectivity: gtk4::Window, pub control: gtk4::Window, pub media: gtk4::Window, dismiss: gtk4::Window, + // Theme 04/spotlight's capsule (plan §7 phase 6c): an extra click-away + // callback invoked alongside the popover-dismiss path below, so the + // SAME `breadbar-dismiss` surface/click-catcher also collapses the + // capsule's drawer — see `show_capsule_dismiss`/`hide_dismiss` and + // `set_on_dismiss`. `None` under every other theme (never set). + on_dismiss: DismissCallback, } impl PanelSet { @@ -36,6 +49,7 @@ impl PanelSet { control, media, dismiss, + on_dismiss: Rc::new(RefCell::new(None)), }; set.wire_dismiss(); set.wire_escape(); @@ -52,6 +66,10 @@ impl PanelSet { pub fn show(&self, which: >k4::Window) { self.hide_panels(); + // A prior capsule search (see `show_capsule_dismiss`) may have left + // the shared dismiss surface's top margin pushed down past its + // popover-shaped default — restore it before this popover uses it. + self.reset_dismiss_margin(); // Dismiss first so the panel maps above it (same Overlay layer). self.dismiss.set_visible(true); self.dismiss.present(); @@ -70,12 +88,55 @@ impl PanelSet { self.media.set_visible(false); } + /// Theme 04/spotlight's capsule (plan §7 phase 6c): registers `cb` to + /// run whenever the shared dismiss surface is clicked, alongside the + /// popovers' own `hide_all`. `cb` is expected to no-op when the capsule + /// isn't actually open (matching `close_fn`'s own guard in main.rs), so + /// this firing on an ordinary popover click-away is harmless. + pub fn set_on_dismiss(&self, cb: impl Fn() + 'static) { + *self.on_dismiss.borrow_mut() = Some(Rc::new(cb)); + } + + /// Shows the dismiss scrim with its clickable region starting at + /// `top_margin` px from the screen top, rather than the theme's own + /// popover-shaped default. See the call site in main.rs's capsule + /// `open_fn` for why this needs to be at least the capsule's own row + /// height plus the drawer's maximum possible height: the dismiss + /// surface's layer (`overlay`) always renders above the bar's own + /// (`top`), so if its clickable region ever reached up into where the + /// drawer is actually drawn, it would swallow clicks meant for a + /// result row instead of forwarding them. + pub fn show_capsule_dismiss(&self, top_margin: i32) { + self.dismiss.set_margin(Edge::Top, top_margin); + self.dismiss.set_visible(true); + self.dismiss.present(); + } + + /// Hides the dismiss scrim and restores its margin to the theme's own + /// popover default, so a later popover `show()` isn't left with a + /// leftover capsule-sized gap. + pub fn hide_dismiss(&self) { + self.reset_dismiss_margin(); + self.dismiss.set_visible(false); + } + + fn reset_dismiss_margin(&self) { + let theme = theme::shell_theme(); + if let Some(surf) = theme.surfaces().get("breadbar-dismiss") { + let top = surf.offset.first().copied().unwrap_or(0.0) as i32; + self.dismiss.set_margin(Edge::Top, top); + } + } + fn wire_dismiss(&self) { let set = self.clone(); let click = gtk4::GestureClick::new(); click.set_button(0); click.connect_pressed(move |_, _, _, _| { set.hide_all(); + if let Some(cb) = set.on_dismiss.borrow().as_ref() { + cb(); + } }); if let Some(child) = self.dismiss.child() { child.add_controller(click);