feat: implement template panel UI with picker, save, save-as, reset
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,236 @@
|
||||
from PySide6.QtWidgets import QWidget
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from PySide6.QtCore import Signal
|
||||
from PySide6.QtWidgets import (
|
||||
QComboBox,
|
||||
QHBoxLayout,
|
||||
QInputDialog,
|
||||
QLabel,
|
||||
QMessageBox,
|
||||
QPushButton,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from core.models import Template, TransformState
|
||||
from services.template_service import add_template, get_all_templates
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_BUILTIN_PREFIX = "★ "
|
||||
_CUSTOM_PREFIX = "◆ "
|
||||
|
||||
|
||||
class TemplatePanel(QWidget):
|
||||
pass
|
||||
"""Right-panel section: template picker, save, save-as, reset."""
|
||||
|
||||
template_applied = Signal(object) # emits TransformState
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._current_state: Optional[TransformState] = None
|
||||
self._garment_w: float = 0.0
|
||||
self._garment_h: float = 0.0
|
||||
self._updating = False
|
||||
self._setup_ui()
|
||||
self._load_templates()
|
||||
self._apply_stylesheet()
|
||||
|
||||
# ── UI construction ──────────────────────────────────────────────────────
|
||||
|
||||
def _setup_ui(self):
|
||||
col = QVBoxLayout(self)
|
||||
col.setContentsMargins(0, 0, 0, 0)
|
||||
col.setSpacing(0)
|
||||
|
||||
header = QLabel("模板")
|
||||
header.setObjectName("templateSectionHeader")
|
||||
col.addWidget(header)
|
||||
|
||||
body = QWidget()
|
||||
body.setObjectName("templateBody")
|
||||
body_col = QVBoxLayout(body)
|
||||
body_col.setContentsMargins(10, 8, 10, 10)
|
||||
body_col.setSpacing(8)
|
||||
|
||||
self._combo = QComboBox()
|
||||
self._combo.setObjectName("templateCombo")
|
||||
self._combo.currentIndexChanged.connect(self._on_template_selected)
|
||||
body_col.addWidget(self._combo)
|
||||
|
||||
btn_row = QHBoxLayout()
|
||||
btn_row.setSpacing(6)
|
||||
|
||||
self._save_btn = QPushButton("保存")
|
||||
self._save_btn.setObjectName("templateBtn")
|
||||
self._save_btn.setToolTip("覆盖当前自定义模板")
|
||||
self._save_btn.clicked.connect(self._save_template)
|
||||
|
||||
self._saveas_btn = QPushButton("另存为")
|
||||
self._saveas_btn.setObjectName("templateBtn")
|
||||
self._saveas_btn.setToolTip("将当前参数保存为新模板")
|
||||
self._saveas_btn.clicked.connect(self._save_as_template)
|
||||
|
||||
btn_row.addWidget(self._save_btn)
|
||||
btn_row.addWidget(self._saveas_btn)
|
||||
body_col.addLayout(btn_row)
|
||||
|
||||
self._reset_btn = QPushButton("重置为模板")
|
||||
self._reset_btn.setObjectName("templateBtn")
|
||||
self._reset_btn.setToolTip("将印花参数还原为所选模板的默认值")
|
||||
self._reset_btn.clicked.connect(self._reset_to_template)
|
||||
body_col.addWidget(self._reset_btn)
|
||||
|
||||
col.addWidget(body)
|
||||
|
||||
# ── template list management ─────────────────────────────────────────────
|
||||
|
||||
def _load_templates(self, select_name: Optional[str] = None):
|
||||
"""Reload combo from template_service; restore selection by name if given."""
|
||||
templates = get_all_templates()
|
||||
self._updating = True
|
||||
try:
|
||||
self._combo.blockSignals(True)
|
||||
self._combo.clear()
|
||||
for t in templates:
|
||||
prefix = _BUILTIN_PREFIX if t.type == "builtin" else _CUSTOM_PREFIX
|
||||
self._combo.addItem(prefix + t.name, userData=t)
|
||||
if select_name:
|
||||
for i in range(self._combo.count()):
|
||||
t = self._combo.itemData(i)
|
||||
if t and t.name == select_name:
|
||||
self._combo.setCurrentIndex(i)
|
||||
break
|
||||
finally:
|
||||
self._combo.blockSignals(False)
|
||||
self._updating = False
|
||||
self._update_button_states()
|
||||
|
||||
def _update_button_states(self):
|
||||
t = self._current_template()
|
||||
is_builtin = (t is None) or (t.type == "builtin")
|
||||
self._save_btn.setEnabled(not is_builtin)
|
||||
|
||||
def _current_template(self) -> Optional[Template]:
|
||||
return self._combo.currentData()
|
||||
|
||||
# ── public API ───────────────────────────────────────────────────────────
|
||||
|
||||
def set_garment_size(self, w: float, h: float):
|
||||
"""Update garment dimensions (pixels) used for ratio <-> pixel conversion."""
|
||||
self._garment_w = w
|
||||
self._garment_h = h
|
||||
|
||||
def set_transform(self, state: TransformState):
|
||||
"""Store current print transform (used when saving a template)."""
|
||||
self._current_state = state
|
||||
|
||||
# ── signal handlers ──────────────────────────────────────────────────────
|
||||
|
||||
def _on_template_selected(self, index: int):
|
||||
if self._updating:
|
||||
return
|
||||
self._update_button_states()
|
||||
t = self._combo.itemData(index)
|
||||
if t and self._garment_w > 0 and self._garment_h > 0:
|
||||
state = t.to_transform_state(self._garment_w, self._garment_h)
|
||||
logger.debug("Template applied: %s", t.name)
|
||||
self.template_applied.emit(state)
|
||||
|
||||
def _reset_to_template(self):
|
||||
"""Re-apply the currently selected template."""
|
||||
t = self._current_template()
|
||||
if not t:
|
||||
return
|
||||
if self._garment_w <= 0 or self._garment_h <= 0:
|
||||
QMessageBox.information(self, "提示", "请先加载衣服图片。")
|
||||
return
|
||||
state = t.to_transform_state(self._garment_w, self._garment_h)
|
||||
self.template_applied.emit(state)
|
||||
|
||||
def _save_template(self):
|
||||
"""Overwrite the selected custom template with the current transform."""
|
||||
t = self._current_template()
|
||||
if not t or t.type == "builtin":
|
||||
QMessageBox.warning(
|
||||
self, "无法保存",
|
||||
"内置模板不可修改。\n请使用「另存为」创建自定义模板。",
|
||||
)
|
||||
return
|
||||
if not self._current_state:
|
||||
QMessageBox.information(self, "提示", "当前没有可保存的印花参数。")
|
||||
return
|
||||
if self._garment_w <= 0 or self._garment_h <= 0:
|
||||
QMessageBox.information(self, "提示", "请先加载衣服图片。")
|
||||
return
|
||||
add_template(self._state_to_template(t.name))
|
||||
logger.info("Template saved: %s", t.name)
|
||||
self._load_templates(select_name=t.name)
|
||||
|
||||
def _save_as_template(self):
|
||||
"""Create a new custom template from the current transform."""
|
||||
if not self._current_state:
|
||||
QMessageBox.information(self, "提示", "当前没有可保存的印花参数。")
|
||||
return
|
||||
if self._garment_w <= 0 or self._garment_h <= 0:
|
||||
QMessageBox.information(self, "提示", "请先加载衣服图片。")
|
||||
return
|
||||
name, ok = QInputDialog.getText(self, "另存为模板", "请输入模板名称:")
|
||||
if not ok:
|
||||
return
|
||||
name = name.strip()
|
||||
if not name:
|
||||
QMessageBox.warning(self, "名称无效", "模板名称不能为空。")
|
||||
return
|
||||
add_template(self._state_to_template(name))
|
||||
logger.info("Template saved-as: %s", name)
|
||||
self._load_templates(select_name=name)
|
||||
|
||||
# ── helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
def _state_to_template(self, name: str) -> Template:
|
||||
s = self._current_state
|
||||
gw, gh = self._garment_w, self._garment_h
|
||||
return Template(
|
||||
name=name,
|
||||
x_ratio=s.x / gw,
|
||||
y_ratio=s.y / gh,
|
||||
width_ratio=s.width / gw,
|
||||
height_ratio=s.height / gh,
|
||||
rotation=s.rotation,
|
||||
type="custom",
|
||||
)
|
||||
|
||||
def _apply_stylesheet(self):
|
||||
self.setStyleSheet("""
|
||||
#templateSectionHeader {
|
||||
font-family: "Microsoft YaHei", "Segoe UI", sans-serif;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
color: #555555;
|
||||
background-color: #f7f7f7;
|
||||
border-bottom: 1px solid #eeeeee;
|
||||
padding: 4px 10px;
|
||||
}
|
||||
#templateBody {
|
||||
background-color: #ffffff;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
#templateCombo {
|
||||
font-size: 12px;
|
||||
border: 1px solid #d6d6d6;
|
||||
border-radius: 3px;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
#templateBtn {
|
||||
font-size: 12px;
|
||||
padding: 4px 8px;
|
||||
border: 1px solid #d6d6d6;
|
||||
border-radius: 3px;
|
||||
background: #f0f0f0;
|
||||
}
|
||||
#templateBtn:hover { background: #e0e0e0; }
|
||||
#templateBtn:pressed { background: #d0d0d0; }
|
||||
#templateBtn:disabled { color: #aaaaaa; background: #f7f7f7; }
|
||||
""")
|
||||
|
||||
Reference in New Issue
Block a user