refactor(src/window): migrate control center windows to AppletWindow and simplify UI

Replace Window base with AppletWindow for consistency, switch to FlatScale, and streamline mouse capture handling.
This commit is contained in:
sankalp committed 2026-06-17 01:45:43 +05:45
1 parent b8d3e74afd
commit f78534dce0
11 files changed
+168 -199

No files matched your search

+28 -41
View File
@@ -7,7 +7,6 @@ from fabric.widgets.label import Label
from shared.dialogs.about import AboutApp, get_about_window
from shared.window.dropdown import ModusDropdown, dropdown_divider, dropdowns
from shared.window.mousecapture import DropDownMouseCapture
from utils.app_name_resolver import format_window
from utils.roam import modus_service
from utils.utils import setup_cursor_hover
@@ -41,8 +40,7 @@ def create_dropdown_with_capture(dropdown_id, parent, dropdown_children, layer="
parent=parent,
dropdown_children=dropdown_children,
)
mouse_capture = DropDownMouseCapture(layer=layer, child_window=dropdown)
return mouse_capture
return dropdown
def manage_button_style_classes(buttons, active_button=None, style_class="active"):
@@ -72,7 +70,7 @@ def on_click_subthread(button, on_clicked, on_click):
for dropdown in dropdowns:
if dropdown.is_visible():
dropdown.hide_via_mousecapture()
dropdown.hide()
break
@@ -137,15 +135,13 @@ class GlobalMenuDropdowns:
self.parent = parent
self.system_dropdown = SystemDropdown(parent=parent)
self.menu_button_dropdown = DropDownMouseCapture(
layer="top", child_window=self.system_dropdown
)
self.menu_button_dropdown = self.system_dropdown
self.menu_button = Button(
label="Modus",
name="global-menu",
on_clicked=lambda _: self.menu_button_dropdown.toggle_mousecapture(),
on_clicked=lambda _: self.menu_button_dropdown.toggle(),
)
self.menu_button_dropdown.child_window.set_pointing_to(self.menu_button)
self.menu_button_dropdown.set_pointing_to(self.menu_button)
self.global_title_menu_about = dropdown_option(
f"About {modus_service.current_active_app_name}",
@@ -162,8 +158,8 @@ class GlobalMenuDropdowns:
parent,
[
dropdown_option(
"Enter Full Screen",
on_click="hyprctl dispatch 'hl.dsp.fullscreen()'",
"Fullscreen",
on_click="hyprctl dispatch 'hl.dsp.window.fullscreen()'",
),
],
)
@@ -183,35 +179,35 @@ class GlobalMenuDropdowns:
),
dropdown_divider("---------------------"),
dropdown_option(
"Move Window to Left",
on_click="hyprctl dispatch 'hl.dsp.movewindow([[l]])'",
"Left",
on_click="hyprctl dispatch 'hl.dsp.window.move([[l]])'",
),
dropdown_option(
"Move Window to Right",
on_click="hyprctl dispatch 'hl.dsp.movewindow([[r]])'",
"Right",
on_click="hyprctl dispatch 'hl.dsp.window.move([[r]])'",
),
dropdown_option(
"Cycle Through Windows",
on_click="hyprctl dispatch 'hl.dsp.cyclenext()'",
"Cycle Next",
on_click="hyprctl dispatch 'hl.dsp.window.cycle_next()'",
),
dropdown_divider("---------------------"),
dropdown_option(
"Float", on_click="hyprctl dispatch 'hl.dsp.togglefloating()'"
"Float", on_click="hyprctl dispatch 'hl.dsp.window.float()'"
),
dropdown_option(
"Quit", on_click="hyprctl dispatch 'hl.dsp.killactive()'"
"Quit", on_click="hyprctl dispatch 'hl.dsp.window.close()'"
),
dropdown_option(
"Pseudo", on_click="hyprctl dispatch 'hl.dsp.pseudo()'"
"Pseudo", on_click="hyprctl dispatch 'hl.dsp.window.pseudo()'"
),
dropdown_option(
"Toggle Split", on_click="hyprctl dispatch 'hl.dsp.togglesplit()'"
),
dropdown_option(
"Center", on_click="hyprctl dispatch 'hl.dsp.centerwindow()'"
"Center", on_click="hyprctl dispatch 'hl.dsp.window.center()'"
),
dropdown_option(
"Group", on_click="hyprctl dispatch 'hl.dsp.togglegroup()'"
"Group", on_click="hyprctl dispatch 'hl.dsp.group.toggle()'"
),
dropdown_option(
"Pin",
@@ -252,31 +248,24 @@ class GlobalMenuDropdowns:
on_clicked=self._on_title_button_clicked,
)
self.global_menu_title.child_window.set_pointing_to(
self.global_menu_button_title
)
self.global_menu_title.set_pointing_to(self.global_menu_button_title)
# File, Edit and Go buttons are placeholders - no dropdowns implemented yet
self.global_menu_button_file = create_menu_button("File")
self.global_menu_button_edit = create_menu_button("Edit")
self.global_menu_button_go = create_menu_button("Go")
self.global_menu_button_view = create_menu_button(
"View",
lambda _: self.global_menu_view.toggle_mousecapture(),
"View", lambda _: self.global_menu_view.toggle()
)
self.global_menu_view.child_window.set_pointing_to(self.global_menu_button_view)
self.global_menu_view.set_pointing_to(self.global_menu_button_view)
self.global_menu_button_window = create_menu_button(
"Window",
lambda _: self.global_menu_window.toggle_mousecapture(),
)
self.global_menu_window.child_window.set_pointing_to(
self.global_menu_button_window
"Window", lambda _: self.global_menu_window.toggle()
)
self.global_menu_window.set_pointing_to(self.global_menu_button_window)
self.global_menu_button_help = create_menu_button(
"Help",
lambda _: self.global_menu_help.toggle_mousecapture(),
"Help", lambda _: self.global_menu_help.toggle()
)
self.global_menu_help.child_window.set_pointing_to(self.global_menu_button_help)
self.global_menu_help.set_pointing_to(self.global_menu_button_help)
self.all_menu_buttons = [
self.menu_button,
@@ -305,7 +294,7 @@ class GlobalMenuDropdowns:
def _on_title_button_clicked(self, _):
if has_active_window():
self.global_menu_title.toggle_mousecapture()
self.global_menu_title.set_visible(not self.global_menu_title.get_visible())
def _on_active_app_changed(self, _, value):
self.global_title_menu_about.set_property("label", f"About {value}")
@@ -366,11 +355,9 @@ class GlobalMenu(Box):
]
def show_system_dropdown(self, imac_button):
self.dropdown_system.menu_button_dropdown.child_window.set_pointing_to(
imac_button
)
self.dropdown_system.menu_button_dropdown.set_pointing_to(imac_button)
mouse_capture = self.dropdown_system.menu_button_dropdown
mouse_capture.set_child_window_visible(not mouse_capture.is_visible())
mouse_capture.toggle()
def destroy(self):
"""Clean up the global menu and its dropdowns"""
+56 -56
View File
@@ -2,12 +2,11 @@ from fabric.bluetooth import BluetoothClient
from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.label import Label
from fabric.widgets.wayland import WaylandWindow as Window
from services.battery import BatteryService, DeviceState
from services.battery import Battery
from services.network import NetworkClient
from shared.window.battery_widget import BatteryControl
from shared.window.mousecapture import DropDownMouseCapture
from shared.window.applet_window import AppletWindow
from utils.functions import format_duration, get_wifi_icon_for_strength
from utils.roam import modus_service
from utils.utils import setup_cursor_hover, svg_file
@@ -15,45 +14,41 @@ from window.controlcenter.bluetooth import BluetoothConnections
from window.controlcenter.wifi import WifiConnections
def create_control_window(name_prefix):
return Window(
def create_control_window(name_prefix, parent=None, pointing_to=None):
return AppletWindow(
parent=parent,
pointing_to=pointing_to,
layer="overlay",
title="modus",
anchor="top right",
margin="2px 10px 0px 0px",
exclusivity="auto",
keyboard_mode="on-demand",
name=f"{name_prefix}-window",
visible=False,
)
def create_mouse_capture(window):
return DropDownMouseCapture(layer="top", child_window=window)
def setup_control_center(
show_window, name_prefix, widget_class, parent, **widget_kwargs
show_window, name_prefix, widget_class, parent, pointing_to=None, **widget_kwargs
):
if not show_window:
return None, None, None
return None, None
window = create_control_window(name_prefix)
window = create_control_window(name_prefix, parent=parent, pointing_to=pointing_to)
widget = widget_class(parent, **widget_kwargs)
window.children = [widget]
mouse_capture = create_mouse_capture(window)
return window, widget, mouse_capture
return window, widget
def handle_indicator_click(mouse_capture):
if mouse_capture:
mouse_capture.toggle_mousecapture()
def handle_indicator_click(window):
if window:
window.toggle()
def hide_control_center(mouse_capture):
if mouse_capture:
mouse_capture.hide_child_window()
def hide_control_center(window):
if window:
window.hide()
class BluetoothIndicator(Box):
@@ -77,16 +72,14 @@ class BluetoothIndicator(Box):
(
self.bluetooth_window,
self.bluetooth_widget,
self.bluetooth_mousecapture,
) = setup_control_center(
self.show_window,
"bluetooth",
BluetoothConnections,
self,
pointing_to=self.bt_button,
show_back_button=False,
)
if self.bluetooth_window:
self.bluetooth_window._pointing_widget = self.bt_button
modus_service.connect("bluetooth-changed", self.on_bluetooth_changed)
self.bluetooth.connect("changed", self.on_bluetooth_direct_changed)
@@ -155,13 +148,13 @@ class BluetoothIndicator(Box):
modus_service.bluetooth = bluetooth_state
def on_bluetooth_clicked(self, *args):
handle_indicator_click(self.bluetooth_mousecapture)
handle_indicator_click(self.bluetooth_window)
def close_bluetooth(self, *args):
hide_control_center(self.bluetooth_mousecapture)
hide_control_center(self.bluetooth_window)
def hide_controlcenter(self, *args):
hide_control_center(self.bluetooth_mousecapture)
hide_control_center(self.bluetooth_window)
class NetworkIndicator(Box):
@@ -188,12 +181,12 @@ class NetworkIndicator(Box):
(
self.wifi_window,
self.wifi_widget,
self.wifi_mousecapture,
) = setup_control_center(
self.show_window,
"wifi",
WifiConnections,
self,
pointing_to=self.network_button,
show_back_button=False,
)
if self.wifi_window:
@@ -232,6 +225,7 @@ class NetworkIndicator(Box):
def on_network_direct_changed(self, *args):
self.update_modus_service_wlan_state()
self.update_state()
hide_control_center(self.wifi_window)
def on_network_changed(self, *args):
self.update_modus_service_wlan_state()
@@ -309,13 +303,19 @@ class NetworkIndicator(Box):
self.network_button.set_tooltip_text(tooltip)
def on_wifi_clicked(self, *args):
handle_indicator_click(self.wifi_mousecapture)
handle_indicator_click(self.wifi_window)
def close_wifi(self, *args):
hide_control_center(self.wifi_mousecapture)
hide_control_center(self.wifi_window)
def on_wifi_direct_changed(self, *args):
hide_control_center(self.wifi_window)
def on_network_clicked(self, *args):
handle_indicator_click(self.wifi_window)
def hide_controlcenter(self, *args):
hide_control_center(self.wifi_mousecapture)
hide_control_center(self.wifi_window)
class BatteryIndicator(Box):
@@ -323,7 +323,7 @@ class BatteryIndicator(Box):
super().__init__(name="battery-indicator", orientation="h", **kwargs)
self.show_window = show_window
self.battery_service = BatteryService()
self.battery_service = Battery()
self.battery_icon = svg_file("battery/battery-100.svg", size=23)
@@ -344,12 +344,12 @@ class BatteryIndicator(Box):
(
self.battery_window,
self.battery_widget,
self.battery_mousecapture,
) = setup_control_center(
self.show_window,
"battery",
BatteryControl,
self,
pointing_to=self.battery_button,
show_back_button=False,
)
if self.battery_window:
@@ -367,22 +367,28 @@ class BatteryIndicator(Box):
def on_battery_direct_changed(self, *args):
self.update_modus_service_battery_state()
self.update_state()
hide_control_center(self.battery_window)
def _get_percentage(self):
return int(self.battery_service.get_property("Percentage") or 0)
return int(self.battery_service.percent)
def _get_state(self):
state_value = self.battery_service.get_property("State")
return DeviceState.get(state_value, "UNKNOWN")
if self.battery_service.charging:
return "CHARGING"
if self.battery_service.discharging:
return "DISCHARGING"
if self.battery_service.charged:
return "FULLY_CHARGED"
return "UNKNOWN"
def _is_present(self):
return bool(self.battery_service.get_property("IsPresent"))
return self.battery_service.available
def _get_time_to_empty(self):
return int(self.battery_service.get_property("TimeToEmpty") or 0)
return self.battery_service.time_remaining
def _get_time_to_full(self):
return int(self.battery_service.get_property("TimeToFull") or 0)
return self.battery_service.time_to_full
def _format_time(self, seconds: int) -> str:
return format_duration(seconds)
@@ -433,10 +439,10 @@ class BatteryIndicator(Box):
def update_state(self):
if not self._is_present():
print("[Battery] No battery detected, hiding indicator")
self.set_visible(False)
self.hide()
return
else:
self.set_visible(True)
self.show()
percentage = self._get_percentage()
state = self._get_state()
@@ -451,13 +457,13 @@ class BatteryIndicator(Box):
self.battery_label.set_label(percentage_text)
def on_battery_clicked(self, *args):
handle_indicator_click(self.battery_mousecapture)
handle_indicator_click(self.battery_window)
def close_battery(self, *args):
hide_control_center(self.battery_mousecapture)
hide_control_center(self.battery_window)
def hide_controlcenter(self, *args):
hide_control_center(self.battery_mousecapture)
hide_control_center(self.battery_window)
def destroy(self):
"""Clean up resources when the indicator is destroyed."""
@@ -466,8 +472,8 @@ class BatteryIndicator(Box):
modus_service.disconnect_by_func(self.on_battery_changed)
if hasattr(self, "battery_service") and self.battery_service:
self.battery_service.disconnect_by_func(self.on_battery_direct_changed)
except Exception:
pass
except Exception as e:
print(f"Error disconnecting battery indicator signals: {e}")
# Destroy window and widget
try:
@@ -475,8 +481,6 @@ class BatteryIndicator(Box):
self.battery_widget.destroy()
if hasattr(self, "battery_window") and self.battery_window:
self.battery_window.destroy()
if hasattr(self, "battery_mousecapture") and self.battery_mousecapture:
self.battery_mousecapture.destroy()
except Exception:
pass
@@ -494,15 +498,13 @@ def add_destroy_to_indicators():
self.bluetooth.disconnect_by_func(self.on_bluetooth_direct_changed)
self.bluetooth.disconnect_by_func(self.on_device_added)
self.bluetooth.disconnect_by_func(self.on_device_removed)
except Exception:
pass
except Exception as e:
print(f"Error disconnecting bluetooth indicator signals: {e}")
try:
if hasattr(self, "bluetooth_widget") and self.bluetooth_widget:
self.bluetooth_widget.destroy()
if hasattr(self, "bluetooth_window") and self.bluetooth_window:
self.bluetooth_window.destroy()
if hasattr(self, "bluetooth_mousecapture") and self.bluetooth_mousecapture:
self.bluetooth_mousecapture.destroy()
except Exception:
pass
Box.destroy(self)
@@ -539,15 +541,13 @@ def add_destroy_to_indicators():
)
except Exception:
pass
except Exception:
pass
except Exception as e:
print(f"Error disconnecting network indicator signals: {e}")
try:
if hasattr(self, "wifi_widget") and self.wifi_widget:
self.wifi_widget.destroy()
if hasattr(self, "wifi_window") and self.wifi_window:
self.wifi_window.destroy()
if hasattr(self, "wifi_mousecapture") and self.wifi_mousecapture:
self.wifi_mousecapture.destroy()
except Exception:
pass
Box.destroy(self)
+11 -20
View File
@@ -8,7 +8,7 @@ from fabric.widgets.wayland import WaylandWindow as Window
from services.config import on_config_change, get_config_all
from services.modus import notification_service
from shared.window.mousecapture import MouseCapture
from utils.roam import modus_service
from utils.utils import setup_cursor_hover, svg_file
from window.controlcenter.main import ModusControlCenter
@@ -78,20 +78,18 @@ class Panel(Window):
)
setup_cursor_hover(self.search, "pointer")
self.control_center = MouseCapture(
layer="top", child_window=ModusControlCenter()
)
self.control_center_btn = Button(
name="panel-button",
child=svg_file("misc/control-center.svg", size=22),
on_clicked=self.control_center.toggle_mousecapture,
)
setup_cursor_hover(self.control_center_btn, "pointer")
self.control_center.child_window._pointing_widget = self.control_center_btn
self.notification_center = MouseCapture(
layer="overlay", child_window=NotificationCenter()
self.control_center = ModusControlCenter(
parent=self, pointing_to=self.control_center_btn
)
self.control_center_btn.connect(
"clicked",
lambda *_: self.control_center.toggle(),
)
self.notification_icon = svg_file(
@@ -104,8 +102,9 @@ class Panel(Window):
on_clicked=self.on_notification_icon_clicked,
)
setup_cursor_hover(self.notification_center_btn, "pointer")
self.notification_center.child_window._pointing_widget = (
self.notification_center_btn
self.notification_center = NotificationCenter(
parent=self, pointing_to=self.notification_center_btn
)
self.datetime_btn = Button(
@@ -174,9 +173,7 @@ class Panel(Window):
def on_notification_icon_clicked(self, *args):
count = notification_service.count
if count > 0:
# Only open notification center if there are notifications
self.notification_center.toggle_mousecapture()
# Do nothing if no notifications
self.notification_center.toggle()
def update_notification_icon(self):
count = notification_service.count
@@ -198,8 +195,6 @@ class Panel(Window):
if config_data is None:
config_data = get_config_all()
print(f"[Panel] Rebuilding layout with config: {config_data}")
# Update indicator visibility directly
battery_visible = config_data.get("battery", True)
network_visible = config_data.get("network", True)
@@ -258,10 +253,6 @@ class Panel(Window):
# Force a layout recalculation
self.queue_resize()
print(
f"[Panel] Battery visibility state: {self.battery_indicator.get_visible()}"
)
def _on_config_changed(self, new_config, old_config):
print("[Panel] Config changed, checking keys...")
keys = {