Dotfiles/desktopenvs/hyprdrive/astro-menu/ui/favorites.py

107 lines
3.8 KiB
Python

"""Full-width favourites row at the top of the app drawer.
Shows pinned apps (settings["favorites"], a list of .desktop entry ids). If nothing
is pinned yet it falls back to the most-frequently-launched apps. Pin/unpin from the
drawer grid via right-click / long-press.
"""
from __future__ import annotations
import gi
gi.require_version("Gtk", "4.0")
gi.require_version("AstalApps", "0.1")
from gi.repository import AstalApps, Gtk # noqa: E402
def add_pin_gestures(widget: Gtk.Widget, on_toggle) -> None:
"""Wire right-click and long-press on `widget` to pin/unpin.
Uses the CAPTURE phase and claims the sequence so the gesture fires reliably on a
Gtk.Button (whose own primary-click gesture would otherwise swallow it) and does
not also trigger the button's launch action."""
def fire(gesture, *_a) -> None:
on_toggle()
gesture.set_state(Gtk.EventSequenceState.CLAIMED)
rclick = Gtk.GestureClick(button=3)
rclick.set_propagation_phase(Gtk.PropagationPhase.CAPTURE)
rclick.connect("pressed", fire)
widget.add_controller(rclick)
longpress = Gtk.GestureLongPress()
longpress.set_touch_only(False)
longpress.set_propagation_phase(Gtk.PropagationPhase.CAPTURE)
longpress.connect("pressed", fire)
widget.add_controller(longpress)
class Favorites(Gtk.Box):
def __init__(self, settings, on_launch):
super().__init__(orientation=Gtk.Orientation.VERTICAL)
self.add_css_class("favorites")
self.settings = settings
self._on_launch = on_launch
self._apps = AstalApps.Apps()
title = Gtk.Label(label="Favorites", xalign=0.0)
title.add_css_class("section-title")
self.append(title)
self._row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
self._row.add_css_class("favorites-row")
scroller = Gtk.ScrolledWindow(
vscrollbar_policy=Gtk.PolicyType.NEVER,
hscrollbar_policy=Gtk.PolicyType.AUTOMATIC)
scroller.set_child(self._row)
self.append(scroller)
settings.subscribe(self.refresh)
self.refresh()
def _by_entry(self) -> dict:
return {a.get_entry(): a for a in self._apps.get_list() if a.get_entry()}
def _resolve(self) -> list:
by_entry = self._by_entry()
entries = self.settings.favorites()
if entries:
return [by_entry[e] for e in entries if e in by_entry]
# fallback: most-used apps
apps = sorted(self._apps.get_list(), key=lambda a: -a.get_frequency())
return [a for a in apps if a.get_frequency() > 0][:8]
def refresh(self) -> None:
child = self._row.get_first_child()
while child:
self._row.remove(child)
child = self._row.get_first_child()
apps = self._resolve()
if not apps:
self._row.append(Gtk.Label(label="Right-click an app below to pin it",
xalign=0.0))
return
for app in apps:
self._row.append(self._tile(app))
def _tile(self, app) -> Gtk.Widget:
btn = Gtk.Button()
btn.add_css_class("fav-tile")
content = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
icon = Gtk.Image.new_from_icon_name(app.get_icon_name() or "application-x-executable")
icon.set_pixel_size(28)
content.append(icon)
content.append(Gtk.Label(label=app.get_name(), ellipsize=3, max_width_chars=14))
btn.set_child(content)
btn.connect("clicked", lambda *_a: self._launch(app))
# right-click / long-press unpins
add_pin_gestures(btn, lambda: self.settings.toggle_favorite(app.get_entry()))
return btn
def _launch(self, app) -> None:
try:
app.launch()
except Exception:
pass
self._on_launch()