From e004add28fe4a80c52139f2eed53266e86e24203 Mon Sep 17 00:00:00 2001 From: pax Date: Mon, 13 Apr 2026 21:49:35 -0500 Subject: [PATCH] popout: let open animation play on first fit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit resize() and resize_and_move() gain an animate flag — when True, skip the no_anim setprop so Hyprland's windowsIn/popin animation plays through. Popout passes animate=_first_fit_pending so the first fit after open animates; subsequent navigation fits still suppress anim to avoid resize flicker. behavior change: popout now animates in on open instead of snapping. --- HYPRLAND.md | 4 +++- booru_viewer/gui/popout/hyprland.py | 9 +++++---- booru_viewer/gui/popout/window.py | 7 +++++-- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/HYPRLAND.md b/HYPRLAND.md index 6c57d56..c8fd552 100644 --- a/HYPRLAND.md +++ b/HYPRLAND.md @@ -89,7 +89,9 @@ windowrule { popout geometry - `dispatch togglefloating` on the main window at launch - `dispatch setprop address: no_anim 1` applied during popout - transitions + transitions (skipped on the first fit after open so Hyprland's + `windowsIn` / `popin` animation can play — subsequent navigation + fits still suppress anim to avoid resize flicker) - The startup "prime" sequence that warms Hyprland's per-window floating cache diff --git a/booru_viewer/gui/popout/hyprland.py b/booru_viewer/gui/popout/hyprland.py index 5ceaafd..9638172 100644 --- a/booru_viewer/gui/popout/hyprland.py +++ b/booru_viewer/gui/popout/hyprland.py @@ -54,7 +54,7 @@ def get_window(window_title: str) -> dict | None: return None -def resize(window_title: str, w: int, h: int) -> None: +def resize(window_title: str, w: int, h: int, animate: bool = False) -> None: """Ask Hyprland to resize the popout and lock its aspect ratio. No-op on non-Hyprland systems. Tiled windows skip the resize @@ -86,12 +86,12 @@ def resize(window_title: str, w: int, h: int) -> None: if not win.get("floating"): # Tiled — don't resize (fights the layout). Optionally set # aspect lock and no_anim depending on the env vars. - if rules_on: + if rules_on and not animate: cmds.append(f"dispatch setprop address:{addr} no_anim 1") if aspect_on: cmds.append(f"dispatch setprop address:{addr} keep_aspect_ratio 1") else: - if rules_on: + if rules_on and not animate: cmds.append(f"dispatch setprop address:{addr} no_anim 1") if aspect_on: cmds.append(f"dispatch setprop address:{addr} keep_aspect_ratio 0") @@ -111,6 +111,7 @@ def resize_and_move( x: int, y: int, win: dict | None = None, + animate: bool = False, ) -> None: """Atomically resize and move the popout via a single hyprctl batch. @@ -140,7 +141,7 @@ def resize_and_move( if not addr: return cmds: list[str] = [] - if rules_on: + if rules_on and not animate: cmds.append(f"dispatch setprop address:{addr} no_anim 1") if aspect_on: cmds.append(f"dispatch setprop address:{addr} keep_aspect_ratio 0") diff --git a/booru_viewer/gui/popout/window.py b/booru_viewer/gui/popout/window.py index 68ce1bc..0ede060 100644 --- a/booru_viewer/gui/popout/window.py +++ b/booru_viewer/gui/popout/window.py @@ -1325,7 +1325,7 @@ class FullscreenPreview(QMainWindow): else: floating = None if floating is False: - hyprland.resize(self.windowTitle(), 0, 0) # tiled: just set keep_aspect_ratio + hyprland.resize(self.windowTitle(), 0, 0, animate=self._first_fit_pending) # tiled: just set keep_aspect_ratio self._tiled_pending_content = (content_w, content_h) return self._tiled_pending_content = None @@ -1373,7 +1373,10 @@ class FullscreenPreview(QMainWindow): # Hyprland: hyprctl is the sole authority. Calling self.resize() # here would race with the batch below and produce visible flashing # when the window also has to move. - hyprland.resize_and_move(self.windowTitle(), w, h, x, y, win=win) + hyprland.resize_and_move( + self.windowTitle(), w, h, x, y, win=win, + animate=self._first_fit_pending, + ) else: # Non-Hyprland fallback: Qt drives geometry directly. Use # setGeometry with the computed top-left rather than resize()