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);