Popout: fix first-fit aspect lock race, fill images to window, tighten combo/button padding across all themes

Three fixes that all surfaced from the bookmark/library decoupling
shake-out:

  - Popout first-image aspect-lock race: _fit_to_content used to call
    _is_hypr_floating which returned None for both "not Hyprland" and
    "Hyprland but the window isn't visible to hyprctl yet". The latter
    happens on the very first popout open because the wm:openWindow
    event hasn't been processed when set_media fires. The method then
    fell through to a plain Qt resize and skipped the
    keep_aspect_ratio setprop, so the first image always opened
    unlocked and only subsequent navigations got the right shape. Now
    we inline the env-var check, distinguish the two None cases, and
    retry on Hyprland with a 40ms backoff (capped at 5 attempts /
    200ms total) when the window isn't registered yet.

  - Image fill in popout (and embedded preview): ImageViewer._fit_to_view
    used min(scale_w, scale_h, 1.0) which clamped the zoom at native
    pixel size, so a smaller image in a larger window centered with
    letterbox space around it. Dropped the 1.0 cap so images scale up
    to fill the available view, matching how the video player fills
    its widget. Combined with the popout's keep_aspect_ratio, the
    window matches the image's aspect AND the image fills it cleanly.
    Tiled popouts with mismatched aspect still letterbox (intentional —
    the layout owns the window shape).

  - Combo + button padding tightening across all 12 bundled themes
    and Library sort combo: QPushButton padding 2px 8px → 2px 6px,
    QComboBox padding 2px 6px → 2px 4px, QComboBox::drop-down width
    18px → 14px. Saves 8px non-text width per combo and 4px per
    button, so the new "Post ID" sort entry fits in 75px instead of
    needing 90. Library sort combo bumped from "Name" (lexicographic)
    to "Post ID" with a numeric stem sort that handles non-digit
    stems gracefully.
This commit is contained in:
pax 2026-04-07 20:48:09 -05:00
parent b89baaae34
commit baa910ac81
14 changed files with 87 additions and 44 deletions

View File

@ -76,8 +76,10 @@ class LibraryView(QWidget):
top.addWidget(self._folder_combo) top.addWidget(self._folder_combo)
self._sort_combo = QComboBox() self._sort_combo = QComboBox()
self._sort_combo.addItems(["Date", "Name", "Size"]) self._sort_combo.addItems(["Date", "Post ID", "Size"])
self._sort_combo.setFixedWidth(80) # 75 is the tight floor: 68 clipped the trailing D under the
# bundled themes (font metrics ate more than the math suggested).
self._sort_combo.setFixedWidth(75)
self._sort_combo.currentTextChanged.connect(lambda _: self.refresh()) self._sort_combo.currentTextChanged.connect(lambda _: self.refresh())
top.addWidget(self._sort_combo) top.addWidget(self._sort_combo)
@ -252,8 +254,16 @@ class LibraryView(QWidget):
def _sort_files(self) -> None: def _sort_files(self) -> None:
mode = self._sort_combo.currentText() mode = self._sort_combo.currentText()
if mode == "Name": if mode == "Post ID":
self._files.sort(key=lambda p: p.name.lower()) # Numeric sort by post id (filename stem). Library files are
# named {post_id}.{ext} in normal usage; anything with a
# non-digit stem (someone manually dropped a file in) sorts
# to the end alphabetically so the numeric ordering of real
# posts isn't disrupted by stray names.
def _key(p: Path) -> tuple:
stem = p.stem
return (0, int(stem)) if stem.isdigit() else (1, stem.lower())
self._files.sort(key=_key)
elif mode == "Size": elif mode == "Size":
self._files.sort(key=lambda p: p.stat().st_size, reverse=True) self._files.sort(key=lambda p: p.stat().st_size, reverse=True)
else: else:

View File

