panel: extend the dismiss-scrim pattern for the capsule's click-away (item B)

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.
This commit is contained in:
Breadway 2026-08-25 11:47:49 +08:00
parent 33181a7461
commit c68ed0d7e9

View file

@ -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<RefCell<Option<Rc<dyn Fn()>>>>;
#[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: &gtk4::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);