From bda92a0a8704ef84a810cdc8a4bc1b2e5f1e8dc1 Mon Sep 17 00:00:00 2001 From: pax Date: Mon, 6 Apr 2026 01:00:39 -0500 Subject: [PATCH] Instant infinite scroll drain, trigger 3 rows early MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Old staggered drain (50ms per post) was added for visual polish but made infinite scroll painfully slow — a 40-post page took 2 seconds just to add to the grid. Thumbnails already load async via _fetch_thumbnail, so the stagger was just delaying grid population for no real benefit. Now all posts are added instantly in one pass with thumbnails filling in as they arrive. Scroll trigger widened from 1 row to 3 rows from bottom so the next page starts loading before you reach the end. --- booru_viewer/gui/app.py | 52 +++++++++++++++++++--------------------- booru_viewer/gui/grid.py | 4 ++-- 2 files changed, 27 insertions(+), 29 deletions(-) diff --git a/booru_viewer/gui/app.py b/booru_viewer/gui/app.py index de5051e..9dddf28 100644 --- a/booru_viewer/gui/app.py +++ b/booru_viewer/gui/app.py @@ -920,7 +920,7 @@ class BooruApp(QMainWindow): self._drain_append_queue() def _drain_append_queue(self) -> None: - """Add queued posts to the grid one at a time with thumbnail fetch.""" + """Add all queued posts to the grid at once, thumbnails load async.""" if not getattr(self, '_append_queue', None) or len(self._append_queue) == 0: self._loading = False return @@ -933,36 +933,34 @@ class BooruApp(QMainWindow): if _sd.exists(): _saved_ids = {int(f.stem) for f in _sd.iterdir() if f.is_file() and f.stem.isdigit()} - post = self._append_queue.pop(0) - idx = len(self._posts) - self._posts.append(post) - thumbs = self._grid.append_posts(1) - thumb = thumbs[0] + posts = self._append_queue[:] + self._append_queue.clear() + start_idx = len(self._posts) + self._posts.extend(posts) + thumbs = self._grid.append_posts(len(posts)) - if site_id and self._db.is_bookmarked(site_id, post.id): - thumb.set_bookmarked(True) - thumb.set_saved_locally(post.id in _saved_ids) - cached = cached_path_for(post.file_url) - if cached.exists(): - thumb._cached_path = str(cached) - if post.preview_url: - self._fetch_thumbnail(idx, post.preview_url) + for i, (post, thumb) in enumerate(zip(posts, thumbs)): + idx = start_idx + i + if site_id and self._db.is_bookmarked(site_id, post.id): + thumb.set_bookmarked(True) + thumb.set_saved_locally(post.id in _saved_ids) + cached = cached_path_for(post.file_url) + if cached.exists(): + thumb._cached_path = str(cached) + if post.preview_url: + self._fetch_thumbnail(idx, post.preview_url) self._status.showMessage(f"{len(self._posts)} results") - # Schedule next post - if self._append_queue: - QTimer.singleShot(50, self._drain_append_queue) - else: - # All done — unlock loading, evict - self._loading = False - self._auto_evict_cache() - # Check if still at bottom or content doesn't fill viewport - sb = self._grid.verticalScrollBar() - from .grid import THUMB_SIZE, THUMB_SPACING - threshold = THUMB_SIZE + THUMB_SPACING * 2 - if sb.maximum() == 0 or sb.value() >= sb.maximum() - threshold: - self._on_reached_bottom() + # All done — unlock loading, evict + self._loading = False + self._auto_evict_cache() + # Check if still at bottom or content doesn't fill viewport + sb = self._grid.verticalScrollBar() + from .grid import THUMB_SIZE, THUMB_SPACING + threshold = THUMB_SIZE + THUMB_SPACING * 2 + if sb.maximum() == 0 or sb.value() >= sb.maximum() - threshold: + self._on_reached_bottom() def _fetch_thumbnail(self, index: int, url: str) -> None: async def _download(): diff --git a/booru_viewer/gui/grid.py b/booru_viewer/gui/grid.py index 990609b..f205d6d 100644 --- a/booru_viewer/gui/grid.py +++ b/booru_viewer/gui/grid.py @@ -482,8 +482,8 @@ class ThumbnailGrid(QScrollArea): def _check_scroll_bottom(self, value: int) -> None: sb = self.verticalScrollBar() - # Trigger when within one row height of the bottom - threshold = THUMB_SIZE + THUMB_SPACING * 2 + # Trigger when within 3 rows of the bottom for early prefetch + threshold = (THUMB_SIZE + THUMB_SPACING) * 3 if sb.maximum() > 0 and value >= sb.maximum() - threshold: self.reached_bottom.emit() if value <= 0 and sb.maximum() > 0: