637 Commits

Author SHA1 Message Date
pax
4ba9990f3a pixmap-aware double-click and dynamic cursor on hover 2026-04-10 20:21:58 -05:00
pax
868b1a7708 cell padding starts rubber band and deselects, not just flow gaps 2026-04-10 20:20:23 -05:00
pax
09fadcf3c2 hover only when cursor is over the pixmap, not cell padding 2026-04-10 20:18:49 -05:00
pax
88a3fe9528 fix stuck hover state when mouse exits grid on Wayland 2026-04-10 20:16:49 -05:00
pax
e28ae6f4af Reapply "only select cell when clicking the pixmap, not the surrounding padding"
This reverts commit 6aa8677a2d28af2eb00961fb16169128df72d2fc.
2026-04-10 20:15:50 -05:00
pax
6aa8677a2d Revert "only select cell when clicking the pixmap, not the surrounding padding"
This reverts commit cc616d1cf4ab460f204095af44607b7fce5a2dad.
2026-04-10 20:15:24 -05:00
pax
cc616d1cf4 only select cell when clicking the pixmap, not the surrounding padding 2026-04-10 20:14:49 -05:00
pax
42e7f2b529 add Escape to deselect in grid 2026-04-10 20:13:54 -05:00
pax
0b4fc9fa49 click empty grid space to deselect, reset stuck drag cursor on release 2026-04-10 20:12:08 -05:00
pax
0f2e800481 skip media reload when clicking already-selected post 2026-04-10 20:10:04 -05:00
pax
15870daae5 fix stuck forbidden cursor after drag-and-drop 2026-04-10 20:07:52 -05:00
pax
27c53cb237 prevent info panel from pushing splitter on long source URLs 2026-04-10 20:05:57 -05:00
pax
b1139cbea6 update README settings list with new options 2026-04-10 19:58:26 -05:00
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
pax
d7b3c304d7 add B/S keybinds to popout, refactor toggle_save 2026-04-10 18:32:57 -05:00
pax
28c40bc1f5 document B/F and S keybinds in KEYBINDS.md 2026-04-10 18:30:39 -05:00
pax
094a22db25 add B and S keyboard shortcuts for bookmark and save 2026-04-10 18:29:58 -05:00
pax
faf9657ed9 add thumbnail fade-in animation 2026-04-10 18:18:17 -05:00
pax
5261fa176d add search history setting
New setting "Record recent searches" (on by default). When disabled,
searches are not recorded and the Recent section is hidden from the
history dropdown. Saved searches are unaffected.

behavior change: opt-in setting, on by default (preserves existing behavior)
2026-04-10 16:28:43 -05:00
pax
94588e324c add unbookmark-on-save setting
New setting "Remove bookmark when saved to library" (off by default).
When enabled, _maybe_unbookmark runs directly in each save callback
after save_post_file succeeds -- handles DB removal, grid dot, preview
state, popout sync, and bookmarks tab refresh. Wired into all 4 save
paths: save_to_library, bulk_save, save_as, batch_download_to.

behavior change: opt-in setting, off by default
2026-04-10 16:23:54 -05:00
pax
9cc294a16a Revert "add unbookmark-on-save setting"
This reverts commit 08f99a61011532202b22d05750416aa1e754f9c9.
2026-04-10 16:20:26 -05:00
pax
08f99a6101 add unbookmark-on-save setting
New setting "Remove bookmark when saved to library" (off by default).
When enabled, saving a post to the library automatically removes its
bookmark. Handles both single saves (on_bookmark_done) and bulk saves
(on_batch_done). UI toggle in Settings > General.

behavior change: opt-in setting, off by default
2026-04-10 16:19:00 -05:00
pax
ba49a59385 updated README.md and fixed redundant entries 2026-04-10 16:06:44 -05:00
pax
aac7b08787 create KEYBINDS.md 2026-04-10 16:02:37 -05:00
pax
d4bad47d42 add themes screanshots to README.md 2026-04-10 16:02:15 -05:00
pax
df301c754c condense README.md 2026-04-10 16:01:51 -05:00
pax
de6961da37 fix: move PySide6 imports to lazy in controllers for CI compat
CI installs httpx + Pillow + pytest but not PySide6. The Phase C
tests import pure functions from controller modules, which had
top-level PySide6 imports (QTimer, QPixmap, QApplication, QMessageBox).
Move these to lazy imports inside the methods that need them so the
module-level pure functions remain importable without Qt.
2026-04-10 15:39:50 -05:00
pax
f9977b61e6 fix: restore collateral-damage methods and fix controller init order
1. Move controller construction before _setup_signals/_setup_ui —
   signals reference controller methods at connect time.

