pax 93459dfff6 UI overhaul: icon buttons, video controls, popout anchor, layout flip, compact top bar
- Preview/popout toolbar: icon buttons (☆/★, ↓/✕, ⊘, ⊗, ⧉) with QSS
  object names (#_tb_bookmark, #_tb_save, etc.) for theme targeting
- Video controls: QPainter-drawn icons for play/pause, volume/mute;
  text labels for loop/once/next and autoplay
- Popout anchor setting: resize pivot (center/tl/tr/bl/br) controls
  which corner stays fixed on aspect change, works on all platforms
- Hyprland monitor reserved areas: reads waybar exclusive zones from
  hyprctl monitors -j for correct edge positioning
- Layout flip setting: swap grid and preview sides
- Compact top bar: AdjustToContents combos, tighter spacing, named
  containers (#_top_bar, #_nav_bar) for QSS targeting
- Reduced main window minimum size from 900x600 to 740x400
- Trimmed bundled QSS: removed 12 unused widget selectors, added
  popout overlay font-weight/size, regenerated all 12 theme files
- Updated themes/README.md with icon button reference
2026-04-10 19:58:11 -05:00

63 lines
2.4 KiB
Python

"""Popout viewport math: persistent intent + drift tolerance."""
from __future__ import annotations
from typing import NamedTuple
class Viewport(NamedTuple):
"""Where and how large the user wants popout content to appear.
Three numbers + an anchor mode, no aspect. Aspect is a property of
the currently-displayed post and is recomputed from actual content
on every navigation. The viewport stays put across navigations; the
window rect is a derived projection (Viewport, content_aspect) →
(x,y,w,h).
`long_side` is the binding edge length: for landscape it becomes
width, for portrait it becomes height. Symmetric across the two
orientations, which is the property that breaks the
width-anchor ratchet that the previous `_fit_to_content` had.
`anchor` controls which point of the window stays fixed across
navigations as the window size changes with aspect ratio:
``"center"`` (default) pins the window center; ``"tl"``/``"tr"``/
``"bl"``/``"br"`` pin the corresponding corner. The window
grows/shrinks away from the anchored corner. The user can drag the
window anywhere — the anchor only affects resize direction, not
screen position.
`center_x`/`center_y` hold the anchor point coordinates (center
of the window in center mode, the pinned corner in corner modes).
"""
center_x: float
center_y: float
long_side: float
anchor: str = "center"
def anchor_point(x: float, y: float, w: float, h: float, anchor: str) -> tuple[float, float]:
"""Extract the anchor point from a window rect based on anchor mode."""
if anchor == "tl":
return (x, y)
if anchor == "tr":
return (x + w, y)
if anchor == "bl":
return (x, y + h)
if anchor == "br":
return (x + w, y + h)
return (x + w / 2, y + h / 2)
# Maximum drift between our last-dispatched window rect and the current
# Hyprland-reported rect that we still treat as "no user action happened."
# Anything within this tolerance is absorbed (Hyprland gap rounding,
# subpixel accumulation, decoration accounting). Anything beyond it is
# treated as "the user dragged or resized the window externally" and the
# persistent viewport gets updated from current state.
#
# 2px is small enough not to false-positive on real user drags (which
# are always tens of pixels minimum) and large enough to absorb the
# 1-2px per-nav drift that compounds across many navigations.
_DRIFT_TOLERANCE = 2