164 lines
6.6 KiB
Python
164 lines
6.6 KiB
Python
"""The popup: a content-sized floating layer-shell panel anchored top-centre.
|
|
|
|
Earlier this was a full-monitor overlay with a dim backdrop, but that blocks the
|
|
whole screen — the invisible full-screen surface intercepts every click. Instead the
|
|
window now sizes to its own content and only occupies that area, leaving the rest of
|
|
the screen usable. It is dismissed with the launcher toggle, Esc, or the ✕ button
|
|
(there is no click-outside-to-close, since that would require a blocking full-screen
|
|
surface).
|
|
|
|
Gtk.Window (layer TOP, anchored TOP → horizontally centred, height = content)
|
|
└ Gtk.Overlay
|
|
main : #panel-root (Taskbar / QuadGrid / AppDrawer, each drawn-bordered)
|
|
over : ✕ close button (top-right)
|
|
|
|
Expanding the app drawer additionally anchors the BOTTOM edge so the panel stretches
|
|
down and the drawer fills to the bottom; collapsing removes that anchor.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import gi
|
|
|
|
gi.require_version("Gtk", "4.0")
|
|
gi.require_version("Gdk", "4.0")
|
|
gi.require_version("Gtk4LayerShell", "1.0")
|
|
from gi.repository import Gdk, GLib, Gtk # noqa: E402
|
|
from gi.repository import Gtk4LayerShell as LayerShell # noqa: E402
|
|
|
|
from appservices import Services
|
|
from lib.border import bordered
|
|
from registry import ordered_specs
|
|
from ui.appdrawer import AppDrawer
|
|
from ui.quadgrid import QuadGrid
|
|
from ui.taskbar import Taskbar
|
|
|
|
PANEL_WIDTH_FRACTION = 0.5 # of the monitor width...
|
|
MAX_PANEL_WIDTH = 1100 # ...clamped to this
|
|
TOP_MARGIN = 46 # gap below the top bar
|
|
BOTTOM_MARGIN = 34 # gap from the screen bottom when expanded
|
|
|
|
|
|
class MenuWindow(Gtk.ApplicationWindow):
|
|
def __init__(self, app, settings, services: Services):
|
|
super().__init__(application=app)
|
|
self.set_name("menu-window")
|
|
self.add_css_class("menu-window")
|
|
self.settings = settings
|
|
self.services = services
|
|
self._drawer_expanded = False
|
|
|
|
self._init_layer_shell()
|
|
|
|
self.root = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
|
|
self.root.set_name("panel-root")
|
|
self.root.add_css_class("panel")
|
|
|
|
# taskbar (full-width, top)
|
|
self.taskbar = Taskbar(on_activate=self.hide_menu)
|
|
self.taskbar_reveal = Gtk.Revealer(
|
|
transition_type=Gtk.RevealerTransitionType.SLIDE_DOWN,
|
|
transition_duration=160, reveal_child=True)
|
|
self.taskbar_reveal.set_child(bordered(self.taskbar))
|
|
self.root.append(self.taskbar_reveal)
|
|
|
|
# quads
|
|
specs = list(ordered_specs(settings))
|
|
self.grid = QuadGrid(specs, settings, services)
|
|
self.quad_reveal = Gtk.Revealer(
|
|
transition_type=Gtk.RevealerTransitionType.SLIDE_UP,
|
|
transition_duration=200, reveal_child=True)
|
|
self.quad_reveal.set_child(self.grid)
|
|
self.root.append(self.quad_reveal)
|
|
|
|
# appdrawer
|
|
self.appdrawer = AppDrawer(settings, on_launch=self.hide_menu,
|
|
on_toggle_expand=self._on_appdrawer_expand)
|
|
self.appdrawer_wrap = bordered(self.appdrawer)
|
|
self.appdrawer_wrap.set_valign(Gtk.Align.FILL)
|
|
self.root.append(self.appdrawer_wrap)
|
|
|
|
overlay = Gtk.Overlay()
|
|
overlay.set_child(self.root)
|
|
close = Gtk.Button(label="✕")
|
|
close.add_css_class("close-btn")
|
|
close.set_halign(Gtk.Align.END)
|
|
close.set_valign(Gtk.Align.START)
|
|
close.connect("clicked", lambda *_: self.hide_menu())
|
|
overlay.add_overlay(close)
|
|
self.set_child(overlay)
|
|
|
|
key = Gtk.EventControllerKey()
|
|
key.connect("key-pressed", self._on_key)
|
|
self.add_controller(key)
|
|
|
|
self.connect("map", lambda *_: self._apply_size())
|
|
self.set_visible(False)
|
|
|
|
# -- layer shell / size -----------------------------------------------
|
|
def _init_layer_shell(self) -> None:
|
|
LayerShell.init_for_window(self)
|
|
LayerShell.set_layer(self, LayerShell.Layer.TOP)
|
|
LayerShell.set_namespace(self, "astal-menu")
|
|
# Anchor the top edge only: the surface is horizontally centred and its
|
|
# height follows the content (no opposite anchors = no full-screen fill).
|
|
LayerShell.set_anchor(self, LayerShell.Edge.TOP, True)
|
|
LayerShell.set_margin(self, LayerShell.Edge.TOP, TOP_MARGIN)
|
|
LayerShell.set_keyboard_mode(self, LayerShell.KeyboardMode.ON_DEMAND)
|
|
|
|
def _monitor_width(self) -> int:
|
|
display = Gdk.Display.get_default()
|
|
surface = self.get_surface()
|
|
mon = display.get_monitor_at_surface(surface) if surface is not None else None
|
|
if mon is None:
|
|
monitors = display.get_monitors()
|
|
mon = monitors.get_item(0) if monitors.get_n_items() else None
|
|
return mon.get_geometry().width if mon is not None else 1920
|
|
|
|
def _apply_size(self) -> None:
|
|
width = min(int(self._monitor_width() * PANEL_WIDTH_FRACTION), MAX_PANEL_WIDTH)
|
|
self.root.set_size_request(width, -1)
|
|
|
|
# -- appdrawer expansion ----------------------------------------------
|
|
def _on_appdrawer_expand(self, expanded: bool) -> None:
|
|
self._drawer_expanded = expanded
|
|
# Stretch to the bottom only while the drawer is expanded.
|
|
LayerShell.set_anchor(self, LayerShell.Edge.BOTTOM, expanded)
|
|
LayerShell.set_margin(self, LayerShell.Edge.BOTTOM, BOTTOM_MARGIN if expanded else 0)
|
|
# Hide (not just un-reveal) the taskbar + quads so they reserve zero space
|
|
# and the drawer fills the whole panel from the top.
|
|
self.quad_reveal.set_reveal_child(not expanded)
|
|
self.taskbar_reveal.set_reveal_child(not expanded)
|
|
self.quad_reveal.set_visible(not expanded)
|
|
self.taskbar_reveal.set_visible(not expanded)
|
|
self.appdrawer_wrap.set_vexpand(expanded)
|
|
|
|
# -- visibility --------------------------------------------------------
|
|
def show_menu(self, focus_appdrawer: bool = False) -> None:
|
|
self.appdrawer.set_expanded(False)
|
|
self.taskbar.refresh()
|
|
self.grid.on_show()
|
|
self.appdrawer.on_show()
|
|
self.set_visible(True)
|
|
self.present()
|
|
GLib.timeout_add(30, lambda: (self._apply_size(), False)[1])
|
|
if focus_appdrawer:
|
|
self.appdrawer.set_expanded(True)
|
|
|
|
def hide_menu(self) -> None:
|
|
self.grid.on_hide()
|
|
self.appdrawer.set_expanded(False)
|
|
self.set_visible(False)
|
|
|
|
def toggle(self, focus_appdrawer: bool = False) -> None:
|
|
if self.get_visible():
|
|
self.hide_menu()
|
|
else:
|
|
self.show_menu(focus_appdrawer)
|
|
|
|
def _on_key(self, _c, keyval, _kc, _state) -> bool:
|
|
if keyval == Gdk.KEY_Escape:
|
|
self.hide_menu()
|
|
return True
|
|
return False
|