Instant infinite scroll drain, trigger 3 rows early

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.
This commit is contained in:
pax 2026-04-06 01:00:39 -05:00
parent a93a8bc70f
commit bda92a0a87
2 changed files with 27 additions and 29 deletions

View File

@ -920,7 +920,7 @@ class BooruApp(QMainWindow):
self._drain_append_queue() self._drain_append_queue()
def _drain_append_queue(self) -> None: 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: if not getattr(self, '_append_queue', None) or len(self._append_queue) == 0:
self._loading = False self._loading = False
return return
@ -933,36 +933,34 @@ class BooruApp(QMainWindow):
if _sd.exists(): if _sd.exists():
_saved_ids = {int(f.stem) for f in _sd.iterdir() if f.is_file() and f.stem.isdigit()} _saved_ids = {int(f.stem) for f in _sd.iterdir() if f.is_file() and f.stem.isdigit()}
post = self._append_queue.pop(0) posts = self._append_queue[:]
idx = len(self._posts) self._append_queue.clear()
self._posts.append(post) start_idx = len(self._posts)
thumbs = self._grid.append_posts(1) self._posts.extend(posts)
thumb = thumbs[0] thumbs = self._grid.append_posts(len(posts))
if site_id and self._db.is_bookmarked(site_id, post.id): for i, (post, thumb) in enumerate(zip(posts, thumbs)):
thumb.set_bookmarked(True) idx = start_idx + i
thumb.set_saved_locally(post.id in _saved_ids) if site_id and self._db.is_bookmarked(site_id, post.id):
cached = cached_path_for(post.file_url) thumb.set_bookmarked(True)
if cached.exists(): thumb.set_saved_locally(post.id in _saved_ids)
thumb._cached_path = str(cached) cached = cached_path_for(post.file_url)
if post.preview_url: if cached.exists():
self._fetch_thumbnail(idx, post.preview_url) thumb._cached_path = str(cached)
if post.preview_url:
self._fetch_thumbnail(idx, post.preview_url)
self._status.showMessage(f"{len(self._posts)} results") self._status.showMessage(f"{len(self._posts)} results")
# Schedule next post # All done — unlock loading, evict
if self._append_queue: self._loading = False
QTimer.singleShot(50, self._drain_append_queue) self._auto_evict_cache()
else: # Check if still at bottom or content doesn't fill viewport
# All done — unlock loading, evict sb = self._grid.verticalScrollBar()
self._loading = False from .grid import THUMB_SIZE, THUMB_SPACING
self._auto_evict_cache() threshold = THUMB_SIZE + THUMB_SPACING * 2
# Check if still at bottom or content doesn't fill viewport if sb.maximum() == 0 or sb.value() >= sb.maximum() - threshold:
sb = self._grid.verticalScrollBar() self._on_reached_bottom()
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: def _fetch_thumbnail(self, index: int, url: str) -> None:
async def _download(): async def _download():

View File

@ -482,8 +482,8 @@ class ThumbnailGrid(QScrollArea):
def _check_scroll_bottom(self, value: int) -> None: def _check_scroll_bottom(self, value: int) -> None:
sb = self.verticalScrollBar() sb = self.verticalScrollBar()
# Trigger when within one row height of the bottom # Trigger when within 3 rows of the bottom for early prefetch
threshold = THUMB_SIZE + THUMB_SPACING * 2 threshold = (THUMB_SIZE + THUMB_SPACING) * 3
if sb.maximum() > 0 and value >= sb.maximum() - threshold: if sb.maximum() > 0 and value >= sb.maximum() - threshold:
self.reached_bottom.emit() self.reached_bottom.emit()
if value <= 0 and sb.maximum() > 0: if value <= 0 and sb.maximum() > 0: