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:
5 files changed
+202
-68
No files matched your search
+11
-11
@@ -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:
|
||||
|
||||
@@ -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
|
||||
@@ -17,6 +17,7 @@
|
||||
@import url("./todo.css");
|
||||
@import url("./launcher.css");
|
||||
@import url("./switcher.css");
|
||||
@import url("./settings.css");
|
||||
|
||||
* {
|
||||
all: unset;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
|
||||
Reference in new issue
Block a user