refactor: rewrite ModusDropdown to use native Gtk.Menu

Replace layer-shell AppletWindow-based dropdown with Gtk.Menu approach.
Adds hide signal for button state reset, dropdown_option factory,
append/clear methods, and popup_at_widget/popup aliases.
This commit is contained in:
sankalp committed 2026-07-28 09:59:31 +05:45
1 parent 7fdeb7beba
commit 8407c9685c
1 file changed
+115 -72
+115 -72
View File
@@ -1,81 +1,124 @@
from fabric.utils import GLib, logger
from fabric.widgets.box import Box
from fabric.widgets.centerbox import CenterBox
from fabric.widgets.eventbox import EventBox
from functools import partial
from shared.window.applet_window import AppletWindow
from utils.roam import modus_service
from fabric.utils import logger
from gi.repository import Gdk, Gtk
dropdowns = []
from utils.functions import run_command, thread
def dropdown_divider(comment):
return Box(
children=[Box(name="dropdown-divider", h_expand=True)],
name="dropdown-divider-box",
h_align="fill",
h_expand=True,
v_expand=True,
class ModusDropdown:
"""Reusable Gtk.Menu-based dropdown."""
def __init__(self, items=None):
self._menu = Gtk.Menu()
self._source_button = None
self._menu.connect("hide", self._on_menu_hide)
if items:
self._build(items)
@property
def menu(self):
return self._menu
def _build(self, items):
for entry in items:
item = self._create_item(entry)
if item is not None:
self._menu.append(item)
self._menu.show_all()
def _create_item(self, entry):
if entry is None:
return Gtk.SeparatorMenuItem()
if isinstance(entry, Gtk.Widget):
return entry
if isinstance(entry, dict):
return self._make_item(entry)
label, callback = entry[0], entry[1]
sensitive = entry[2] if len(entry) > 2 else True
item = Gtk.MenuItem.new_with_label(label)
if sensitive and callback:
item.connect("activate", callback)
elif not sensitive:
item.set_sensitive(False)
return item
def _make_item(self, entry):
label = entry.get("label", "")
accel = entry.get("accel")
command = entry.get("command")
callback = entry.get("callback")
sensitive = entry.get("sensitive", True)
if accel:
label = f"{label} {accel}"
item = Gtk.MenuItem.new_with_label(label)
if not sensitive:
item.set_sensitive(False)
elif callback or command:
item.connect(
"activate",
partial(self._activate, callback, command),
)
return item
@staticmethod
def _activate(callback, command, *_):
if callback:
callback()
if command:
thread(run_command, ["sh", "-c", command], timeout=30)
class ModusDropdown(AppletWindow):
def __init__(
self, dropdown_children=None, dropdown_id=None, parent_dropdown=None, **kwargs
):
super().__init__(
layer="top",
exclusivity="auto",
name="dropdown-menu",
title="modus-dropdown",
visible=False,
parent_dropdown=parent_dropdown,
**kwargs,
)
self.id = dropdown_id or str(len(dropdowns))
dropdowns.append(self)
self.connect("notify::visible", self._on_visible_changed)
modus_service.connect("dropdowns-hide-changed", self.hide_dropdown)
self.dropdown = Box(
children=dropdown_children or [],
h_expand=True,
name="dropdown-options",
orientation="vertical",
)
self.child_box = CenterBox(start_children=[self.dropdown])
self.event_box = EventBox(
events=["enter-notify", "leave-notify"],
child=self.child_box,
all_visible=True,
)
self.children = [self.event_box]
self.add_keybinding("escape", self.hide_dropdown)
def hide_dropdown(self, *_):
if self.is_visible():
GLib.idle_add(lambda: self.hide())
def _on_visible_changed(self, *_):
if self.is_visible():
modus_service.current_dropdown = self.id
elif str(modus_service.current_dropdown) == str(self.id):
modus_service.current_dropdown = None
def destroy(self):
"""Clean up resources and global references"""
global dropdowns
if self in dropdowns:
dropdowns.remove(self)
def _on_menu_hide(self, *_):
if not self._source_button:
return
try:
modus_service.disconnect_by_func(self.hide_dropdown)
except Exception as e:
logger.error(f"An error occurred: {e}")
self._source_button.unset_state_flags(Gtk.StateFlags.ACTIVE)
self._source_button.queue_draw()
except Exception:
logger.exception("[ModusDropdown] failed to reset button state")
self._source_button = None
super().destroy()
def append(self, item):
created = self._create_item(item)
if created is not None:
self._menu.append(created)
self._menu.show_all()
def clear(self):
for child in self._menu.get_children():
child.destroy()
def popup_at_widget(self, widget, source_button=None):
self._source_button = source_button or widget
self._menu.popup_at_widget(
widget,
Gdk.Gravity.SOUTH_WEST,
Gdk.Gravity.NORTH_WEST,
None,
)
def popup(self, widget, source_button=None):
self.popup_at_widget(widget, source_button)
def dropdown_option(label, accel=None, command=None, callback=None, sensitive=True):
"""Create a dropdown menu option dict.
Usage:
dropdown_option("Lock Screen", "󰘳 L", "fabric-cli exec modus 'lock_screen.lock()'")
dropdown_option("Settings", callback=my_func)
dropdown_option("Disabled Item", sensitive=False)
"""
return {
"label": label,
"accel": accel,
"command": command,
"callback": callback,
"sensitive": sensitive,
}