feat: add infinite scrolling, improve page performance (#1119)

* perf: remove unnecessary path conversions

* perf: search_library if no limit set don't do extra count

* perf: improve responsiveness of ui when rendering thumbnails

* feat: infinite scrolling thumbnail grid

* fix: update tests

* perf: don't run update for thumb grid if rows haven't changed

* fix: update blank thumbnails on initial page load

* fix: do partial updates when selecting items

* fix: remove badges on loading thumbnails

* fix: move all extra item_thumbs off screen

* load a few hidden rows when scrolling

* cleanup

* update imports

* remove todo

* support pagination

* allow setting page_size to 0 for no limit

* add ui setting for infinite scrolling

* undo render thread affinity changes

* always load a few off-screen rows
This commit is contained in:
TheBobBobs
2025-09-12 05:15:58 +00:00
committed by GitHub
parent d7573b3f26
commit 6e6a91aaf4
15 changed files with 528 additions and 278 deletions

View File

@@ -10,6 +10,7 @@ from tempfile import TemporaryDirectory
from unittest.mock import Mock, patch
import pytest
from PySide6.QtWidgets import QScrollArea
CWD = Path(__file__).parent
# this needs to be above `src` imports
@@ -19,6 +20,7 @@ from tagstudio.core.constants import THUMB_CACHE_NAME, TS_FOLDER_NAME
from tagstudio.core.library.alchemy.library import Library
from tagstudio.core.library.alchemy.models import Entry, Tag
from tagstudio.core.utils.types import unwrap
from tagstudio.qt.thumb_grid_layout import ThumbGridLayout
from tagstudio.qt.ts_qt import QtDriver
@@ -165,7 +167,8 @@ def qt_driver(library: Library, library_dir: Path):
driver.app = Mock()
driver.main_window = Mock()
driver.main_window.thumb_size = 128
driver.item_thumbs = []
driver.main_window.thumb_layout = ThumbGridLayout(driver, QScrollArea())
driver.main_window.menu_bar.autofill_action = Mock()
driver.copy_buffer = {"fields": [], "tags": []}

View File

@@ -17,8 +17,7 @@ def test_badge_visual_state(qt_driver: QtDriver, entry_min: int, new_value: bool
)
qt_driver.frame_content = [entry_min]
qt_driver.selected = [0]
qt_driver.item_thumbs = [thumb]
qt_driver.toggle_item_selection(0, append=False, bridge=False)
thumb.badges[BadgeType.FAVORITE].setChecked(new_value)
assert thumb.badges[BadgeType.FAVORITE].isChecked() == new_value

View File

@@ -2,19 +2,17 @@
# Licensed under the GPL-3.0 License.
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
from tagstudio.core.library.alchemy.enums import BrowsingState, ItemType
from tagstudio.core.library.alchemy.enums import BrowsingState
from tagstudio.core.utils.types import unwrap
from tagstudio.qt.mixed.item_thumb import ItemThumb
from tagstudio.qt.ts_qt import QtDriver
def test_browsing_state_update(qt_driver: QtDriver):
# Given
for entry in qt_driver.lib.all_entries(with_joins=True):
thumb = ItemThumb(ItemType.ENTRY, qt_driver.lib, qt_driver, (100, 100))
qt_driver.item_thumbs.append(thumb)
qt_driver.frame_content.append(entry.id)
entries = qt_driver.lib.all_entries(with_joins=True)
ids = [e.id for e in entries]
qt_driver.frame_content = ids
qt_driver.main_window.thumb_layout.set_entries(ids)
# no filter, both items are returned
qt_driver.update_browsing_state()
@@ -49,7 +47,7 @@ def test_close_library(qt_driver: QtDriver):
assert qt_driver.lib.library_dir is None
assert not qt_driver.frame_content
assert not qt_driver.selected
assert not any(x.mode for x in qt_driver.item_thumbs)
assert len(qt_driver.main_window.thumb_layout._entry_ids) == 0
# close library again to see there's no error
qt_driver.close_library()