From 704c8b229ca36343ccd25ae1955f1a1c728aed5a Mon Sep 17 00:00:00 2001 From: S4NKALP Date: Sun, 3 May 2026 22:06:35 +0545 Subject: [PATCH] =?UTF-8?q?feat(src/shared):=20introduce=20settings=20wind?= =?UTF-8?q?ow=20and=20refine=20visibility=20configurationAdd=20a=20dedicat?= =?UTF-8?q?ed=20settings=20window=20with=20full=20CSS=20styling=20and=20im?= =?UTF-8?q?prove=20UI=20consistency=20-=20New=20Settings=20window=20implem?= =?UTF-8?q?entation=20with=20styling=20and=20layout=20-=20Updated=20visibi?= =?UTF-8?q?lity=20configuration=20to=20use=20direct=20configuration=20keys?= =?UTF-8?q?=20-=20Simplified=20layer=E2=80=91shell=20positioning=20logic?= =?UTF-8?q?=20and=20improved=20reliability?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/shared/data.py | 22 ++--- src/shared/dialogs/about.py | 16 +++- src/shared/styles/main.css | 1 + src/shared/styles/settings.css | 146 ++++++++++++++++++++++++++++++ src/shared/window/mousecapture.py | 85 ++++++----------- 5 files changed, 202 insertions(+), 68 deletions(-) create mode 100644 src/shared/styles/settings.css diff --git a/src/shared/data.py b/src/shared/data.py index 43f9241..429635e 100644 --- a/src/shared/data.py +++ b/src/shared/data.py @@ -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: diff --git a/src/shared/dialogs/about.py b/src/shared/dialogs/about.py index e203369..0c41648 100644 --- a/src/shared/dialogs/about.py +++ b/src/shared/dialogs/about.py @@ -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 diff --git a/src/shared/styles/main.css b/src/shared/styles/main.css index 102278e..e4abf1b 100644 --- a/src/shared/styles/main.css +++ b/src/shared/styles/main.css @@ -17,6 +17,7 @@ @import url("./todo.css"); @import url("./launcher.css"); @import url("./switcher.css"); +@import url("./settings.css"); * { all: unset; diff --git a/src/shared/styles/settings.css b/src/shared/styles/settings.css new file mode 100644 index 0000000..4f61e87 --- /dev/null +++ b/src/shared/styles/settings.css @@ -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; +} \ No newline at end of file diff --git a/src/shared/window/mousecapture.py b/src/shared/window/mousecapture.py index 088f495..5ef5181 100644 --- a/src/shared/window/mousecapture.py +++ b/src/shared/window/mousecapture.py @@ -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()