2026-06-16 08:44:17 +08:00
|
|
|
import logging
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
from typing import List, Optional
|
|
|
|
|
|
2026-06-16 15:11:56 +08:00
|
|
|
from PySide6.QtCore import QSize, Qt, Signal
|
2026-06-16 08:44:17 +08:00
|
|
|
from PySide6.QtGui import QPixmap
|
|
|
|
|
from PySide6.QtWidgets import (
|
|
|
|
|
QCheckBox,
|
|
|
|
|
QComboBox,
|
|
|
|
|
QFileDialog,
|
|
|
|
|
QHBoxLayout,
|
|
|
|
|
QLabel,
|
|
|
|
|
QListWidget,
|
|
|
|
|
QListWidgetItem,
|
|
|
|
|
QPushButton,
|
|
|
|
|
QSizePolicy,
|
|
|
|
|
QSplitter,
|
|
|
|
|
QVBoxLayout,
|
|
|
|
|
QWidget,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
from core.models import ImageAsset
|
|
|
|
|
from services.file_service import scan_image_folder
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
2026-06-16 15:11:56 +08:00
|
|
|
_THUMB_SIZE = 84 # thumbnail size in px
|
|
|
|
|
_CARD_W = 104 # grid cell width
|
|
|
|
|
_CARD_H = 116 # grid cell height
|
2026-06-16 08:44:17 +08:00
|
|
|
|
|
|
|
|
# CheckState int values (avoids enum import issues across PySide6 versions)
|
|
|
|
|
_UNCHECKED = 0
|
|
|
|
|
_PARTIAL = 1
|
|
|
|
|
_CHECKED = 2
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class _AssetItemWidget(QWidget):
|
2026-06-16 15:11:56 +08:00
|
|
|
"""Grid card: thumbnail with checkbox overlay on top, filename below."""
|
2026-06-16 08:44:17 +08:00
|
|
|
|
|
|
|
|
check_changed = Signal()
|
|
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
|
self,
|
|
|
|
|
asset: ImageAsset,
|
|
|
|
|
root_folder: Optional[Path] = None,
|
|
|
|
|
parent=None,
|
|
|
|
|
):
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
self._asset = asset
|
|
|
|
|
self._root_folder = root_folder
|
|
|
|
|
# Required so setStyleSheet() paints the widget background
|
|
|
|
|
self.setAttribute(Qt.WA_StyledBackground, True)
|
|
|
|
|
self._setup_ui()
|
|
|
|
|
|
|
|
|
|
def _setup_ui(self):
|
2026-06-16 15:11:56 +08:00
|
|
|
self.setObjectName("assetCard")
|
|
|
|
|
self.setFixedSize(_CARD_W, _CARD_H)
|
2026-06-16 08:44:17 +08:00
|
|
|
|
2026-06-16 15:11:56 +08:00
|
|
|
col = QVBoxLayout(self)
|
|
|
|
|
col.setContentsMargins(4, 4, 4, 4)
|
|
|
|
|
col.setSpacing(3)
|
2026-06-16 08:44:17 +08:00
|
|
|
|
2026-06-16 15:11:56 +08:00
|
|
|
# Thumbnail with the batch-selection checkbox overlaid on its corner
|
|
|
|
|
thumb_box = QWidget()
|
|
|
|
|
thumb_box.setFixedSize(_THUMB_SIZE, _THUMB_SIZE)
|
|
|
|
|
|
|
|
|
|
self._thumb = QLabel(thumb_box)
|
2026-06-16 08:44:17 +08:00
|
|
|
self._thumb.setObjectName("thumbLabel")
|
2026-06-16 15:11:56 +08:00
|
|
|
self._thumb.setGeometry(0, 0, _THUMB_SIZE, _THUMB_SIZE)
|
|
|
|
|
self._thumb.setAlignment(Qt.AlignCenter)
|
2026-06-16 08:44:17 +08:00
|
|
|
self._load_thumbnail()
|
|
|
|
|
|
2026-06-16 15:11:56 +08:00
|
|
|
self._check = QCheckBox(thumb_box)
|
|
|
|
|
self._check.setObjectName("cardCheck")
|
|
|
|
|
self._check.setChecked(self._asset.selected)
|
|
|
|
|
self._check.stateChanged.connect(self._on_check_changed)
|
|
|
|
|
self._check.move(3, 3)
|
2026-06-16 08:44:17 +08:00
|
|
|
|
2026-06-16 15:11:56 +08:00
|
|
|
thumb_row = QHBoxLayout()
|
|
|
|
|
thumb_row.setContentsMargins(0, 0, 0, 0)
|
|
|
|
|
thumb_row.addStretch()
|
|
|
|
|
thumb_row.addWidget(thumb_box)
|
|
|
|
|
thumb_row.addStretch()
|
|
|
|
|
col.addLayout(thumb_row)
|
2026-06-16 08:44:17 +08:00
|
|
|
|
2026-06-16 15:11:56 +08:00
|
|
|
# Filename (middle-elided to fit the card width)
|
|
|
|
|
name = Path(self._asset.path).name
|
|
|
|
|
self._name_label = QLabel()
|
2026-06-16 08:44:17 +08:00
|
|
|
self._name_label.setObjectName("fileNameLabel")
|
2026-06-16 15:11:56 +08:00
|
|
|
self._name_label.setAlignment(Qt.AlignHCenter | Qt.AlignTop)
|
|
|
|
|
elided = self._name_label.fontMetrics().elidedText(
|
|
|
|
|
name, Qt.ElideMiddle, _CARD_W - 10
|
|
|
|
|
)
|
|
|
|
|
self._name_label.setText(elided)
|
|
|
|
|
col.addWidget(self._name_label)
|
|
|
|
|
col.addStretch()
|
2026-06-16 08:44:17 +08:00
|
|
|
|
2026-06-16 15:11:56 +08:00
|
|
|
# Subfolder + filename shown on hover (no room for a label in the grid)
|
|
|
|
|
subfolder = self._compute_subfolder()
|
|
|
|
|
self.setToolTip("{}/{}".format(subfolder, name) if subfolder else name)
|
2026-06-16 08:44:17 +08:00
|
|
|
|
|
|
|
|
def _compute_subfolder(self) -> str:
|
|
|
|
|
asset_path = Path(self._asset.path)
|
|
|
|
|
if self._root_folder:
|
|
|
|
|
try:
|
|
|
|
|
rel = asset_path.parent.relative_to(self._root_folder)
|
|
|
|
|
s = str(rel)
|
|
|
|
|
return "" if s == "." else s
|
|
|
|
|
except ValueError:
|
|
|
|
|
pass
|
|
|
|
|
name = asset_path.parent.name
|
|
|
|
|
return name if name else ""
|
|
|
|
|
|
|
|
|
|
def _load_thumbnail(self):
|
|
|
|
|
try:
|
|
|
|
|
pix = QPixmap(str(self._asset.path))
|
|
|
|
|
if not pix.isNull():
|
|
|
|
|
pix = pix.scaled(
|
|
|
|
|
_THUMB_SIZE, _THUMB_SIZE,
|
|
|
|
|
Qt.KeepAspectRatio,
|
|
|
|
|
Qt.SmoothTransformation,
|
|
|
|
|
)
|
|
|
|
|
self._thumb.setPixmap(pix)
|
|
|
|
|
return
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
self._thumb.setText("?")
|
|
|
|
|
|
|
|
|
|
def _on_check_changed(self):
|
|
|
|
|
self._asset.selected = self._check.isChecked()
|
|
|
|
|
self.check_changed.emit()
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def asset(self) -> ImageAsset:
|
|
|
|
|
return self._asset
|
|
|
|
|
|
|
|
|
|
def set_checked(self, checked: bool):
|
|
|
|
|
self._check.blockSignals(True)
|
|
|
|
|
self._check.setChecked(checked)
|
|
|
|
|
self._check.blockSignals(False)
|
|
|
|
|
self._asset.selected = checked
|
|
|
|
|
|
|
|
|
|
def set_preview_selected(self, active: bool):
|
|
|
|
|
"""Highlight or clear the current-preview indicator."""
|
|
|
|
|
if active:
|
2026-06-16 15:11:56 +08:00
|
|
|
self.setStyleSheet(
|
|
|
|
|
"#assetCard { background-color: #cce4f7; border-radius: 4px; }"
|
|
|
|
|
)
|
2026-06-16 08:44:17 +08:00
|
|
|
else:
|
|
|
|
|
self.setStyleSheet("")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class _AssetPanel(QWidget):
|
|
|
|
|
"""Panel for one asset type (garment or print)."""
|
|
|
|
|
|
|
|
|
|
preview_changed = Signal(object) # ImageAsset | None
|
|
|
|
|
assets_changed = Signal(list) # List[ImageAsset]
|
|
|
|
|
|
|
|
|
|
def __init__(self, title: str, open_text: str, parent=None):
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
self._assets: List[ImageAsset] = []
|
|
|
|
|
self._root_folder: Optional[Path] = None
|
|
|
|
|
self._current_preview_row: int = -1
|
|
|
|
|
self._setup_ui(title, open_text)
|
|
|
|
|
|
|
|
|
|
# ── UI construction ────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
def _setup_ui(self, title: str, open_text: str):
|
|
|
|
|
col = QVBoxLayout(self)
|
|
|
|
|
col.setContentsMargins(0, 0, 0, 0)
|
|
|
|
|
col.setSpacing(0)
|
|
|
|
|
|
|
|
|
|
col.addWidget(self._make_header(title, open_text))
|
|
|
|
|
col.addWidget(self._make_ctrl_row())
|
|
|
|
|
|
|
|
|
|
self._list = QListWidget()
|
|
|
|
|
self._list.setObjectName("assetList")
|
2026-06-16 15:11:56 +08:00
|
|
|
# Thumbnail grid: wrap items left-to-right, reflow on resize
|
|
|
|
|
self._list.setViewMode(QListWidget.IconMode)
|
|
|
|
|
self._list.setFlow(QListWidget.LeftToRight)
|
|
|
|
|
self._list.setWrapping(True)
|
|
|
|
|
self._list.setResizeMode(QListWidget.Adjust)
|
|
|
|
|
self._list.setMovement(QListWidget.Static)
|
|
|
|
|
self._list.setUniformItemSizes(True)
|
|
|
|
|
self._list.setGridSize(QSize(_CARD_W + 6, _CARD_H + 6))
|
|
|
|
|
self._list.setSpacing(2)
|
2026-06-16 08:44:17 +08:00
|
|
|
self._list.setVerticalScrollMode(QListWidget.ScrollPerPixel)
|
|
|
|
|
self._list.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
|
|
|
|
# Disable QListWidget's own selection highlight — preview uses custom style
|
|
|
|
|
self._list.setSelectionMode(QListWidget.NoSelection)
|
|
|
|
|
self._list.itemClicked.connect(self._on_item_clicked)
|
|
|
|
|
|
|
|
|
|
col.addWidget(self._list, 1)
|
|
|
|
|
|
|
|
|
|
def _make_header(self, title: str, open_text: str) -> QWidget:
|
|
|
|
|
header = QWidget()
|
|
|
|
|
header.setObjectName("assetPanelHeader")
|
|
|
|
|
row = QHBoxLayout(header)
|
|
|
|
|
row.setContentsMargins(8, 5, 8, 5)
|
|
|
|
|
|
|
|
|
|
title_lbl = QLabel(title)
|
|
|
|
|
title_lbl.setObjectName("assetPanelTitle")
|
|
|
|
|
|
|
|
|
|
self._open_btn = QPushButton(open_text)
|
|
|
|
|
self._open_btn.setObjectName("openFolderBtn")
|
|
|
|
|
self._open_btn.clicked.connect(self._open_folder)
|
|
|
|
|
|
|
|
|
|
row.addWidget(title_lbl)
|
|
|
|
|
row.addStretch()
|
|
|
|
|
row.addWidget(self._open_btn)
|
|
|
|
|
return header
|
|
|
|
|
|
|
|
|
|
def _make_ctrl_row(self) -> QWidget:
|
|
|
|
|
ctrl = QWidget()
|
|
|
|
|
ctrl.setObjectName("ctrlRow")
|
|
|
|
|
row = QHBoxLayout(ctrl)
|
|
|
|
|
row.setContentsMargins(8, 3, 8, 3)
|
|
|
|
|
row.setSpacing(4)
|
|
|
|
|
|
|
|
|
|
self._select_all_cb = QCheckBox("全选")
|
|
|
|
|
self._select_all_cb.setTristate(True)
|
|
|
|
|
self._select_all_cb.stateChanged.connect(self._on_select_all_changed)
|
|
|
|
|
|
|
|
|
|
self._stats_lbl = QLabel("共 0")
|
|
|
|
|
self._stats_lbl.setObjectName("statsLabel")
|
|
|
|
|
self._stats_lbl.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
|
|
|
|
|
|
|
|
|
|
self._sort_combo = QComboBox()
|
|
|
|
|
self._sort_combo.setObjectName("sortCombo")
|
|
|
|
|
self._sort_combo.addItem("文件夹+名称")
|
|
|
|
|
self._sort_combo.addItem("名称")
|
|
|
|
|
self._sort_combo.currentIndexChanged.connect(self._apply_sort)
|
|
|
|
|
|
|
|
|
|
row.addWidget(self._select_all_cb)
|
|
|
|
|
row.addWidget(self._stats_lbl, 1)
|
|
|
|
|
row.addWidget(self._sort_combo)
|
|
|
|
|
return ctrl
|
|
|
|
|
|
|
|
|
|
# ── folder scanning ────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
def _open_folder(self):
|
|
|
|
|
folder = QFileDialog.getExistingDirectory(self, "选择文件夹")
|
|
|
|
|
if not folder:
|
|
|
|
|
return
|
|
|
|
|
self._root_folder = Path(folder)
|
|
|
|
|
try:
|
|
|
|
|
self._assets = scan_image_folder(self._root_folder)
|
|
|
|
|
except OSError as exc:
|
|
|
|
|
logger.error("Failed to scan folder %s: %s", folder, exc)
|
|
|
|
|
return
|
|
|
|
|
logger.info("Loaded %d asset(s) from %s", len(self._assets), folder)
|
|
|
|
|
self._apply_sort()
|
|
|
|
|
self.assets_changed.emit(list(self._assets))
|
|
|
|
|
|
|
|
|
|
# ── sorting & list rebuild ─────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
def _apply_sort(self):
|
|
|
|
|
if self._sort_combo.currentIndex() == 0: # 文件夹+名称
|
|
|
|
|
self._assets.sort(
|
|
|
|
|
key=lambda a: (
|
|
|
|
|
str(Path(a.path).parent),
|
|
|
|
|
str(Path(a.path).name).lower(),
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
else: # 名称
|
|
|
|
|
self._assets.sort(key=lambda a: str(Path(a.path).name).lower())
|
|
|
|
|
self._rebuild_list()
|
|
|
|
|
|
|
|
|
|
def _rebuild_list(self):
|
|
|
|
|
self._list.clear()
|
|
|
|
|
self._current_preview_row = -1
|
|
|
|
|
|
|
|
|
|
for asset in self._assets:
|
|
|
|
|
item = QListWidgetItem()
|
|
|
|
|
widget = _AssetItemWidget(asset, self._root_folder)
|
|
|
|
|
widget.check_changed.connect(self._on_check_changed)
|
|
|
|
|
item.setSizeHint(widget.sizeHint())
|
|
|
|
|
self._list.addItem(item)
|
|
|
|
|
self._list.setItemWidget(item, widget)
|
|
|
|
|
|
|
|
|
|
self._refresh_stats()
|
|
|
|
|
self._refresh_select_all()
|
|
|
|
|
|
|
|
|
|
# ── interaction ────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
def _on_item_clicked(self, item: QListWidgetItem):
|
|
|
|
|
row = self._list.row(item)
|
|
|
|
|
|
|
|
|
|
# Clear previous preview highlight
|
|
|
|
|
if 0 <= self._current_preview_row < self._list.count():
|
|
|
|
|
prev = self._list.item(self._current_preview_row)
|
|
|
|
|
if prev:
|
|
|
|
|
w = self._list.itemWidget(prev)
|
|
|
|
|
if w:
|
|
|
|
|
w.set_preview_selected(False)
|
|
|
|
|
|
|
|
|
|
self._current_preview_row = row
|
|
|
|
|
widget = self._list.itemWidget(item)
|
|
|
|
|
if widget:
|
|
|
|
|
widget.set_preview_selected(True)
|
|
|
|
|
self.preview_changed.emit(widget.asset)
|
|
|
|
|
|
|
|
|
|
def _on_check_changed(self):
|
|
|
|
|
self._refresh_stats()
|
|
|
|
|
self._refresh_select_all()
|
|
|
|
|
self.assets_changed.emit(list(self._assets))
|
|
|
|
|
|
|
|
|
|
def _on_select_all_changed(self, state: int):
|
|
|
|
|
if state == _PARTIAL: # programmatic tristate — ignore
|
|
|
|
|
return
|
|
|
|
|
checked = (state == _CHECKED)
|
|
|
|
|
for i in range(self._list.count()):
|
|
|
|
|
w = self._list.itemWidget(self._list.item(i))
|
|
|
|
|
if w:
|
|
|
|
|
w.set_checked(checked)
|
|
|
|
|
self._refresh_stats()
|
|
|
|
|
self.assets_changed.emit(list(self._assets))
|
|
|
|
|
|
|
|
|
|
# ── stat helpers ───────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
def _has_subfolders(self) -> bool:
|
|
|
|
|
if not self._root_folder:
|
|
|
|
|
return False
|
|
|
|
|
return any(
|
|
|
|
|
Path(a.path).parent != self._root_folder for a in self._assets
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def _refresh_stats(self):
|
|
|
|
|
n = len(self._assets)
|
|
|
|
|
sel = sum(1 for a in self._assets if a.selected)
|
|
|
|
|
prefix = "含子文件夹 · " if self._has_subfolders() else ""
|
|
|
|
|
self._stats_lbl.setText("{}共 {} 已选 {}".format(prefix, n, sel))
|
|
|
|
|
|
|
|
|
|
def _refresh_select_all(self):
|
|
|
|
|
n = len(self._assets)
|
|
|
|
|
self._select_all_cb.blockSignals(True)
|
|
|
|
|
if n == 0:
|
|
|
|
|
self._select_all_cb.setCheckState(Qt.Unchecked)
|
|
|
|
|
else:
|
|
|
|
|
sel = sum(1 for a in self._assets if a.selected)
|
|
|
|
|
if sel == 0:
|
|
|
|
|
self._select_all_cb.setCheckState(Qt.Unchecked)
|
|
|
|
|
elif sel == n:
|
|
|
|
|
self._select_all_cb.setCheckState(Qt.Checked)
|
|
|
|
|
else:
|
|
|
|
|
self._select_all_cb.setCheckState(Qt.PartiallyChecked)
|
|
|
|
|
self._select_all_cb.blockSignals(False)
|
|
|
|
|
|
|
|
|
|
def get_assets(self) -> List[ImageAsset]:
|
|
|
|
|
return list(self._assets)
|
2026-06-15 15:53:01 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class ImageListPanel(QWidget):
|
2026-06-16 08:44:17 +08:00
|
|
|
"""Left-panel container: garment list (top) + print list (bottom)."""
|
|
|
|
|
|
|
|
|
|
garment_preview_changed = Signal(object) # ImageAsset | None
|
|
|
|
|
print_preview_changed = Signal(object) # ImageAsset | None
|
|
|
|
|
garments_changed = Signal(list) # List[ImageAsset]
|
|
|
|
|
prints_changed = Signal(list) # List[ImageAsset]
|
|
|
|
|
|
|
|
|
|
def __init__(self, parent=None):
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
self._setup_ui()
|
|
|
|
|
|
|
|
|
|
def _setup_ui(self):
|
|
|
|
|
col = QVBoxLayout(self)
|
|
|
|
|
col.setContentsMargins(0, 0, 0, 0)
|
|
|
|
|
col.setSpacing(0)
|
|
|
|
|
|
|
|
|
|
splitter = QSplitter(Qt.Vertical)
|
|
|
|
|
splitter.setHandleWidth(3)
|
|
|
|
|
splitter.setObjectName("assetSplitter")
|
|
|
|
|
|
|
|
|
|
self._garment_panel = _AssetPanel("衣服图片", "打开衣服文件夹")
|
|
|
|
|
self._print_panel = _AssetPanel("印花图片", "打开印花文件夹")
|
|
|
|
|
|
|
|
|
|
self._garment_panel.preview_changed.connect(self.garment_preview_changed)
|
|
|
|
|
self._print_panel.preview_changed.connect(self.print_preview_changed)
|
|
|
|
|
self._garment_panel.assets_changed.connect(self.garments_changed)
|
|
|
|
|
self._print_panel.assets_changed.connect(self.prints_changed)
|
|
|
|
|
|
|
|
|
|
splitter.addWidget(self._garment_panel)
|
|
|
|
|
splitter.addWidget(self._print_panel)
|
|
|
|
|
splitter.setStretchFactor(0, 1)
|
|
|
|
|
splitter.setStretchFactor(1, 1)
|
|
|
|
|
|
|
|
|
|
col.addWidget(splitter)
|
|
|
|
|
self._apply_stylesheet()
|
|
|
|
|
|
|
|
|
|
def _apply_stylesheet(self):
|
|
|
|
|
self.setStyleSheet("""
|
|
|
|
|
#assetPanelHeader {
|
|
|
|
|
background-color: #f7f7f7;
|
|
|
|
|
border-bottom: 1px solid #e0e0e0;
|
|
|
|
|
}
|
|
|
|
|
#assetPanelTitle {
|
|
|
|
|
font-family: "Microsoft YaHei", "Segoe UI", sans-serif;
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
color: #1a1a1a;
|
|
|
|
|
}
|
|
|
|
|
#openFolderBtn {
|
|
|
|
|
font-size: 12px;
|
2026-06-16 15:11:56 +08:00
|
|
|
font-weight: bold;
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
padding: 5px 12px;
|
|
|
|
|
border: none;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
background: #0078d4;
|
2026-06-16 08:44:17 +08:00
|
|
|
}
|
2026-06-16 15:11:56 +08:00
|
|
|
#openFolderBtn:hover { background: #106ebe; }
|
|
|
|
|
#openFolderBtn:pressed { background: #005a9e; }
|
2026-06-16 08:44:17 +08:00
|
|
|
#ctrlRow {
|
|
|
|
|
background-color: #fafafa;
|
|
|
|
|
border-bottom: 1px solid #eeeeee;
|
|
|
|
|
}
|
|
|
|
|
#statsLabel {
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
color: #666666;
|
|
|
|
|
}
|
|
|
|
|
#sortCombo {
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
max-width: 96px;
|
|
|
|
|
}
|
|
|
|
|
#assetList {
|
|
|
|
|
background-color: #ffffff;
|
|
|
|
|
border: none;
|
|
|
|
|
}
|
|
|
|
|
#assetList::item {
|
|
|
|
|
background: transparent;
|
|
|
|
|
border: none;
|
|
|
|
|
}
|
|
|
|
|
#fileNameLabel {
|
2026-06-16 15:11:56 +08:00
|
|
|
font-size: 11px;
|
|
|
|
|
color: #333333;
|
2026-06-16 08:44:17 +08:00
|
|
|
}
|
|
|
|
|
#thumbLabel {
|
|
|
|
|
background-color: #f0f0f0;
|
|
|
|
|
border: 1px solid #e0e0e0;
|
|
|
|
|
border-radius: 2px;
|
|
|
|
|
color: #aaaaaa;
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
}
|
2026-06-16 15:11:56 +08:00
|
|
|
#cardCheck {
|
|
|
|
|
background-color: rgba(255, 255, 255, 0.75);
|
|
|
|
|
border-radius: 2px;
|
|
|
|
|
}
|
2026-06-16 08:44:17 +08:00
|
|
|
QSplitter#assetSplitter::handle {
|
|
|
|
|
background-color: #d6d6d6;
|
|
|
|
|
height: 3px;
|
|
|
|
|
}
|
|
|
|
|
""")
|
|
|
|
|
|
|
|
|
|
def get_garments(self) -> List[ImageAsset]:
|
|
|
|
|
return self._garment_panel.get_assets()
|
|
|
|
|
|
|
|
|
|
def get_prints(self) -> List[ImageAsset]:
|
|
|
|
|
return self._print_panel.get_assets()
|