feat: template delete, whole-reset relocation, and compact param/export rows
- Template delete: add a 删除 button (reuses template_service.delete_template) with a confirm dialog; hidden for built-ins like 保存; reloads and re-selects the first built-in after deletion, persisting the new selection. - Whole reset: move the template-wide reset out of the template area to the bottom of the transform panel and rename it to 位置尺寸旋转重置为当前模板, so it sits with the params it resets, beside the three per-section resets. - Compact layout: pair X/Y and 宽/高 onto single rows (short labels), and put the export 格式/质量 selectors on one row. Sync logic unchanged. Docs: PRD 6.6, UI design 7.1/7.2/7.3/7.4/7.5/10, tasks.md 17.4/17.5/17.6. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+3
-1
@@ -165,7 +165,9 @@
|
||||
- 内置模板尺寸以贴近真实印花占比为准(例如正方形模板约为衣服宽度的 1/3),避免印花占据过大面积。
|
||||
- 选中的模板是**粘性**的:在预览区切换衣服或切换印花时,印花不应丢失,应按当前选中模板基于新的衣服/印花尺寸重新计算落位,而非回退到画布的写死默认值。已手动微调的项不受影响,仍使用其自身参数。
|
||||
- **记住上次选择的模板**:程序退出前持久化当前选中的模板,下次启动自动恢复选中该模板。若该模板为自定义模板且已被删除,则回退到第一个内置模板。
|
||||
- 提供 `位置` / `尺寸` / `旋转` **三项独立的重置**,分别只把对应参数还原为当前选中模板的值(位置→模板居中位置;尺寸→模板目标框内适配的大小;旋转→模板角度,通常为 0),三者互不影响;不再使用单一的合并重置。未选模板或未加载衣服/印花时,重置不可用。
|
||||
- 提供 `位置` / `尺寸` / `旋转` **三项独立的重置**,分别只把对应参数还原为当前选中模板的值(位置→模板居中位置;尺寸→模板目标框内适配的大小;旋转→模板角度,通常为 0),三者互不影响。
|
||||
- 另在参数栏底部提供 `位置尺寸旋转重置为当前模板`,一次性把位置、尺寸、旋转整体还原为模板,作为三项分项重置的「全部」入口。该按钮归属「调整参数」,放在参数栏(紧挨它操作的参数),不放在模板管理区域。
|
||||
- 未选模板或未加载衣服/印花时,以上所有重置不可用。
|
||||
|
||||
### 6.7 自定义模板
|
||||
|
||||
|
||||
+28
-13
@@ -195,7 +195,9 @@ Shift 锁比例
|
||||
- 尺寸重置 → 还原为模板目标框内适配的大小(宽 / 高)。
|
||||
- 旋转重置 → 还原为模板角度(通常为 0)。
|
||||
|
||||
未选模板或未加载衣服/印花时三个重置按钮置灰。视觉层级见第 10 节「重置类按钮」。
|
||||
参数栏底部另有一个全宽的 `位置尺寸旋转重置为当前模板` 按钮,一次性把位置、尺寸、旋转整体还原为当前选中模板,作为三个分项重置的「全部」入口。它紧挨它所操作的参数,不放在模板区域。
|
||||
|
||||
未选模板或未加载衣服/印花时,三个分项重置与 `位置尺寸旋转重置为当前模板` 一并置灰。视觉层级见第 10 节「重置类按钮」。
|
||||
|
||||
### 7.1 模板区域
|
||||
|
||||
@@ -204,7 +206,9 @@ Shift 锁比例
|
||||
- 模板下拉框。
|
||||
- `保存` 按钮。
|
||||
- `另存为` 按钮。
|
||||
- `重置为模板` 按钮(把印花参数一次性整体还原为所选模板)。
|
||||
- `删除` 按钮。
|
||||
|
||||
模板区域只管模板本身(选择 / 保存 / 另存为 / 删除)。把印花参数整体还原为模板的 `位置尺寸旋转重置为当前模板` 属于「调整参数」而非「管理模板」,因此放在参数栏底部(见 7 节开头与 7.4)。
|
||||
|
||||
示例模板:
|
||||
|
||||
@@ -217,19 +221,22 @@ Shift 锁比例
|
||||
- 选择模板后更新当前印花位置、尺寸和旋转。
|
||||
- 保存用于覆盖当前自定义模板。
|
||||
- 另存为用于创建新模板。
|
||||
- `保存` 仅对自定义模板有意义:选中内置模板时**隐藏**该按钮(而非禁用置灰),避免出现长期灰显且无法说明原因的控件。
|
||||
- `保存` 与 `删除` 仅对自定义模板有意义:选中内置模板时**隐藏**这两个按钮(而非禁用置灰),避免出现长期灰显且无法说明原因的控件。
|
||||
- `删除` 为破坏性操作,点击后必须二次确认(弹「确定删除模板「X」?此操作不可撤销。」),确认后才删除;删除后下拉框回选第一个内置模板。
|
||||
- `删除` 用红色文字提示危险,但保持安静(不使用红色实心块);删除可逆性低,区别于「重置」类的可逆操作。
|
||||
|
||||
### 7.2 位置区域
|
||||
|
||||
控件:
|
||||
|
||||
- `X 坐标` 数字输入框。
|
||||
- `Y 坐标` 数字输入框。
|
||||
- `X` 数字输入框。
|
||||
- `Y` 数字输入框。
|
||||
- 分区标题右侧的 `重置` 按钮(还原 X / Y 为模板值)。
|
||||
|
||||
要求:
|
||||
|
||||
- 单位为衣服底图原始像素。
|
||||
- X、Y 是一对坐标,放在**同一行**(标签简化为 `X` / `Y`),节省纵向空间。
|
||||
- 用户输入后实时或确认后更新预览。
|
||||
- 预览区拖动后同步更新输入框。
|
||||
|
||||
@@ -237,14 +244,15 @@ Shift 锁比例
|
||||
|
||||
控件:
|
||||
|
||||
- `宽度` 数字输入框。
|
||||
- `高度` 数字输入框。
|
||||
- `宽` 数字输入框。
|
||||
- `高` 数字输入框。
|
||||
- `锁定宽高比例` 复选框。
|
||||
- 分区标题右侧的 `重置` 按钮(仅还原宽 / 高为模板值,位置 X / Y 不变)。
|
||||
|
||||
要求:
|
||||
|
||||
- 单位为像素。
|
||||
- 宽、高是一对,放在**同一行**(标签简化为 `宽` / `高`),`锁定宽高比例` 复选框在其下方。
|
||||
- 默认锁定宽高比例。
|
||||
- 预览区缩放后同步更新宽度和高度。
|
||||
- 输入框修改后同步更新预览。
|
||||
@@ -264,7 +272,8 @@ Shift 锁比例
|
||||
- 支持负角度。
|
||||
- 顶部旋转控制点拖动后同步角度输入框。
|
||||
- 按钮旋转后同步预览和角度输入框。
|
||||
- 旋转分区标题的 `重置` 已覆盖「拉直」需求,不再单独提供 `归零` 按钮;也不再提供合并的 `重置位置 / 尺寸 / 角度` 按钮,统一由三个分区各自的独立重置取代。
|
||||
- 旋转分区标题的 `重置` 已覆盖「拉直」需求,不再单独提供 `归零` 按钮。
|
||||
- 旋转分区下方(参数栏底部)提供 `位置尺寸旋转重置为当前模板`,把位置 / 尺寸 / 旋转一并还原为所选模板;它和三个分项重置构成「全部 + 分项」两级还原。
|
||||
|
||||
### 7.5 输出设置区域
|
||||
|
||||
@@ -281,6 +290,7 @@ Shift 锁比例
|
||||
- 格式至少支持 PNG 和 JPG。
|
||||
- PNG 默认保留透明相关能力;JPG 需要处理背景。
|
||||
- 质量设置用于 JPG 或需要压缩的输出格式。
|
||||
- `格式` 与 `质量` 是一对相关设置,放在**同一行**;`质量` 仅在选中 JPG 时可用(PNG 时置灰),并排能体现「先选格式 → 质量才可用」的关系。
|
||||
|
||||
### 7.6 提示信息区域
|
||||
|
||||
@@ -456,16 +466,21 @@ Microsoft YaHei
|
||||
按钮层级(一套主色,按角色克制使用):
|
||||
|
||||
- **主按钮**:主色填充、白字。用于每个区域「推动流程前进」的唯一动作:左侧 `打开文件夹`、右侧 `导出当前单张`、底部 `开始批量导出`。三者分处不同区域,各自是本区域主操作,不互相竞争。
|
||||
- **次按钮**:白底、主色描边与文字。用于有意义但非主线的动作,如 `应用为模板`、导出区的 `重置为模板`。
|
||||
- **辅助按钮**:浅灰底、细边框。用于就地微调类动作,如 `浏览…`、排序、`左旋 / 右旋`。
|
||||
- **重置类按钮**:最安静的一档(幽灵样式),用于 `位置 / 尺寸 / 旋转` 三个分区的独立重置。它们会重复出现且属于「纠正/撤销」类动作,不应占用常驻强调色;强调色只在悬停时短暂出现以表达意图。重置可逆,因此**不使用红色/警告色**。
|
||||
- **次按钮**:白底、主色描边与文字。用于有意义但非主线的动作,如模板区域的 `另存为`。
|
||||
- **辅助按钮**:浅灰底、细边框。用于就地微调类动作,如 `浏览…`、排序、`左旋 / 右旋`、模板区域的 `保存`。
|
||||
- **重置类按钮**:最安静的一档,用于「还原为模板」的动作(三个分区的 `↺ 重置` 与底部的 `位置尺寸旋转重置为当前模板`)。它们属于「纠正/撤销」类动作,不占用常驻强调色;强调色只在悬停时短暂出现以表达意图。重置可逆,因此**不使用红色/警告色**。
|
||||
|
||||
```text
|
||||
重置类按钮(分区标题内的 ↺ 重置):
|
||||
分区标题内的 ↺ 重置(幽灵样式):
|
||||
静止:背景 透明 / #f7f7f7 图标+文字 #8a8a8a 无边框
|
||||
悬停:背景 #eaf3fc 图标+文字 #0078d4
|
||||
按下:背景 #d6e8f9
|
||||
禁用:文字 #c0c0c0 不响应悬停(未选模板或未加载图片时)
|
||||
禁用:文字 #c0c0c0 不响应悬停
|
||||
|
||||
底部 位置尺寸旋转重置为当前模板(全宽,安静但有存在感):
|
||||
静止:背景 #f0f0f0 文字 #555555 边框 #d6d6d6
|
||||
悬停:背景 #eaf3fc 文字 #0078d4 边框 #0078d4
|
||||
禁用:文字 #c0c0c0 背景 #f7f7f7
|
||||
```
|
||||
|
||||
避免所有按钮使用同一种灰色样式而丧失层级;同一动作在不同位置应使用一致的文案与层级。
|
||||
|
||||
@@ -277,6 +277,8 @@ class MainWindow(QMainWindow):
|
||||
self.transform_panel.reset_position.connect(lambda: self._reset_aspect("pos"))
|
||||
self.transform_panel.reset_size.connect(lambda: self._reset_aspect("size"))
|
||||
self.transform_panel.reset_rotation.connect(lambda: self._reset_aspect("rot"))
|
||||
# Whole reset: restore position + size + rotation to the selected template
|
||||
self.transform_panel.reset_all.connect(self._reset_all_to_template)
|
||||
|
||||
# ── Template panel ─────────────────────────────────────────────────
|
||||
# Template selection updates canvas, param panel, and queue template
|
||||
@@ -403,6 +405,18 @@ class MainWindow(QMainWindow):
|
||||
new.rotation = tpl.rotation
|
||||
self._apply_preview_transform(new)
|
||||
|
||||
def _reset_all_to_template(self):
|
||||
"""Restore position + size + rotation to the selected template at once."""
|
||||
tpl = self.template_panel.current_template_state()
|
||||
cur = self.image_canvas.get_transform()
|
||||
if tpl is None or cur is None:
|
||||
return
|
||||
new = TransformState(
|
||||
x=tpl.x, y=tpl.y, width=tpl.width, height=tpl.height,
|
||||
rotation=tpl.rotation, keep_aspect_ratio=cur.keep_aspect_ratio,
|
||||
)
|
||||
self._apply_preview_transform(new)
|
||||
|
||||
def _apply_preview_transform(self, state):
|
||||
"""Push a transform to the canvas + panels as if it were a user edit."""
|
||||
self.image_canvas.set_transform(state)
|
||||
|
||||
@@ -6,7 +6,6 @@ from PySide6.QtCore import Qt
|
||||
from PySide6.QtWidgets import (
|
||||
QComboBox,
|
||||
QFileDialog,
|
||||
QFormLayout,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QLineEdit,
|
||||
@@ -77,17 +76,15 @@ class ExportPanel(QWidget):
|
||||
row.addWidget(browse_btn)
|
||||
return row
|
||||
|
||||
def _make_format_form(self) -> QFormLayout:
|
||||
form = QFormLayout()
|
||||
form.setSpacing(6)
|
||||
form.setLabelAlignment(Qt.AlignRight | Qt.AlignVCenter)
|
||||
def _make_format_form(self) -> QHBoxLayout:
|
||||
row = QHBoxLayout()
|
||||
row.setSpacing(6)
|
||||
|
||||
self._format_combo = QComboBox()
|
||||
self._format_combo.setObjectName("exportCombo")
|
||||
self._format_combo.addItem("PNG", "PNG")
|
||||
self._format_combo.addItem("JPG", "JPG")
|
||||
self._format_combo.currentIndexChanged.connect(self._on_format_changed)
|
||||
form.addRow("格式", self._format_combo)
|
||||
|
||||
self._quality_combo = QComboBox()
|
||||
self._quality_combo.setObjectName("exportCombo")
|
||||
@@ -95,9 +92,13 @@ class ExportPanel(QWidget):
|
||||
self._quality_combo.addItem(label, val)
|
||||
self._quality_combo.setCurrentIndex(1) # default: 高 (90)
|
||||
self._quality_combo.setEnabled(False) # enabled only for JPG
|
||||
form.addRow("质量", self._quality_combo)
|
||||
|
||||
return form
|
||||
# 格式 and 质量 share one row (quality lights up only for JPG)
|
||||
row.addWidget(QLabel("格式"))
|
||||
row.addWidget(self._format_combo, 1)
|
||||
row.addWidget(QLabel("质量"))
|
||||
row.addWidget(self._quality_combo, 1)
|
||||
return row
|
||||
|
||||
def _make_hint_label(self) -> QLabel:
|
||||
self._hint_lbl = QLabel("加载衣服和印花图片后即可导出。")
|
||||
|
||||
@@ -14,7 +14,7 @@ from PySide6.QtWidgets import (
|
||||
)
|
||||
|
||||
from core.models import Template, TransformState
|
||||
from services.template_service import add_template, get_all_templates
|
||||
from services.template_service import add_template, delete_template, get_all_templates
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -75,16 +75,16 @@ class TemplatePanel(QWidget):
|
||||
self._saveas_btn.setToolTip("将当前参数保存为新模板")
|
||||
self._saveas_btn.clicked.connect(self._save_as_template)
|
||||
|
||||
self._delete_btn = QPushButton("删除")
|
||||
self._delete_btn.setObjectName("templateDeleteBtn")
|
||||
self._delete_btn.setToolTip("删除当前自定义模板")
|
||||
self._delete_btn.clicked.connect(self._delete_template)
|
||||
|
||||
btn_row.addWidget(self._save_btn)
|
||||
btn_row.addWidget(self._saveas_btn)
|
||||
btn_row.addWidget(self._delete_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 ─────────────────────────────────────────────
|
||||
@@ -113,9 +113,10 @@ class TemplatePanel(QWidget):
|
||||
def _update_button_states(self):
|
||||
t = self._current_template()
|
||||
is_builtin = (t is None) or (t.type == "builtin")
|
||||
# "保存" overwrites a custom template — meaningless for built-ins, so
|
||||
# hide it entirely rather than leaving a permanently-greyed button.
|
||||
# "保存"/"删除" only apply to custom templates — hide them for built-ins
|
||||
# rather than leaving permanently-greyed buttons.
|
||||
self._save_btn.setVisible(not is_builtin)
|
||||
self._delete_btn.setVisible(not is_builtin)
|
||||
|
||||
def _current_template(self) -> Optional[Template]:
|
||||
return self._combo.currentData()
|
||||
@@ -182,17 +183,6 @@ class TemplatePanel(QWidget):
|
||||
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 = self._state_for_template(t)
|
||||
self.template_applied.emit(state)
|
||||
|
||||
def _save_template(self):
|
||||
"""Overwrite the selected custom template with the current transform."""
|
||||
t = self._current_template()
|
||||
@@ -233,6 +223,29 @@ class TemplatePanel(QWidget):
|
||||
self._load_templates(select_name=name)
|
||||
self.template_changed.emit(name)
|
||||
|
||||
def _delete_template(self):
|
||||
"""Delete the selected custom template after confirmation."""
|
||||
t = self._current_template()
|
||||
if not t or t.type == "builtin":
|
||||
return
|
||||
reply = QMessageBox.question(
|
||||
self, "删除模板",
|
||||
"确定删除模板「{}」?此操作不可撤销。".format(t.name),
|
||||
QMessageBox.Yes | QMessageBox.No, QMessageBox.No,
|
||||
)
|
||||
if reply != QMessageBox.Yes:
|
||||
return
|
||||
try:
|
||||
delete_template(t.name)
|
||||
except ValueError as exc:
|
||||
QMessageBox.warning(self, "删除失败", str(exc))
|
||||
return
|
||||
logger.info("Template deleted: %s", t.name)
|
||||
# Reload; selection falls back to the first (built-in) template
|
||||
self._load_templates()
|
||||
cur = self._current_template()
|
||||
self.template_changed.emit(cur.name if cur else "")
|
||||
|
||||
# ── helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
def _state_to_template(self, name: str) -> Template:
|
||||
@@ -279,4 +292,14 @@ class TemplatePanel(QWidget):
|
||||
#templateBtn:hover { background: #e0e0e0; }
|
||||
#templateBtn:pressed { background: #d0d0d0; }
|
||||
#templateBtn:disabled { color: #aaaaaa; background: #f7f7f7; }
|
||||
#templateDeleteBtn {
|
||||
font-size: 12px;
|
||||
padding: 4px 8px;
|
||||
border: 1px solid #d6d6d6;
|
||||
border-radius: 3px;
|
||||
background: #f0f0f0;
|
||||
color: #c42b1c;
|
||||
}
|
||||
#templateDeleteBtn:hover { background: #fde7e9; border-color: #c42b1c; }
|
||||
#templateDeleteBtn:pressed { background: #f9d4d7; }
|
||||
""")
|
||||
|
||||
@@ -27,6 +27,7 @@ class TransformPanel(QWidget):
|
||||
reset_position = Signal()
|
||||
reset_size = Signal()
|
||||
reset_rotation = Signal()
|
||||
reset_all = Signal() # "位置尺寸旋转重置为当前模板" — reset all three at once
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
@@ -46,6 +47,7 @@ class TransformPanel(QWidget):
|
||||
col.addWidget(self._make_section("位置", self._make_pos_form(), "pos"))
|
||||
col.addWidget(self._make_section("尺寸", self._make_size_form(), "size"))
|
||||
col.addWidget(self._make_section("旋转", self._make_rot_form(), "rot"))
|
||||
col.addWidget(self._make_footer())
|
||||
|
||||
def _make_section(self, title: str, body: QWidget, reset_tag: str) -> QWidget:
|
||||
section = QWidget()
|
||||
@@ -82,18 +84,20 @@ class TransformPanel(QWidget):
|
||||
def _make_pos_form(self) -> QWidget:
|
||||
w = QWidget()
|
||||
w.setObjectName("transformForm")
|
||||
form = QFormLayout(w)
|
||||
form.setContentsMargins(10, 6, 10, 6)
|
||||
form.setSpacing(6)
|
||||
form.setLabelAlignment(Qt.AlignRight | Qt.AlignVCenter)
|
||||
row = QHBoxLayout(w)
|
||||
row.setContentsMargins(10, 6, 10, 6)
|
||||
row.setSpacing(6)
|
||||
|
||||
self._x_spin = self._make_spin(-99999, 99999, "px")
|
||||
self._y_spin = self._make_spin(-99999, 99999, "px")
|
||||
self._x_spin.valueChanged.connect(self._on_xy_changed)
|
||||
self._y_spin.valueChanged.connect(self._on_xy_changed)
|
||||
|
||||
form.addRow("X 坐标", self._x_spin)
|
||||
form.addRow("Y 坐标", self._y_spin)
|
||||
# X and Y are a coordinate pair → keep them on one row
|
||||
row.addWidget(self._make_field_label("X"))
|
||||
row.addWidget(self._x_spin, 1)
|
||||
row.addWidget(self._make_field_label("Y"))
|
||||
row.addWidget(self._y_spin, 1)
|
||||
return w
|
||||
|
||||
def _make_size_form(self) -> QWidget:
|
||||
@@ -103,18 +107,20 @@ class TransformPanel(QWidget):
|
||||
col.setContentsMargins(10, 6, 10, 6)
|
||||
col.setSpacing(6)
|
||||
|
||||
form = QFormLayout()
|
||||
form.setSpacing(6)
|
||||
form.setLabelAlignment(Qt.AlignRight | Qt.AlignVCenter)
|
||||
|
||||
self._w_spin = self._make_spin(_MIN_SIZE, 99999, "px")
|
||||
self._h_spin = self._make_spin(_MIN_SIZE, 99999, "px")
|
||||
self._w_spin.valueChanged.connect(self._on_width_changed)
|
||||
self._h_spin.valueChanged.connect(self._on_height_changed)
|
||||
|
||||
form.addRow("宽度", self._w_spin)
|
||||
form.addRow("高度", self._h_spin)
|
||||
col.addLayout(form)
|
||||
# Width and height are a pair → keep them on one row
|
||||
size_row = QHBoxLayout()
|
||||
size_row.setContentsMargins(0, 0, 0, 0)
|
||||
size_row.setSpacing(6)
|
||||
size_row.addWidget(self._make_field_label("宽"))
|
||||
size_row.addWidget(self._w_spin, 1)
|
||||
size_row.addWidget(self._make_field_label("高"))
|
||||
size_row.addWidget(self._h_spin, 1)
|
||||
col.addLayout(size_row)
|
||||
|
||||
self._lock_cb = QCheckBox("锁定宽高比例")
|
||||
self._lock_cb.setChecked(True)
|
||||
@@ -157,6 +163,21 @@ class TransformPanel(QWidget):
|
||||
col.addLayout(btn_row)
|
||||
return w
|
||||
|
||||
def _make_footer(self) -> QWidget:
|
||||
footer = QWidget()
|
||||
footer.setObjectName("transformFooter")
|
||||
col = QVBoxLayout(footer)
|
||||
col.setContentsMargins(10, 8, 10, 10)
|
||||
col.setSpacing(0)
|
||||
|
||||
self._reset_all_btn = QPushButton("位置尺寸旋转重置为当前模板")
|
||||
self._reset_all_btn.setObjectName("transformResetAllBtn")
|
||||
self._reset_all_btn.setToolTip("将位置、尺寸、旋转一并还原为当前选中模板")
|
||||
self._reset_all_btn.setEnabled(False)
|
||||
self._reset_all_btn.clicked.connect(lambda: self.reset_all.emit())
|
||||
col.addWidget(self._reset_all_btn)
|
||||
return footer
|
||||
|
||||
def _emit_reset(self, tag: str):
|
||||
if tag == "pos":
|
||||
self.reset_position.emit()
|
||||
@@ -166,9 +187,16 @@ class TransformPanel(QWidget):
|
||||
self.reset_rotation.emit()
|
||||
|
||||
def set_resets_enabled(self, enabled: bool):
|
||||
"""Enable/disable the three section reset buttons together."""
|
||||
"""Enable/disable the section resets and the全部重置 button together."""
|
||||
for btn in self._reset_btns.values():
|
||||
btn.setEnabled(enabled)
|
||||
self._reset_all_btn.setEnabled(enabled)
|
||||
|
||||
@staticmethod
|
||||
def _make_field_label(text: str) -> QLabel:
|
||||
lbl = QLabel(text)
|
||||
lbl.setObjectName("fieldLabel")
|
||||
return lbl
|
||||
|
||||
@staticmethod
|
||||
def _make_spin(minimum: float, maximum: float, suffix: str) -> QDoubleSpinBox:
|
||||
@@ -209,6 +237,11 @@ class TransformPanel(QWidget):
|
||||
#transformForm {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
#fieldLabel {
|
||||
font-family: "Microsoft YaHei", "Segoe UI", sans-serif;
|
||||
font-size: 12px;
|
||||
color: #555555;
|
||||
}
|
||||
#transformSpin {
|
||||
font-size: 12px;
|
||||
border: 1px solid #d6d6d6;
|
||||
@@ -227,6 +260,25 @@ class TransformPanel(QWidget):
|
||||
}
|
||||
#rotBtn:hover { background: #e0e0e0; }
|
||||
#rotBtn:pressed { background: #d0d0d0; }
|
||||
#transformFooter {
|
||||
background-color: #ffffff;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
#transformResetAllBtn {
|
||||
font-size: 12px;
|
||||
color: #555555;
|
||||
background: #f0f0f0;
|
||||
border: 1px solid #d6d6d6;
|
||||
border-radius: 4px;
|
||||
padding: 6px 0;
|
||||
}
|
||||
#transformResetAllBtn:hover {
|
||||
background: #eaf3fc;
|
||||
color: #0078d4;
|
||||
border-color: #0078d4;
|
||||
}
|
||||
#transformResetAllBtn:pressed { background: #d6e8f9; }
|
||||
#transformResetAllBtn:disabled { color: #c0c0c0; background: #f7f7f7; }
|
||||
""")
|
||||
|
||||
# ── public API ────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -608,6 +608,66 @@
|
||||
- [ ] 同一个动作在界面上只出现一次,命名一致
|
||||
- [ ] 导出面板仅保留输出设置与 `导出当前单张`
|
||||
|
||||
### 17.4 「重置为模板」归位并更名为「位置尺寸旋转重置为当前模板」
|
||||
|
||||
前置阅读:
|
||||
|
||||
- `docs/07-ui-design.md`(7 右侧参数栏、10 视觉规范)
|
||||
|
||||
任务:
|
||||
|
||||
- [ ] 把模板区域的 `重置为模板` 按钮移出,移到参数栏底部(旋转分区下方)
|
||||
- [ ] 更名为 `位置尺寸旋转重置为当前模板`,与三个分项 `↺ 重置` 构成「全部 + 分项」两级
|
||||
- [ ] 还原逻辑改为把位置/尺寸/旋转整体设为模板值,并与分项重置同走 `_apply_preview_transform`
|
||||
- [ ] 与三个分项重置一起受「未加载图片时置灰」控制
|
||||
- [ ] 采用安静、全宽、悬停出强调色的样式(不使用红色)
|
||||
|
||||
验收:
|
||||
|
||||
- [ ] 模板区域只剩 `保存` / `另存为`,不含整体重置
|
||||
- [ ] `位置尺寸旋转重置为当前模板` 在旋转分区下方,未加载图片时置灰
|
||||
- [ ] 点击后位置、尺寸、旋转一并还原为当前选中模板
|
||||
|
||||
### 17.5 模板删除按钮
|
||||
|
||||
前置阅读:
|
||||
|
||||
- `docs/07-ui-design.md`(7.1 模板区域)
|
||||
|
||||
任务:
|
||||
|
||||
- [ ] 在模板区域增加 `删除` 按钮(复用 `template_service.delete_template`)
|
||||
- [ ] `删除` 仅对自定义模板可用,选中内置模板时隐藏(与 `保存` 一致)
|
||||
- [ ] 删除前弹二次确认对话框;确认后才删除
|
||||
- [ ] 删除后刷新下拉框并回选第一个内置模板,同步持久化选择
|
||||
- [ ] `删除` 用红色文字提示危险但保持安静样式
|
||||
|
||||
验收:
|
||||
|
||||
- [ ] 可在界面上删除自定义模板,无需手改 `templates.json`
|
||||
- [ ] 内置模板不显示 `删除`
|
||||
- [ ] 删除有二次确认,取消则不删
|
||||
|
||||
### 17.6 参数/导出面板紧凑化与命名
|
||||
|
||||
前置阅读:
|
||||
|
||||
- `docs/07-ui-design.md`(7.2 位置区域、7.3 尺寸区域、7.5 输出设置区域)
|
||||
|
||||
任务:
|
||||
|
||||
- [ ] 「位置」的 X、Y 输入框放在同一行(标签简化为 `X` / `Y`)
|
||||
- [ ] 「尺寸」的宽、高输入框放在同一行(标签简化为 `宽` / `高`),`锁定宽高比例` 在其下方
|
||||
- [ ] 导出面板「格式」「质量」放在同一行
|
||||
- [ ] 底部整体重置按钮更名为 `位置尺寸旋转重置为当前模板`
|
||||
- [ ] 仅改布局/文案,不动同步与导出逻辑
|
||||
|
||||
验收:
|
||||
|
||||
- [ ] X/Y 一行、宽/高一行、格式/质量一行,界面更紧凑
|
||||
- [ ] 拖动/缩放预览后输入框仍正常同步
|
||||
- [ ] 质量仍仅在 JPG 时可用
|
||||
|
||||
## 18. 后续暂缓任务
|
||||
|
||||
以下任务第一阶段暂不做,后续需要时再新增设计文档:
|
||||
|
||||
Reference in New Issue
Block a user