2. Restore _post_id_from_library_path, _set_library_info,
   _on_library_selected, _on_library_activated — accidentally deleted
   in the commit 4/6 line-range removals (they lived adjacent to
   methods being extracted and got caught in the sweep).

behavior change: none (restores lost code, fixes startup crash)
2026-04-10 15:24:01 -05:00
pax
562c03071b test: Phase 2 — add 64 tests for extracted pure functions
5 new test files covering the pure-function extractions from Phase 1:
- test_search_controller.py (24): tag building, blacklist filtering, backfill
- test_window_state.py (16): geometry parsing, splitter parsing, hyprctl cmds
- test_media_controller.py (9): prefetch ring-expansion ordering
- test_post_actions.py (10): batch message detection, library membership
- test_popout_controller.py (3): video sync dict shape

All import-pure (no PySide6, no mpv, no httpx). Total suite: 186 tests.
2026-04-10 15:20:57 -05:00
pax
b858b4ac43 refactor: cleanup pass — remove dead imports from main_window.py
Remove 11 imports no longer needed after controller extractions:
QMenu, QFileDialog, QScrollArea, QMessageBox, QColor, QObject,
Property, dataclass, download_thumbnail, cache_size_bytes,
evict_oldest, evict_oldest_thumbnails, MEDIA_EXTENSIONS, SearchState.

main_window.py: 1140 -> 1128 lines (final Phase 1 state).

behavior change: none
2026-04-10 15:16:30 -05:00
pax
87be4eb2a6 refactor: extract ContextMenuHandler from main_window.py
Move _on_context_menu, _on_multi_context_menu, _is_child_of_menu into
gui/context_menus.py. Pure dispatch to already-extracted controllers.

main_window.py: 1400 -> 1140 lines.

behavior change: none
2026-04-10 15:15:21 -05:00
pax
8e9dda8671 refactor: extract PostActionsController from main_window.py
Move 26 bookmark/save/library/batch/blacklist methods and _batch_dest
state into gui/post_actions.py. Rewire 8 signal connections and update
popout_controller signal targets.

Extract is_batch_message and is_in_library as pure functions for
Phase 2 tests. main_window.py: 1935 -> 1400 lines.

behavior change: none
2026-04-10 15:13:29 -05:00
pax
0a8d392158 refactor: extract PopoutController from main_window.py
Move 5 popout lifecycle methods (_open_fullscreen_preview,
_on_fullscreen_closed, _navigate_fullscreen, _update_fullscreen,
_update_fullscreen_state) and 4 state attributes (_fullscreen_window,
_popout_active, _info_was_visible, _right_splitter_sizes) into
gui/popout_controller.py.

Rename pass across ALL gui/ files: self._fullscreen_window ->
self._popout_ctrl.window (or self._app._popout_ctrl.window in other
controllers), self._popout_active -> self._popout_ctrl.is_active.
Zero remaining references outside popout_controller.py.

Extract build_video_sync_dict as a pure function for Phase 2 tests.

main_window.py: 2145 -> 1935 lines.

behavior change: none
2026-04-10 15:03:42 -05:00
pax
20fc6f551e fix: restore _update_fullscreen and _update_fullscreen_state
These two methods were accidentally deleted in the commit 4 line-range
removal (they lived between _set_preview_media and _on_image_done).
Restored from pre-commit-4 state.

behavior change: none (restores lost code)
2026-04-10 15:00:42 -05:00
pax
71d426e0cf refactor: extract MediaController from main_window.py
Move 10 media loading methods (_on_post_activated, _on_image_done,
_on_video_stream, _on_download_progress, _set_preview_media,
_prefetch_adjacent, _on_prefetch_progress, _auto_evict_cache,
_image_dimensions) and _prefetch_pause state into
gui/media_controller.py.

Extract compute_prefetch_order as a pure function for Phase 2 tests.
Update search_controller.py cross-references to use media_ctrl.

main_window.py: 2525 -> 2114 lines.

behavior change: none
2026-04-10 14:55:32 -05:00
pax
446abe6ba9 refactor: extract SearchController from main_window.py
Move 21 search/pagination/scroll/blacklist methods and 8 state
attributes (_current_page, _current_tags, _current_rating, _min_score,
_loading, _search, _last_scroll_page, _infinite_scroll) into
gui/search_controller.py.

