From 183b76900050b91c415be98014d05b1965b932ac Mon Sep 17 00:00:00 2001 From: lm Date: Sun, 19 Oct 2025 18:36:27 +0200 Subject: [PATCH] Revert "Restore native title bar with theme-aware styling" This reverts commit 07d76798896cc73fa9a6b73f2c83c274e3927561. --- app/app.py | 98 ++++++++++++++++++++++++++---------------------- app/gui/theme.py | 4 -- app/gui/ui.py | 16 +------- 3 files changed, 54 insertions(+), 64 deletions(-) diff --git a/app/app.py b/app/app.py index 159f53b..d0f6151 100644 --- a/app/app.py +++ b/app/app.py @@ -5,6 +5,7 @@ from __future__ import annotations import ctypes import platform import tkinter as tk +from ctypes import wintypes from importlib import resources from .gui import ColorPickerMixin, ExclusionMixin, ThemeMixin, UIBuilderMixin @@ -76,9 +77,6 @@ class ICRAApp( self.bring_to_front() def _setup_window(self) -> None: - system = platform.system() - self.use_native_titlebar = system == "Windows" - screen_width = self.root.winfo_screenwidth() screen_height = self.root.winfo_screenheight() default_width = int(screen_width * 0.8) @@ -86,15 +84,8 @@ class ICRAApp( default_x = (screen_width - default_width) // 2 default_y = (screen_height - default_height) // 4 self._window_geometry = f"{default_width}x{default_height}+{default_x}+{default_y}" - - if self.use_native_titlebar: - self._is_maximized = False - self.root.geometry(self._window_geometry) - self.root.after(0, lambda: self.root.state("zoomed")) - else: - self.root.overrideredirect(True) - self._is_maximized = True - self.root.geometry(f"{screen_width}x{screen_height}+0+0") + self._is_maximized = True + self.root.geometry(f"{screen_width}x{screen_height}+0+0") self.root.configure(bg="#f2f2f7") self._window_icon_ref = None self._apply_window_icon() @@ -151,55 +142,72 @@ class ICRAApp( self._window_icon_ref = None def _init_window_chrome(self) -> None: - """Configure window chrome based on platform.""" + """Configure a borderless window while retaining a taskbar entry.""" try: - if self.use_native_titlebar: - if platform.system() == "Windows": - self.root.after(0, self._ensure_taskbar_entry) - initial_mode = getattr(self, "theme", "light") - self.root.after(0, lambda: self._apply_os_titlebar_theme(initial_mode)) - self.root.bind( - "", - lambda _e: self._apply_os_titlebar_theme(getattr(self, "theme", "light")), - add="+", - ) - self.root.bind("", lambda _e: self._ensure_taskbar_entry(), add="+") - else: - self.root.bind("", self._restore_borderless) - self.root.after(0, self._restore_borderless) + system = platform.system() + if system == "Windows": + self.root.after(0, self._apply_windows_borderless_style) self.root.after(0, self._ensure_taskbar_entry) + self.root.bind("", lambda _e: self._apply_windows_borderless_style(), add="+") + self.root.bind("", lambda _e: self._ensure_taskbar_entry(), add="+") + else: + self.root.overrideredirect(True) except Exception: self.root.overrideredirect(True) - def _restore_borderless(self, _event=None) -> None: - try: - self.root.overrideredirect(True) - except Exception: - pass - - def _apply_os_titlebar_theme(self, mode: str | None = None) -> None: + def _apply_windows_borderless_style(self) -> None: try: if platform.system() != "Windows": return hwnd = self.root.winfo_id() if not hwnd: - self.root.after(50, lambda: self._apply_os_titlebar_theme(mode)) + self.root.after(50, self._apply_windows_borderless_style) return + user32 = ctypes.windll.user32 # type: ignore[attr-defined] + GWL_STYLE = -16 + WS_CAPTION = 0x00C00000 + WS_THICKFRAME = 0x00040000 + WS_BORDER = 0x00800000 + SWP_NOSIZE = 0x0001 + SWP_NOMOVE = 0x0002 + SWP_NOZORDER = 0x0004 + SWP_FRAMECHANGED = 0x0020 + + set_window_long = getattr(user32, "SetWindowLongPtrW", user32.SetWindowLongW) + get_window_long = getattr(user32, "GetWindowLongPtrW", user32.GetWindowLongW) + ptr_type = ctypes.c_longlong if ctypes.sizeof(ctypes.c_void_p) == 8 else ctypes.c_long + get_window_long.restype = ptr_type # type: ignore[attr-defined] + get_window_long.argtypes = [wintypes.HWND, ctypes.c_int] # type: ignore[attr-defined] + set_window_long.restype = ptr_type # type: ignore[attr-defined] + set_window_long.argtypes = [wintypes.HWND, ctypes.c_int, ptr_type] # type: ignore[attr-defined] + + style = get_window_long(hwnd, GWL_STYLE) + new_style = style & ~(WS_CAPTION | WS_THICKFRAME | WS_BORDER) + if new_style != style: + set_window_long(hwnd, GWL_STYLE, ptr_type(new_style)) + user32.SetWindowPos( + hwnd, + 0, + 0, + 0, + 0, + 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED, + ) try: dwmapi = ctypes.windll.dwmapi # type: ignore[attr-defined] + DWMWA_USE_IMMERSIVE_DARK_MODE = 20 + value = ctypes.c_int(1) + dwmapi.DwmSetWindowAttribute( + hwnd, + ctypes.c_uint(DWMWA_USE_IMMERSIVE_DARK_MODE), + ctypes.byref(value), + ctypes.sizeof(value), + ) except Exception: - return - - attribute = ctypes.c_uint(20) # DWMWA_USE_IMMERSIVE_DARK_MODE - value = ctypes.c_int(1 if (mode or self.theme) == "dark" else 0) - dwmapi.DwmSetWindowAttribute( - hwnd, - attribute, - ctypes.byref(value), - ctypes.sizeof(value), - ) + pass except Exception: pass diff --git a/app/gui/theme.py b/app/gui/theme.py index 855e1fe..24d23b0 100644 --- a/app/gui/theme.py +++ b/app/gui/theme.py @@ -71,10 +71,6 @@ class ThemeMixin: if callable(canvas_refresher): canvas_refresher() - os_theme_hook = getattr(self, "_apply_os_titlebar_theme", None) - if callable(os_theme_hook): - os_theme_hook(self.theme) - def detect_system_theme(self) -> str: """Best-effort detection of the OS theme preference.""" try: diff --git a/app/gui/ui.py b/app/gui/ui.py index f5180d8..a347e20 100644 --- a/app/gui/ui.py +++ b/app/gui/ui.py @@ -12,9 +12,7 @@ class UIBuilderMixin: """Constructs the Tkinter UI and common widgets.""" def setup_ui(self) -> None: - self._custom_titlebar = not getattr(self, "use_native_titlebar", False) - if self._custom_titlebar: - self._create_titlebar() + self._create_titlebar() toolbar = ttk.Frame(self.root) toolbar.pack(fill=tk.X, padx=12, pady=(4, 2)) @@ -467,8 +465,6 @@ class UIBuilderMixin: pass def _start_window_drag(self, event) -> None: - if not getattr(self, "_custom_titlebar", True): - return if getattr(self, "_is_maximized", False): cursor_x, cursor_y = event.x_root, event.y_root self._toggle_maximize_window(force_state=False) @@ -480,8 +476,6 @@ class UIBuilderMixin: self._drag_offset = (event.x_root - self.root.winfo_rootx(), event.y_root - self.root.winfo_rooty()) def _perform_window_drag(self, event) -> None: - if not getattr(self, "_custom_titlebar", True): - return offset = getattr(self, "_drag_offset", None) if offset is None: return @@ -531,8 +525,6 @@ class UIBuilderMixin: return None def _maximize_window(self) -> None: - if not getattr(self, "_custom_titlebar", True): - return self._remember_window_geometry() work_area = self._monitor_work_area() if work_area is None: @@ -551,8 +543,6 @@ class UIBuilderMixin: self._update_maximize_button() def _restore_window(self) -> None: - if not getattr(self, "_custom_titlebar", True): - return geometry = getattr(self, "_window_geometry", None) if not geometry: screen_width = self.root.winfo_screenwidth() @@ -567,8 +557,6 @@ class UIBuilderMixin: self._update_maximize_button() def _toggle_maximize_window(self, force_state: bool | None = None) -> None: - if not getattr(self, "_custom_titlebar", True): - return desired = force_state if force_state is not None else not getattr(self, "_is_maximized", False) if desired: self._maximize_window() @@ -576,8 +564,6 @@ class UIBuilderMixin: self._restore_window() def _minimize_window(self) -> None: - if not getattr(self, "_custom_titlebar", True): - return try: self._remember_window_geometry() self.root.iconify()