Hi-res scroll mice (e.g. G502) send many small angleDelta events
per physical notch instead of one ±120. Without accumulation, each
micro-event triggered a ±5 volume jump, making volume unusable on
hi-res hardware. Now accumulates to ±120 boundaries before firing.
behavior change
- preview_pane: unsave button now checks self._is_saved instead of
self._save_btn.text() == "Unsave", which stopped matching after the
button text became a Unicode icon (✕ / ⤓)
- popout: new _exec_menu_at_button helper uses menu.popup() +
QEventLoop blocked on aboutToHide instead of menu.exec(globalPos).
On Hyprland the popout gets moved via hyprctl after Qt maps it and
Qt's window-position tracking stays stale, so exec(btn.mapToGlobal)
resolved to a global point on the wrong monitor, flashing the menu
there before the compositor corrected it. popup() routes through a
different positioning path that anchors correctly.
behavior change: right-click context menu now shows "Unbookmark" when
the post is already bookmarked, and "Bookmark as" with a folder submenu
(Unfiled / existing folders / + New Folder) when not. Previously showed
a stateless "Bookmark" action regardless of state.
Step 7 of the gui/app.py + gui/preview.py structural refactor. Pure
move: the embedded preview pane class (the one that lives in the
right column of the main window and combines image+video+toolbar)
is now in its own module. preview_pane.py is at the same package
depth as preview.py was, so no relative-import depth adjustment is
needed inside the class body.
preview.py grows the final preview-side re-export shim line. After
this commit preview.py is just the original imports + _log + shim
block — every class that used to live in it now lives in its
canonical module under media/ or popout/ or as preview_pane. The
file gets deleted entirely in commit 14.