Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion client/ayon_core/tools/loader/ui/_review_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,6 @@ def _on_display_type_changed(self, display_type: str) -> None:
if active is self._table:
self._eagerly_enqueue_visible_thumbnails()


self.display_type_changed.emit(active)

def _on_group_by_options_changed(
Expand Down
61 changes: 60 additions & 1 deletion client/ayon_core/tools/loader/ui/review_inspector.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import time
from collections import defaultdict

from ayon_ui_qt.components.buttons import AYButton
Expand Down Expand Up @@ -53,6 +54,16 @@ def __init__(
self._current_thumb_key: str = ""
# Use a dict as an ordered set to track the currently selected indices
self._current_selection: dict[QtCore.QModelIndex, None] = {}
# Track if mouse is currently pressed for drag selection
self._mouse_pressed: bool = False
# Timer for throttling updates during drag selection
self._update_timer = QtCore.QTimer()
self._update_timer.setSingleShot(True)
self._update_timer.timeout.connect(self._throttled_update)
# Interval for throttling updates (in milliseconds)
self._base_update_time: int = 0
# Flag to indicate if the first update has been measured
self._first_update_measured: bool = False

self._build()

Expand Down Expand Up @@ -168,19 +179,63 @@ def set_view(self, view: QtWidgets.QAbstractItemView) -> None:
self._on_selection_changed
)
self._view.model().modelReset.disconnect(self._on_model_reset)
# Remove event filter from previous view's viewport
self._view.viewport().removeEventFilter(self)
except (RuntimeError, TypeError):
pass

self._view = view
self._view.activated.connect(self._on_activated)
self._view.selection_changed.connect(self._on_selection_changed)
self._view.model().modelReset.connect(self._on_model_reset)
# Install event filter on the viewport to track mouse press/release
self._view.viewport().installEventFilter(self)

def _throttled_update(self) -> None:
"""Throttled update method called by the timer during drag selection."""
if self._mouse_pressed:
start_time = time.time()
self._update()
Comment thread
philippe-ynput marked this conversation as resolved.
elapsed_time_ms = (time.time() - start_time) * 1000
Comment thread
philippe-ynput marked this conversation as resolved.
num_selected = len(self._current_selection)
if not self._first_update_measured and num_selected > 0:
# Calculate the update interval based on the first update time
self._base_update_time = max(
16,
int(elapsed_time_ms / num_selected),
)
self._first_update_measured = True
# print(f"base update time: {self._base_update_time} ms")
self._update_timer.start(max(16, self._base_update_time * num_selected))

def _on_model_reset(self) -> None:
"""Clear the selection when the model is reset."""
self._current_selection.clear()
self._update()

def eventFilter(self, obj: QtCore.QObject, event: QtCore.QEvent) -> bool:
"""Track mouse press/release events on the view's viewport.

Args:
obj: The watched object.
event: The event.

Returns:
True if the event was handled, False otherwise.
"""
if obj is self._view.viewport():
if event.type() == QtCore.QEvent.Type.MouseButtonPress:
self._mouse_pressed = True
Comment thread
philippe-ynput marked this conversation as resolved.
# Trigger an immediate update on first mouse down
self._throttled_update()
elif event.type() == QtCore.QEvent.Type.MouseButtonRelease:
self._mouse_pressed = False
self._update_timer.stop()
# Trigger update on mouse release for finalized selection
if self.isVisible():
self._update()
return super().eventFilter(obj, event)

def _on_activated(self, index: QtCore.QModelIndex) -> None:
"""Activation is typically when the user double-clicks on an item.
We want to show the inspector in this case if it's not visible.
Expand All @@ -206,7 +261,10 @@ def _on_selection_changed(
for idx in deselected.indexes():
if idx.column() == 0:
self._current_selection.pop(idx, None)
self._update()
# Only update immediately if mouse is not pressed (keyboard selection)
# Otherwise, update will be triggered on mouse release
if not self._mouse_pressed:
self._update()

def _update(self) -> None:
"""Update the inspector with the current selection, if visible."""
Expand Down Expand Up @@ -258,6 +316,7 @@ def _update(self) -> None:
version_ids.append(vid)

if thumb_keys:
thumb_keys = sorted(thumb_keys) # limit cache misses
Comment thread
philippe-ynput marked this conversation as resolved.
self._load_thumbnail(thumb_keys)
else:
self._current_thumb_key = ""
Expand Down