@ -380,11 +380,37 @@ class FullscreenPreview(QMainWindow):
return None # not Hyprland return None # not Hyprland
return bool(win.get("floating")) return bool(win.get("floating"))
def _fit_to_content(self, content_w: int, content_h: int) -> None: def _fit_to_content(self, content_w: int, content_h: int, _retry: int = 0) -> None:
"""Size window to fit content. Width preserved, height from aspect ratio, clamped to screen.""" """Size window to fit content. Width preserved, height from aspect ratio, clamped to screen.
Distinguishes "not on Hyprland" (Qt drives geometry, no aspect
lock available) from "on Hyprland but the window isn't visible
to hyprctl yet" (the very first call after a popout open races
the wm:openWindow event `hyprctl clients -j` returns no entry
for our title for ~tens of ms). The latter case used to fall
through to a plain Qt resize and skip the keep_aspect_ratio
setprop entirely, so the *first* image popout always opened
without aspect locking and only subsequent navigations got the
right shape. Now we retry with a short backoff when on Hyprland
and the window isn't found, capped so a real "not Hyprland"
signal can't loop.
"""
if self.isFullScreen() or content_w <= 0 or content_h <= 0: if self.isFullScreen() or content_w <= 0 or content_h <= 0:
return return
floating = self._is_hypr_floating() import os
on_hypr = bool(os.environ.get("HYPRLAND_INSTANCE_SIGNATURE"))
if on_hypr:
win = self._hyprctl_get_window()
if win is None:
if _retry < 5:
QTimer.singleShot(
40,
lambda: self._fit_to_content(content_w, content_h, _retry + 1),
)
return
floating = bool(win.get("floating"))
else:
floating = None
if floating is False: if floating is False:
self._hyprctl_resize(0, 0) # tiled: just set keep_aspect_ratio self._hyprctl_resize(0, 0) # tiled: just set keep_aspect_ratio
return return
@ -789,7 +815,14 @@ class ImageViewer(QWidget):
return return
scale_w = vw / pw scale_w = vw / pw
scale_h = vh / ph scale_h = vh / ph
self._zoom = min(scale_w, scale_h, 1.0) # No 1.0 cap — scale up to fill the available view, matching how
# the video player fills its widget. In the popout the window is
# already aspect-locked to the image's aspect, so scaling up
# produces a clean fill with no letterbox. In the embedded
# preview the user can drag the splitter past the image's native
# size; letting it scale up there fills the pane the same way
# the popout does.
self._zoom = min(scale_w, scale_h)
self._offset = QPointF( self._offset = QPointF(
(vw - pw * self._zoom) / 2, (vw - pw * self._zoom) / 2,
(vh - ph * self._zoom) / 2, (vh - ph * self._zoom) / 2,

View File

@ -60,7 +60,7 @@ QPushButton {
color: ${text}; color: ${text};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
border-radius: 4px; border-radius: 4px;
padding: 2px 8px; padding: 2px 6px;
min-height: 17px; min-height: 17px;
} }
QPushButton:hover { QPushButton:hover {
@ -145,7 +145,7 @@ QComboBox {
color: ${text}; color: ${text};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
border-radius: 4px; border-radius: 4px;
padding: 2px 6px; padding: 2px 4px;
min-height: 16px; min-height: 16px;
} }
QComboBox:hover { QComboBox:hover {
@ -156,7 +156,7 @@ QComboBox:focus {
} }
QComboBox::drop-down { QComboBox::drop-down {
border: none; border: none;
width: 18px; width: 14px;
} }
QComboBox QAbstractItemView { QComboBox QAbstractItemView {
background-color: ${bg_subtle}; background-color: ${bg_subtle};

View File

@ -59,7 +59,7 @@ QPushButton {
background-color: ${bg_subtle}; background-color: ${bg_subtle};
color: ${text}; color: ${text};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
padding: 2px 8px; padding: 2px 6px;
min-height: 17px; min-height: 17px;
} }
QPushButton:hover { QPushButton:hover {
@ -141,7 +141,7 @@ QComboBox {
background-color: ${bg_subtle}; background-color: ${bg_subtle};
color: ${text}; color: ${text};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
padding: 2px 6px; padding: 2px 4px;
min-height: 16px; min-height: 16px;
} }
QComboBox:hover { QComboBox:hover {
@ -152,7 +152,7 @@ QComboBox:focus {
} }
QComboBox::drop-down { QComboBox::drop-down {
border: none; border: none;
width: 18px; width: 14px;
} }
QComboBox QAbstractItemView { QComboBox QAbstractItemView {
background-color: ${bg_subtle}; background-color: ${bg_subtle};

View File

@ -60,7 +60,7 @@ QPushButton {
color: ${text}; color: ${text};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
border-radius: 4px; border-radius: 4px;
padding: 2px 8px; padding: 2px 6px;
min-height: 17px; min-height: 17px;
} }
QPushButton:hover { QPushButton:hover {
@ -145,7 +145,7 @@ QComboBox {
color: ${text}; color: ${text};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
border-radius: 4px; border-radius: 4px;
padding: 2px 6px; padding: 2px 4px;
min-height: 16px; min-height: 16px;
} }
QComboBox:hover { QComboBox:hover {
@ -156,7 +156,7 @@ QComboBox:focus {
} }
QComboBox::drop-down { QComboBox::drop-down {
border: none; border: none;
width: 18px; width: 14px;
} }
QComboBox QAbstractItemView { QComboBox QAbstractItemView {
background-color: ${bg_subtle}; background-color: ${bg_subtle};

View File

@ -59,7 +59,7 @@ QPushButton {
background-color: ${bg_subtle}; background-color: ${bg_subtle};
color: ${text}; color: ${text};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
padding: 2px 8px; padding: 2px 6px;
min-height: 17px; min-height: 17px;
} }
QPushButton:hover { QPushButton:hover {
@ -141,7 +141,7 @@ QComboBox {
background-color: ${bg_subtle}; background-color: ${bg_subtle};
color: ${text}; color: ${text};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
padding: 2px 6px; padding: 2px 4px;
min-height: 16px; min-height: 16px;
} }
QComboBox:hover { QComboBox:hover {
@ -152,7 +152,7 @@ QComboBox:focus {
} }
QComboBox::drop-down { QComboBox::drop-down {
border: none; border: none;
width: 18px; width: 14px;
} }
QComboBox QAbstractItemView { QComboBox QAbstractItemView {
background-color: ${bg_subtle}; background-color: ${bg_subtle};

View File

@ -60,7 +60,7 @@ QPushButton {
color: ${text}; color: ${text};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
border-radius: 4px; border-radius: 4px;
padding: 2px 8px; padding: 2px 6px;
min-height: 17px; min-height: 17px;
} }
QPushButton:hover { QPushButton:hover {
@ -145,7 +145,7 @@ QComboBox {
color: ${text}; color: ${text};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
border-radius: 4px; border-radius: 4px;
padding: 2px 6px; padding: 2px 4px;
min-height: 16px; min-height: 16px;
} }
QComboBox:hover { QComboBox:hover {
@ -156,7 +156,7 @@ QComboBox:focus {
} }
QComboBox::drop-down { QComboBox::drop-down {
border: none; border: none;
width: 18px; width: 14px;
} }
QComboBox QAbstractItemView { QComboBox QAbstractItemView {
background-color: ${bg_subtle}; background-color: ${bg_subtle};

View File

@ -59,7 +59,7 @@ QPushButton {
background-color: ${bg_subtle}; background-color: ${bg_subtle};
color: ${text}; color: ${text};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
padding: 2px 8px; padding: 2px 6px;
min-height: 17px; min-height: 17px;
} }
QPushButton:hover { QPushButton:hover {
@ -141,7 +141,7 @@ QComboBox {
background-color: ${bg_subtle}; background-color: ${bg_subtle};
color: ${text}; color: ${text};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
padding: 2px 6px; padding: 2px 4px;
min-height: 16px; min-height: 16px;
} }
QComboBox:hover { QComboBox:hover {
@ -152,7 +152,7 @@ QComboBox:focus {
} }
QComboBox::drop-down { QComboBox::drop-down {
border: none; border: none;
width: 18px; width: 14px;
} }
QComboBox QAbstractItemView { QComboBox QAbstractItemView {
background-color: ${bg_subtle}; background-color: ${bg_subtle};

View File

@ -60,7 +60,7 @@ QPushButton {
color: ${text}; color: ${text};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
border-radius: 4px; border-radius: 4px;
padding: 2px 8px; padding: 2px 6px;
min-height: 17px; min-height: 17px;
} }
QPushButton:hover { QPushButton:hover {
@ -145,7 +145,7 @@ QComboBox {
color: ${text}; color: ${text};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
border-radius: 4px; border-radius: 4px;
padding: 2px 6px; padding: 2px 4px;
min-height: 16px; min-height: 16px;
} }
QComboBox:hover { QComboBox:hover {
@ -156,7 +156,7 @@ QComboBox:focus {
} }
QComboBox::drop-down { QComboBox::drop-down {
border: none; border: none;
width: 18px; width: 14px;
} }
QComboBox QAbstractItemView { QComboBox QAbstractItemView {
background-color: ${bg_subtle}; background-color: ${bg_subtle};

View File

@ -59,7 +59,7 @@ QPushButton {
background-color: ${bg_subtle}; background-color: ${bg_subtle};
color: ${text}; color: ${text};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
padding: 2px 8px; padding: 2px 6px;
min-height: 17px; min-height: 17px;
} }
QPushButton:hover { QPushButton:hover {
@ -141,7 +141,7 @@ QComboBox {
background-color: ${bg_subtle}; background-color: ${bg_subtle};
color: ${text}; color: ${text};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
padding: 2px 6px; padding: 2px 4px;
min-height: 16px; min-height: 16px;
} }
QComboBox:hover { QComboBox:hover {
@ -152,7 +152,7 @@ QComboBox:focus {
} }
QComboBox::drop-down { QComboBox::drop-down {
border: none; border: none;
width: 18px; width: 14px;
} }
QComboBox QAbstractItemView { QComboBox QAbstractItemView {
background-color: ${bg_subtle}; background-color: ${bg_subtle};

View File

@ -60,7 +60,7 @@ QPushButton {
color: ${text}; color: ${text};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
border-radius: 4px; border-radius: 4px;
padding: 2px 8px; padding: 2px 6px;
min-height: 17px; min-height: 17px;
} }
QPushButton:hover { QPushButton:hover {
@ -145,7 +145,7 @@ QComboBox {
color: ${text}; color: ${text};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
border-radius: 4px; border-radius: 4px;
padding: 2px 6px; padding: 2px 4px;
min-height: 16px; min-height: 16px;
} }
QComboBox:hover { QComboBox:hover {
@ -156,7 +156,7 @@ QComboBox:focus {
} }
QComboBox::drop-down { QComboBox::drop-down {
border: none; border: none;
width: 18px; width: 14px;
} }
QComboBox QAbstractItemView { QComboBox QAbstractItemView {
background-color: ${bg_subtle}; background-color: ${bg_subtle};

View File

@ -59,7 +59,7 @@ QPushButton {
background-color: ${bg_subtle}; background-color: ${bg_subtle};
color: ${text}; color: ${text};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
padding: 2px 8px; padding: 2px 6px;
min-height: 17px; min-height: 17px;
} }
QPushButton:hover { QPushButton:hover {
@ -141,7 +141,7 @@ QComboBox {
background-color: ${bg_subtle}; background-color: ${bg_subtle};
color: ${text}; color: ${text};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
padding: 2px 6px; padding: 2px 4px;
min-height: 16px; min-height: 16px;
} }
QComboBox:hover { QComboBox:hover {
@ -152,7 +152,7 @@ QComboBox:focus {
} }
QComboBox::drop-down { QComboBox::drop-down {
border: none; border: none;
width: 18px; width: 14px;
} }
QComboBox QAbstractItemView { QComboBox QAbstractItemView {
background-color: ${bg_subtle}; background-color: ${bg_subtle};

View File

@ -60,7 +60,7 @@ QPushButton {
color: ${text}; color: ${text};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
border-radius: 4px; border-radius: 4px;
padding: 2px 8px; padding: 2px 6px;
min-height: 17px; min-height: 17px;
} }
QPushButton:hover { QPushButton:hover {
@ -145,7 +145,7 @@ QComboBox {
color: ${text}; color: ${text};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
border-radius: 4px; border-radius: 4px;
padding: 2px 6px; padding: 2px 4px;
min-height: 16px; min-height: 16px;
} }
QComboBox:hover { QComboBox:hover {
@ -156,7 +156,7 @@ QComboBox:focus {
} }
QComboBox::drop-down { QComboBox::drop-down {
border: none; border: none;
width: 18px; width: 14px;
} }
QComboBox QAbstractItemView { QComboBox QAbstractItemView {
background-color: ${bg_subtle}; background-color: ${bg_subtle};

View File

@ -59,7 +59,7 @@ QPushButton {
background-color: ${bg_subtle}; background-color: ${bg_subtle};
color: ${text}; color: ${text};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
padding: 2px 8px; padding: 2px 6px;
min-height: 17px; min-height: 17px;
} }
QPushButton:hover { QPushButton:hover {
@ -141,7 +141,7 @@ QComboBox {
background-color: ${bg_subtle}; background-color: ${bg_subtle};
color: ${text}; color: ${text};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
padding: 2px 6px; padding: 2px 4px;
min-height: 16px; min-height: 16px;
} }
QComboBox:hover { QComboBox:hover {
@ -152,7 +152,7 @@ QComboBox:focus {
} }
QComboBox::drop-down { QComboBox::drop-down {
border: none; border: none;
width: 18px; width: 14px;
} }
QComboBox QAbstractItemView { QComboBox QAbstractItemView {
background-color: ${bg_subtle}; background-color: ${bg_subtle};