fix(gui): make cover gallery previews responsive

This commit is contained in:
chengma
2026-07-14 08:13:56 +08:00
parent 3217d4b11a
commit fb4403228d
3 changed files with 308 additions and 27 deletions
+154 -23
View File
@@ -34,31 +34,88 @@ class OriginalImageDialog(QDialog):
def __init__(self, image_path, parent=None):
super().__init__(parent)
self.image_path = image_path
self.image = QImage(str(image_path))
self.fit_to_window = True
self._display_size = QSize()
self._resize_timer = QTimer(self)
self._resize_timer.setSingleShot(True)
self._resize_timer.setInterval(50)
self._resize_timer.timeout.connect(self._update_image_display)
self.setWindowTitle(os.path.basename(str(image_path or "")) or "原图")
layout = QVBoxLayout(self)
image = QImage(str(image_path))
scroll = QScrollArea()
scroll.setWidgetResizable(False)
image_label = QLabel()
image_label.setAlignment(Qt.AlignCenter)
if image.isNull():
image_label.setText("图片读取失败")
self.scroll = QScrollArea()
self.scroll.setObjectName("originalImageScrollArea")
self.scroll.setWidgetResizable(False)
self.image_label = QLabel()
self.image_label.setObjectName("originalImageLabel")
self.image_label.setAlignment(Qt.AlignCenter)
if self.image.isNull():
self.image_label.setText("图片读取失败")
else:
self.setWindowTitle(
f"{os.path.basename(str(image_path))} · {image.width()}x{image.height()}"
f"{os.path.basename(str(image_path))} · {self.image.width()}x{self.image.height()}"
)
pixmap = QPixmap.fromImage(image)
image_label.setPixmap(pixmap)
image_label.resize(pixmap.size())
scroll.setWidget(image_label)
layout.addWidget(scroll, 1)
self.scroll.setWidget(self.image_label)
layout.addWidget(self.scroll, 1)
button_layout = QHBoxLayout()
button_layout.addStretch(1)
self.display_mode_button = QPushButton("原始尺寸")
self.display_mode_button.setObjectName("originalImageDisplayModeButton")
self.display_mode_button.setToolTip("按图片原始像素查看")
self.display_mode_button.setEnabled(not self.image.isNull())
self.display_mode_button.clicked.connect(self.toggle_display_mode)
button_layout.addWidget(self.display_mode_button)
close_button = QPushButton("关闭")
close_button.clicked.connect(self.reject)
button_layout.addWidget(close_button)
layout.addLayout(button_layout)
self._fit_to_screen(image)
self._fit_to_screen(self.image)
self._update_image_display()
QTimer.singleShot(0, self._update_image_display)
def resizeEvent(self, event):
super().resizeEvent(event)
self._resize_timer.start()
def toggle_display_mode(self, checked=False):
self.fit_to_window = not self.fit_to_window
if self.fit_to_window:
self.display_mode_button.setText("原始尺寸")
self.display_mode_button.setToolTip("按图片原始像素查看")
else:
self.display_mode_button.setText("适应窗口")
self.display_mode_button.setToolTip("缩放图片以完整显示在窗口内")
self._display_size = QSize()
self._update_image_display()
QTimer.singleShot(0, self._update_image_display)
def _update_image_display(self):
viewport_size = self.scroll.viewport().size()
viewport_width = max(1, viewport_size.width())
viewport_height = max(1, viewport_size.height())
if self.image.isNull():
self.image_label.clear()
self.image_label.setText("图片读取失败")
self.image_label.resize(viewport_width, viewport_height)
return
if self.fit_to_window:
target_size = QSize(viewport_width, viewport_height)
pixmap = QPixmap.fromImage(
self.image.scaled(
target_size,
Qt.KeepAspectRatio,
Qt.SmoothTransformation,
)
)
label_size = target_size
else:
pixmap = QPixmap.fromImage(self.image)
label_size = pixmap.size()
if self._display_size == label_size and self.image_label.pixmap() is not None:
return
self.image_label.setPixmap(pixmap)
self.image_label.resize(label_size)
self._display_size = QSize(label_size)
def _fit_to_screen(self, image):
available = _available_geometry()
@@ -132,6 +189,8 @@ class _SegmentedProgressBar(QProgressBar):
class CoverGalleryDialog(QDialog):
THUMBNAIL_SIZE = 180
MAX_THUMBNAIL_SIZE = 360
CANDIDATE_TEXT_HEIGHT = 72
OLD_THUMBNAIL_SIZE = 300
def __init__(
@@ -163,6 +222,14 @@ class CoverGalleryDialog(QDialog):
self.current_path = _normalize_file_path(getattr(task, "new_cover_path", None))
self.selected_path = None
self.changed = False
self.candidate_thumbnail_size = self.THUMBNAIL_SIZE
self.candidate_images = {}
self.candidate_thumbnails = {}
self.candidate_items = {}
self._candidate_resize_timer = QTimer(self)
self._candidate_resize_timer.setSingleShot(True)
self._candidate_resize_timer.setInterval(60)
self._candidate_resize_timer.timeout.connect(self._update_candidate_thumbnail_layout)
self.candidate_buttons = {}
self.button_group = QButtonGroup(self)
self.button_group.setExclusive(True)
@@ -203,6 +270,7 @@ class CoverGalleryDialog(QDialog):
self._update_reset_cover_button_state()
self._fit_to_screen()
self._install_navigation_event_filters()
QTimer.singleShot(0, self._update_candidate_thumbnail_layout)
def _old_cover_panel(self):
panel = QWidget()
@@ -267,28 +335,30 @@ class CoverGalleryDialog(QDialog):
def _candidate_item(self, candidate_path, index):
item = QWidget()
item.setFixedWidth(self.THUMBNAIL_SIZE + 16)
item.setFixedWidth(self.candidate_thumbnail_size + 16)
layout = QVBoxLayout(item)
layout.setContentsMargins(0, 0, 0, 0)
thumbnail = _CoverThumbnailLabel(candidate_path, self.open_original_image)
thumbnail.setObjectName("coverCandidateThumbnail")
thumbnail.setAlignment(Qt.AlignCenter)
image = _load_image(candidate_path)
self.candidate_images[candidate_path] = image
self.candidate_thumbnails[candidate_path] = thumbnail
self.candidate_items[candidate_path] = item
if image.isNull():
thumbnail.setText("图片读取失败")
thumbnail.setMinimumSize(self.THUMBNAIL_SIZE, self.THUMBNAIL_SIZE)
else:
thumbnail.setPixmap(
QPixmap.fromImage(
image.scaled(
self.THUMBNAIL_SIZE,
self.THUMBNAIL_SIZE,
self.candidate_thumbnail_size,
self.candidate_thumbnail_size,
Qt.KeepAspectRatio,
Qt.SmoothTransformation,
)
)
)
thumbnail.setMinimumSize(self.THUMBNAIL_SIZE, self.THUMBNAIL_SIZE)
thumbnail.setFixedSize(self.candidate_thumbnail_size, self.candidate_thumbnail_size)
radio = QRadioButton(_candidate_label(candidate_path, self.current_path))
radio.setObjectName(f"coverCandidateRadio{index}")
radio.setFocusPolicy(Qt.ClickFocus)
@@ -320,15 +390,14 @@ class CoverGalleryDialog(QDialog):
min_width = (
margins.left()
+ margins.right()
+ candidate_count * (self.THUMBNAIL_SIZE + 16)
+ candidate_count * (self.candidate_thumbnail_size + 16)
+ max(0, candidate_count - 1) * spacing
)
else:
min_width = margins.left() + margins.right() + 320
min_height = max(
self.THUMBNAIL_SIZE,
self.candidate_thumbnail_size,
hint.height(),
content.minimumHeight(),
)
size = QSize(max(min_width, hint.width()), min_height)
content.setMinimumSize(size)
@@ -338,6 +407,64 @@ class CoverGalleryDialog(QDialog):
if scroll is not None:
scroll.updateGeometry()
def resizeEvent(self, event):
super().resizeEvent(event)
self._candidate_resize_timer.start()
def _candidate_thumbnail_target_size(self):
scroll = getattr(self, "candidate_scroll", None)
if scroll is None or not self.candidates:
return self.THUMBNAIL_SIZE
viewport = scroll.viewport()
viewport_width = viewport.width()
viewport_height = viewport.height()
if viewport_width <= 0 or viewport_height <= 0:
return self.candidate_thumbnail_size
visible_count = min(3, max(1, len(self.candidates)))
spacing = max(0, self.candidate_layout.spacing())
width_budget = (
viewport_width
- max(0, visible_count - 1) * spacing
- visible_count * 16
) // visible_count
height_budget = viewport_height - self.CANDIDATE_TEXT_HEIGHT
target = min(width_budget, height_budget, self.MAX_THUMBNAIL_SIZE)
return max(self.THUMBNAIL_SIZE, int(target))
def _update_candidate_thumbnail_layout(self):
if not self.candidates:
self._sync_candidate_content_size()
return
target_size = self._candidate_thumbnail_target_size()
if target_size == self.candidate_thumbnail_size:
self._sync_candidate_content_size()
return
self.candidate_thumbnail_size = target_size
for candidate_path in self.candidates:
item = self.candidate_items.get(candidate_path)
thumbnail = self.candidate_thumbnails.get(candidate_path)
image = self.candidate_images.get(candidate_path)
if item is not None:
item.setFixedWidth(target_size + 16)
if thumbnail is None:
continue
thumbnail.setFixedSize(target_size, target_size)
thumbnail.clear()
if image is None or image.isNull():
thumbnail.setText("图片读取失败")
continue
thumbnail.setPixmap(
QPixmap.fromImage(
image.scaled(
target_size,
target_size,
Qt.KeepAspectRatio,
Qt.SmoothTransformation,
)
)
)
self._sync_candidate_content_size()
def _sync_initial_selection(self):
for candidate_path, radio in self.candidate_buttons.items():
if _same_file(candidate_path, self.current_path):
@@ -609,6 +736,9 @@ class CoverGalleryDialog(QDialog):
def _rebuild_candidate_items(self):
_clear_layout(self.candidate_layout)
self.candidate_buttons = {}
self.candidate_images = {}
self.candidate_thumbnails = {}
self.candidate_items = {}
self.button_group = QButtonGroup(self)
self.button_group.setExclusive(True)
if self.candidates:
@@ -622,6 +752,7 @@ class CoverGalleryDialog(QDialog):
empty_label.setAlignment(Qt.AlignCenter)
self.candidate_layout.addWidget(empty_label)
self._sync_candidate_content_size()
self._candidate_resize_timer.start()
self._install_navigation_event_filters()
def _fit_to_screen(self):
@@ -631,7 +762,7 @@ class CoverGalleryDialog(QDialog):
return
max_width = max(520, int(available.width() * 0.9))
max_height = max(420, int(available.height() * 0.9))
self.resize(min(960, max_width), min(580, max_height))
self.resize(min(1180, max_width), min(720, max_height))
_center_dialog(self, available)