feat(gui): add shared zoomable image preview

This commit is contained in:
chengma
2026-07-16 09:13:44 +08:00
parent 4cf0f46e90
commit dbbf3ce8f7
5 changed files with 369 additions and 68 deletions
+117
View File
@@ -54,8 +54,10 @@ from app.gui import (
GenerateWorker,
GenerateTab,
ForcedUpdateDialog,
ImageStudioPreviewDialog,
ImageStudioTab,
MainWindow,
ProductSuitePreviewDialog,
ProductSuiteTab,
SettingsTab,
TAB_STYLE,
@@ -4515,6 +4517,121 @@ class GuiTests(TempDirMixin, unittest.TestCase):
self.assert_removed(temp_dir)
def test_shared_image_preview_defaults_to_fit_and_keeps_fixed_zoom_on_resize(self):
with self.make_temp_dir() as temp_dir:
image_path = self.write_test_image(
os.path.join(temp_dir, "suite-large.jpg"),
width=1200,
height=800,
)
dialog = ProductSuitePreviewDialog(image_path, "商品原图预览")
self.addCleanup(dialog.close)
dialog.resize(600, 450)
dialog.show()
QApplication.processEvents()
dialog._resize_timer.stop()
dialog._render()
fit_pixmap = dialog.image_label.pixmap()
viewport = dialog.scroll.viewport().size()
self.assertTrue(dialog.fit_to_window)
self.assertEqual("适应窗口", dialog.zoom_label.text())
self.assertIn("1200x800", dialog.windowTitle())
self.assertLessEqual(fit_pixmap.width(), viewport.width())
self.assertLessEqual(fit_pixmap.height(), viewport.height())
self.assertAlmostEqual(1.5, fit_pixmap.width() / fit_pixmap.height(), places=1)
dialog.show_actual_size()
QApplication.processEvents()
self.assertFalse(dialog.fit_to_window)
self.assertEqual("100%", dialog.zoom_label.text())
self.assertEqual(QSize(1200, 800), dialog.image_label.pixmap().size())
self.assertEqual(QSize(1200, 800), dialog.image_label.size())
self.assertGreater(dialog.scroll.horizontalScrollBar().maximum(), 0)
self.assertGreater(dialog.scroll.verticalScrollBar().maximum(), 0)
dialog.resize(520, 360)
QApplication.processEvents()
dialog._resize_timer.stop()
dialog._render()
self.assertEqual(QSize(1200, 800), dialog.image_label.pixmap().size())
dialog.show_fit()
self.assertTrue(dialog.fit_to_window)
self.assertEqual("适应窗口", dialog.zoom_label.text())
self.assertLessEqual(
dialog.image_label.pixmap().width(),
dialog.scroll.viewport().width(),
)
self.assertLessEqual(
dialog.image_label.pixmap().height(),
dialog.scroll.viewport().height(),
)
self.assert_removed(temp_dir)
def test_shared_image_preview_bounds_zoom_and_does_not_enlarge_small_fit_image(self):
with self.make_temp_dir() as temp_dir:
image_path = self.write_test_image(
os.path.join(temp_dir, "suite-small.jpg"),
width=200,
height=100,
)
dialog = ProductSuitePreviewDialog(image_path, "图片预览")
self.addCleanup(dialog.close)
dialog.resize(700, 500)
dialog.show()
QApplication.processEvents()
dialog._resize_timer.stop()
dialog._render()
self.assertEqual(QSize(200, 100), dialog.image_label.pixmap().size())
for _index in range(20):
dialog.zoom_out()
self.assertEqual(0.25, dialog.zoom_factor)
self.assertEqual("25%", dialog.zoom_label.text())
self.assertFalse(dialog.zoom_out_button.isEnabled())
for _index in range(20):
dialog.zoom_in()
self.assertEqual(4.0, dialog.zoom_factor)
self.assertEqual("400%", dialog.zoom_label.text())
self.assertFalse(dialog.zoom_in_button.isEnabled())
self.assertEqual(QSize(800, 400), dialog.image_label.pixmap().size())
self.assert_removed(temp_dir)
def test_shared_image_preview_legacy_wrapper_and_missing_file_state(self):
with self.make_temp_dir() as temp_dir:
image_path = self.write_test_image(
os.path.join(temp_dir, "legacy.jpg"),
width=320,
height=240,
)
asset = SimpleNamespace(id=7, kind="original", local_path=image_path)
legacy = ImageStudioPreviewDialog(asset)
self.addCleanup(legacy.close)
legacy.show()
QApplication.processEvents()
legacy._resize_timer.stop()
legacy._render()
self.assertIn("AI工场图片预览", legacy.windowTitle())
self.assertIn("320x240", legacy.windowTitle())
self.assertEqual("适应窗口", legacy.zoom_label.text())
missing_path = os.path.join(temp_dir, "missing-private-name.jpg")
missing = ProductSuitePreviewDialog(missing_path, "商品原图预览")
self.addCleanup(missing.close)
missing._render()
self.assertEqual("图片文件不存在或无法读取", missing.image_label.text())
self.assertEqual("无法预览", missing.zoom_label.text())
self.assertFalse(missing.fit_button.isEnabled())
self.assertFalse(missing.actual_size_button.isEnabled())
self.assertNotIn(missing_path, missing.windowTitle())
self.assert_removed(temp_dir)
def test_cover_gallery_previous_next_switch_visible_tasks_without_resizing(self):
with self.make_temp_dir() as temp_dir:
cfg, account, tasks, canonicals, _archives = self._cover_gallery_task_set(temp_dir, count=3)