107 lines
3.8 KiB
Python
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()
|