Extract pure functions for Phase 2 tests: build_search_tags,
filter_posts, should_backfill. Replace inline _filter closures with
calls to the module-level filter_posts function.

Rewire 11 signal connections and update _on_site_changed,
_on_rating_changed, _navigate_preview, _apply_settings to use the
controller. main_window.py: 3068 -> 2525 lines.

behavior change: none
2026-04-10 14:51:17 -05:00
pax
cb2445a90a refactor: extract PrivacyController from main_window.py
Move _toggle_privacy and its lazy state (_privacy_on, _privacy_overlay,
_popout_was_visible) into gui/privacy.py. Rewire menu action, popout
signal, resizeEvent, and keyPressEvent to use the controller.

No behavior change. main_window.py: 3111 -> 3068 lines.
2026-04-10 14:41:10 -05:00
pax
321ba8edfa refactor: extract WindowStateController from main_window.py
Move 6 geometry/splitter persistence methods into gui/window_state.py:
_save_main_window_state, _restore_main_window_state,
_hyprctl_apply_main_state, _hyprctl_main_window,
_save_main_splitter_sizes, _save_right_splitter_sizes.

Extract pure functions for Phase 2 tests: parse_geometry,
format_geometry, build_hyprctl_restore_cmds, parse_splitter_sizes.

Controller uses app-reference pattern (self._app). No behavior change.
main_window.py: 3318 -> 3111 lines.

behavior change: none
2026-04-10 14:39:37 -05:00
pax
3f7981a8c6 Update README.md 2026-04-10 14:18:41 -05:00
pax
d66dc14454 db: fix orphan rows — cascade delete_site, wire up reconcile on startup
delete_site() leaked rows in tag_types, search_history, and
saved_searches; reconcile_library_meta() was implemented but never
called. Add tests for both fixes plus tag cache pruning.
2026-04-10 14:10:57 -05:00
pax
e5a33739c9 Update README.md 2026-04-10 12:34:12 +00:00
pax
60867cfa37 Update readme.md 2026-04-10 00:44:51 -05:00
pax
df3b1d06d8 main_window: reset browse tab on site change 2026-04-10 00:37:53 -05:00
pax
127ee4315c popout/window: add right-click context menu
Popout now has a full context menu matching the embedded preview:
Bookmark as (folder submenu) / Unbookmark, Save to Library (folder
submenu), Unsave, Copy File, Open in Default App, Open in Browser,
Reset View (images), and Close Popout. Signals wired to the same
main_window handlers as the embedded preview.
2026-04-10 00:27:44 -05:00
pax
48feafa977 preview_pane: fix bookmark state in context menu, add folder submenu
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.
2026-04-10 00:27:36 -05:00
pax
38c5aefa27 fix releases link in readme 2026-04-10 00:14:44 -05:00
pax
a632f1b961 ci: use PYTHONPATH instead of editable install 2026-04-10 00:06:35 -05:00
pax
80607835d1 ci: install only test deps (skip PySide6/mpv build) 2026-04-10 00:04:28 -05:00
pax
8c1266ab0d ci: add GitHub Actions test workflow + README badge
Runs pytest tests/ on every push and PR. Ubuntu runner with
Python 3.11, libmpv, and QT_QPA_PLATFORM=offscreen for headless
Qt. Badge in README links to the Actions tab.

117 tests, ~0.2s locally. CI time depends on PySide6 install
(~2 min) + apt deps (~30s) + tests (~1s).
2026-04-10 00:01:28 -05:00
pax
a90d71da47 tests: add 36 tests for CategoryFetcher (parser, cache, probe, dispatch)
New test_category_fetcher.py covering:
  HTML parser (10): Rule34/Moebooru/Konachan markup, Gelbooru-empty,
    metadata->Meta mapping, URL-encoded names, edge cases
  Tag API parser (6): JSON, XML, empty, flat list, malformed
  Canonical ordering (4): standard order, species, unknown, empty
  Cache compose (6): full/partial/zero coverage, empty tags, order,
    per-site isolation
  Probe persistence (5): save/load True/False, per-site, clear wipes
  Batch API availability (3): URL+auth combinations
  Map coverage (2): label and type map constants

All pure Python — synthetic HTML, FakePost/FakeClient/FakeResponse.
No network, no Qt. Uses tmp_db fixture from conftest.

Total suite: 117 tests, 0.19s.
2026-04-09 23:58:56 -05:00