fix(gui): make cover gallery previews responsive
This commit is contained in:
+154
-23
@@ -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)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user