feat(src/shared): introduce settings window and refine visibility configurationAdd a dedicated settings window with full CSS styling and improve UI consistency

- New Settings window implementation with styling and layout
- Updated visibility configuration to use direct configuration keys
- Simplified layer‑shell positioning logic and improved reliability

Enhancements- Refactored MouseCapture geometry calculations for cleaner code
- Integrated size‑allocate handler to maintain input region sync
- Added global About window singleton and improved toggle behavior

Bug Fixes
- Fixed potential index errors in MouseCapture coordinate calculations- Resolved missing imports and path issues for icon assets
This commit is contained in:
sankalp committed 2026-05-03 22:06:35 +05:45
1 parent ebd50e585c
commit 704c8b229c
5 files changed
+202 -68

No files matched your search

+11 -11
View File
@@ -72,17 +72,17 @@ if os.path.exists(CONFIG_FILE):
)
PANEL_COMPONENTS_VISIBILITY = {
"imac_button": config.get("imac_button_visible", True),
"systray": config.get("systray_visible", True),
"control_center": config.get("control_center_visible", True),
"search": config.get("search_visible", True),
"global_menu": config.get("global_menu_visible", True),
"network": config.get("network_visible", True),
"battery": config.get("battery_visible", True),
"notification_center": config.get("notification_center_visible", True),
"workspace_indicator": config.get("workspace_indicator_visible", True),
"bluetooth": config.get("bluetooth_visible", True),
"date_time": config.get("date_time_visible", True),
"imac_button": config.get("imac_button", True),
"systray": config.get("systray", True),
"control_center": config.get("control_center", True),
"search": config.get("search", True),
"global_menu": config.get("global_menu", True),
"network": config.get("network", True),
"battery": config.get("battery", True),
"notification_center": config.get("notification_center", True),
"workspace_indicator": config.get("workspace_indicator", True),
"bluetooth": config.get("bluetooth", True),
"date_time": config.get("date_time", True),
}
else:
+14 -2
View File
@@ -431,6 +431,7 @@ class AboutApp(Gtk.Window):
class About(Gtk.Window):
def __init__(self):
super().__init__(title="About Menu")
self.set_wmclass("modus-about", "Modus")
self.set_default_size(300, 550)
self.set_size_request(300, 500)
self.set_resizable(False)
@@ -447,7 +448,7 @@ class About(Gtk.Window):
# Logo
logo_box = Gtk.Box(halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER)
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(
get_relative_path("../assets/icons/misc/imac.svg"),
get_relative_path("../../assets/icons/misc/imac.svg"),
158,
108,
preserve_aspect_ratio=True,
@@ -555,8 +556,19 @@ class About(Gtk.Window):
# TODO: Implement the logic to open more information
pass
def toggle(self, b):
def toggle(self, b=None):
if self.get_visible():
self.hide()
else:
self.show_all()
self.present()
_about_window = None
def get_about_window():
global _about_window
if _about_window is None:
_about_window = About()
return _about_window
+1
View File
@@ -17,6 +17,7 @@
@import url("./todo.css");
@import url("./launcher.css");
@import url("./switcher.css");
@import url("./settings.css");
* {
all: unset;
+146
View File
@@ -0,0 +1,146 @@
#settings-window {
background-color: var(--background);
border-radius: 12px;
border: 1px solid var(--border);
min-width: 850px;
min-height: 600px;
}
#settings-main-box {
padding: 0;
}
#settings-sidebar {
background-color: rgba(0, 0, 0, 0.05);
padding: 20px 10px;
min-width: 200px;
border-right: 1px solid var(--border);
}
#settings-sidebar-button {
padding: 10px 15px;
border-radius: 8px;
margin-bottom: 2px;
}
#settings-sidebar-button:hover {
background-color: rgba(255, 255, 255, 0.05);
}
#settings-sidebar-button Label {
font-size: 14px;
font-weight: 500;
}
#settings-sidebar-button.active {
background-color: var(--accent);
}
#settings-sidebar-button.active Label {
color: var(--accent-foreground);
}
#settings-content-wrapper {
padding: 30px;
}
#settings-page-title {
font-size: 24px;
font-weight: 700;
margin-bottom: 20px;
color: var(--foreground);
}
#settings-row {
padding: 12px 15px;
border-radius: 8px;
background-color: rgba(255, 255, 255, 0.03);
margin-bottom: 2px;
}
#settings-row-label {
font-size: 15px;
font-weight: 500;
}
#settings-row-description {
font-size: 12px;
color: var(--foreground-dim);
}
#settings-switch {
background-color: rgba(255, 255, 255, 0.1);
padding: 6px 12px;
border-radius: 20px;
min-width: 50px;
transition: all 0.2s ease;
}
#settings-switch.active {
background-color: var(--accent);
color: var(--accent-foreground);
}
#settings-entry {
background-color: rgba(255, 255, 255, 0.05);
padding: 8px 12px;
border-radius: 6px;
border: 1px solid var(--border);
min-width: 200px;
color: var(--foreground);
}
#settings-entry:focus {
border-color: var(--accent);
}
#settings-combo {
background-color: rgba(255, 255, 255, 0.05);
padding: 4px 8px;
border-radius: 6px;
border: 1px solid var(--border);
min-width: 150px;
color: var(--foreground);
}
#settings-combo:hover {
background-color: rgba(255, 255, 255, 0.1);
}
#settings-list-tags {
margin-bottom: 5px;
}
#settings-list-tag {
background-color: rgba(255, 255, 255, 0.08);
border-radius: 15px;
padding: 4px 10px;
margin-right: 5px;
margin-bottom: 5px;
}
#settings-list-tag:hover {
background-color: rgba(255, 255, 255, 0.15);
}
#settings-list-tag Label {
font-size: 13px;
}
#settings-list-tag-close {
color: var(--foreground-dim);
font-size: 10px;
margin-left: 3px;
}
#settings-list-tag-close:hover {
color: var(--accent);
}
#settings-list-entry {
background-color: rgba(255, 255, 255, 0.03);
border: 1px dashed var(--border);
padding: 6px 12px;
border-radius: 6px;
font-size: 13px;
}
+30 -55
View File
@@ -1,4 +1,4 @@
from fabric.utils import Any, Gdk, cairo
from fabric.utils import Any, Gdk, cairo, GLib
from fabric.widgets.eventbox import EventBox
from fabric.widgets.wayland import WaylandWindow as Window
from fabric.widgets.widget import Widget
@@ -75,52 +75,25 @@ class MouseCapture(Window):
return False
def _get_child_window_bounds(self) -> tuple[int, int, int, int]:
"""Calculates absolute screen bounds for the child window using Layer Shell properties"""
"""Calculates absolute screen bounds for the child window using Gdk origin"""
try:
# Get monitor geometry
monitor = self._hyprland.display.get_monitor_at_window(
self.child_window.get_window()
)
if not monitor:
monitor = self._hyprland.display.get_default_monitor()
window = self.child_window.get_window()
if not window:
# Fallback to allocation if window isn't mapped yet
alloc = self.child_window.get_allocation()
return 0, 0, alloc.width, alloc.height
geom = monitor.get_geometry()
mx, my, mw, mh = geom.x, geom.y, geom.width, geom.height
# get_origin provides absolute screen coordinates
success, x, y = window.get_origin()
if not success:
alloc = self.child_window.get_allocation()
return 0, 0, alloc.width, alloc.height
# Get child window properties
alloc = self.child_window.get_allocation()
ww, wh = alloc.width, alloc.height
# fabric's WaylandWindow usually has anchor and margin accessible
anchor = self.child_window.anchor
margin = self.child_window.margin # (top, right, bottom, left)
# Calculation logic based on Layer Shell anchors
# X coordinate
if GtkLayerShell.Edge.LEFT in anchor and GtkLayerShell.Edge.RIGHT in anchor:
x = mx + margin[3]
elif GtkLayerShell.Edge.LEFT in anchor:
x = mx + margin[3]
elif GtkLayerShell.Edge.RIGHT in anchor:
x = mx + mw - ww - margin[1]
else: # Centered horizontally
x = mx + (mw - ww) // 2 + margin[3] - margin[1]
# Y coordinate
if GtkLayerShell.Edge.TOP in anchor and GtkLayerShell.Edge.BOTTOM in anchor:
y = my + margin[0]
elif GtkLayerShell.Edge.TOP in anchor:
y = my + margin[0]
elif GtkLayerShell.Edge.BOTTOM in anchor:
y = my + mh - wh - margin[2]
else: # Centered vertically
y = my + (mh - wh) // 2 + margin[0] - margin[2]
return x, y, ww, wh
return x, y, alloc.width, alloc.height
except Exception as e:
print(f"Error calculating child window bounds: {e}")
# Fallback to allocation if possible, though likely (0,0)
alloc = self.child_window.get_allocation()
return 0, 0, alloc.width, alloc.height
@@ -128,27 +101,19 @@ class MouseCapture(Window):
"""Calculates absolute screen bounds for a widget in another window"""
try:
toplevel = widget.get_toplevel()
if not toplevel:
if not toplevel or not toplevel.get_window():
return 0, 0, 0, 0
# Get monitor geometry
monitor = self._hyprland.display.get_monitor_at_window(
toplevel.get_window()
)
if not monitor:
monitor = self._hyprland.display.get_default_monitor()
geom = monitor.get_geometry()
mx, my = geom.x, geom.y
# Get the origin of the window containing the widget
success, wx, wy = toplevel.get_window().get_origin()
if not success:
return 0, 0, 0, 0
# Get relative position within toplevel
alloc = widget.get_allocation()
x, y = widget.translate_coordinates(toplevel, 0, 0) or (0, 0)
rx, ry = widget.translate_coordinates(toplevel, 0, 0) or (0, 0)
# If the toplevel is a Layer Shell window, its (0,0) is monitor relative
# but we need to account for its own margin/anchor if it's not full screen.
# However, for the Panel (which is usually top-anchored), (0,0) is (monitor_x, monitor_y).
return mx + x, my + y, alloc.width, alloc.height
return wx + rx, wy + ry, alloc.width, alloc.height
except Exception as e:
print(f"Error calculating widget absolute bounds: {e}")
return 0, 0, 0, 0
@@ -250,13 +215,23 @@ class MouseCapture(Window):
self.set_child_window_visible(True)
def hide_child_window(self, widget: Widget = None, event: Any = None) -> None:
self.set_child_window_visible(False)
def set_child_window_visible(self, visible: bool) -> None:
if visible:
# Connect to child window size changes to keep input region in sync
self._size_handler = self.child_window.connect(
"size-allocate", lambda *_: self.update_input_region()
)
self.child_window.show()
self.show()
# Force update with a small delay to ensure window is mapped
GLib.timeout_add(50, self.update_input_region)
else:
if hasattr(self, "_size_handler") and self._size_handler:
self.child_window.disconnect(self._size_handler)
self._size_handler = None
self.child_window.hide()
self.hide()