feat: persist template/batch-mode, per-aspect resets, de-dup template actions
§17.1 Preferences persistence: - Wire the previously-unused config_service; remember and restore the last-selected template and last batch mode. main_window loads config, distributes initial values, then persists on change (save-on-change). - template_panel: template_changed signal, select_template, current_template_state. queue_panel: set_batch_mode + batch_mode_changed. §17.2 Independent resets: - Replace the combined reset and 归零 with per-section 位置/尺寸/旋转 reset buttons (quiet ghost styling) that restore only their own aspect to the selected template. Size reset changes width/height only, leaving X/Y. Button de-dup: - Remove the export panel's 应用为模板 / 重置为模板 — literal duplicates of the template panel's 另存为 / 重置为模板. Template actions now live only in the template area; the export panel handles output only. Docs: PRD (6.6/6.9/9/10), architecture (4.12), UI design (7.x/10), development-rules (6, single-home actions + consistent naming), tasks.md (17). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+78
-6
@@ -14,6 +14,8 @@ from PySide6.QtWidgets import (
|
||||
)
|
||||
|
||||
from version import APP_NAME, APP_VERSION
|
||||
from core.models import BatchMode, TransformState
|
||||
from services.config_service import load_config, save_config
|
||||
from app.widgets.export_panel import ExportPanel
|
||||
from app.widgets.image_canvas import ImageCanvas
|
||||
from app.widgets.image_list_panel import ImageListPanel
|
||||
@@ -66,6 +68,7 @@ class MainWindow(QMainWindow):
|
||||
self.setCentralWidget(root)
|
||||
self._apply_stylesheet()
|
||||
self._connect_signals()
|
||||
self._restore_preferences()
|
||||
|
||||
def _create_header(self):
|
||||
"""Top header: app name, version label, settings button."""
|
||||
@@ -270,8 +273,10 @@ class MainWindow(QMainWindow):
|
||||
self.image_canvas.transform_changed.connect(self._on_transform_changed)
|
||||
self.transform_panel.transform_changed.connect(self._on_transform_changed)
|
||||
|
||||
# Reset position/size/angle back to the selected template
|
||||
self.transform_panel.reset_to_template.connect(self.template_panel._reset_to_template)
|
||||
# Per-section reset: restore only that aspect to the selected template
|
||||
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"))
|
||||
|
||||
# ── Template panel ─────────────────────────────────────────────────
|
||||
# Template selection updates canvas, param panel, and queue template
|
||||
@@ -280,10 +285,6 @@ class MainWindow(QMainWindow):
|
||||
self.template_panel.template_applied.connect(self.queue_panel.set_transform)
|
||||
self.template_panel.template_applied.connect(self.export_panel.set_transform)
|
||||
|
||||
# ── Export panel secondary buttons → template panel ────────────────
|
||||
self.export_panel.apply_as_template.connect(self.template_panel._save_as_template)
|
||||
self.export_panel.reset_to_template.connect(self.template_panel._reset_to_template)
|
||||
|
||||
def _load_print_preview(self, path):
|
||||
"""Load a print into the canvas and sync its native size to the template panel."""
|
||||
self.image_canvas.load_print(path)
|
||||
@@ -300,6 +301,7 @@ class MainWindow(QMainWindow):
|
||||
self._active_queue_item = None # leave queue mode
|
||||
if not asset:
|
||||
self.export_panel.set_garment(None)
|
||||
self._update_reset_availability()
|
||||
return
|
||||
path = str(asset.path)
|
||||
self.image_canvas.load_garment(path)
|
||||
@@ -316,6 +318,7 @@ class MainWindow(QMainWindow):
|
||||
if self._current_print_path:
|
||||
self._load_print_preview(self._current_print_path)
|
||||
self.template_panel.apply_current()
|
||||
self._update_reset_availability()
|
||||
|
||||
def _on_print_preview(self, asset):
|
||||
"""Load a print from the asset list into the canvas for direct preview."""
|
||||
@@ -323,12 +326,14 @@ class MainWindow(QMainWindow):
|
||||
if not asset:
|
||||
self._current_print_path = None
|
||||
self.export_panel.set_print(None)
|
||||
self._update_reset_availability()
|
||||
return
|
||||
self._current_print_path = str(asset.path)
|
||||
self._load_print_preview(self._current_print_path)
|
||||
# Apply the selected template so the print lands per the chosen layout,
|
||||
# not the canvas's hard-coded centred default.
|
||||
self.template_panel.apply_current()
|
||||
self._update_reset_availability()
|
||||
|
||||
def _on_queue_item_activated(self, item):
|
||||
"""Load a queue item into the canvas and arm fine-tune tracking."""
|
||||
@@ -356,6 +361,7 @@ class MainWindow(QMainWindow):
|
||||
pass
|
||||
finally:
|
||||
self._loading_queue_item = False
|
||||
self._update_reset_availability()
|
||||
|
||||
def _on_transform_changed(self, state):
|
||||
"""Route transform changes to panels and mark the active queue item fine-tuned."""
|
||||
@@ -367,6 +373,72 @@ class MainWindow(QMainWindow):
|
||||
return
|
||||
self.queue_panel.mark_item_fine_tuned(self._active_queue_item, state)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Per-aspect reset (position / size / rotation → selected template)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _update_reset_availability(self):
|
||||
"""Enable the section resets only when a print is on the canvas."""
|
||||
has_print = self.image_canvas.get_transform() is not None
|
||||
self.transform_panel.set_resets_enabled(has_print)
|
||||
|
||||
def _reset_aspect(self, aspect):
|
||||
"""Restore only one aspect of the current transform to the selected template."""
|
||||
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=cur.x, y=cur.y, width=cur.width, height=cur.height,
|
||||
rotation=cur.rotation, keep_aspect_ratio=cur.keep_aspect_ratio,
|
||||
)
|
||||
if aspect == "pos":
|
||||
# Centre the current-sized print on the template's centre
|
||||
new.x = (tpl.x + tpl.width / 2.0) - cur.width / 2.0
|
||||
new.y = (tpl.y + tpl.height / 2.0) - cur.height / 2.0
|
||||
elif aspect == "size":
|
||||
# Only the size; leave position (x/y) untouched so each reset is independent
|
||||
new.width, new.height = tpl.width, tpl.height
|
||||
elif aspect == "rot":
|
||||
new.rotation = tpl.rotation
|
||||
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)
|
||||
self.transform_panel.set_transform(state)
|
||||
self._on_transform_changed(state)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Preference persistence (last template / last batch mode)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _restore_preferences(self):
|
||||
"""Load saved preferences, apply them, then start persisting changes."""
|
||||
self._config = load_config()
|
||||
|
||||
name = self._config.get("last_template", "")
|
||||
if name:
|
||||
self.template_panel.select_template(name)
|
||||
|
||||
raw_mode = self._config.get("last_batch_mode", "")
|
||||
try:
|
||||
self.queue_panel.set_batch_mode(BatchMode(raw_mode))
|
||||
except ValueError:
|
||||
logger.warning("Unknown last_batch_mode %r, keeping default", raw_mode)
|
||||
|
||||
# Persist future changes (connected after restore to avoid echo writes)
|
||||
self.template_panel.template_changed.connect(self._on_template_persist)
|
||||
self.queue_panel.batch_mode_changed.connect(self._on_batch_mode_persist)
|
||||
|
||||
def _on_template_persist(self, name):
|
||||
self._config["last_template"] = name
|
||||
save_config(self._config)
|
||||
|
||||
def _on_batch_mode_persist(self, mode):
|
||||
self._config["last_batch_mode"] = getattr(mode, "value", str(mode))
|
||||
save_config(self._config)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Style (ref: docs/07-ui-design.md §10)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
@@ -2,7 +2,7 @@ import logging
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from PySide6.QtCore import Qt, Signal
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtWidgets import (
|
||||
QComboBox,
|
||||
QFileDialog,
|
||||
@@ -24,10 +24,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ExportPanel(QWidget):
|
||||
"""Right-panel section: output settings and export / template actions."""
|
||||
|
||||
apply_as_template = Signal() # "应用为模板" clicked
|
||||
reset_to_template = Signal() # "重置为模板" clicked
|
||||
"""Right-panel section: output settings and single-image export."""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
@@ -58,7 +55,6 @@ class ExportPanel(QWidget):
|
||||
body_col.addLayout(self._make_format_form())
|
||||
body_col.addWidget(self._make_hint_label())
|
||||
body_col.addWidget(self._make_export_btn())
|
||||
body_col.addLayout(self._make_secondary_btns())
|
||||
|
||||
col.addWidget(body)
|
||||
|
||||
@@ -116,24 +112,6 @@ class ExportPanel(QWidget):
|
||||
self._export_btn.clicked.connect(self._do_export)
|
||||
return self._export_btn
|
||||
|
||||
def _make_secondary_btns(self) -> QHBoxLayout:
|
||||
row = QHBoxLayout()
|
||||
row.setSpacing(6)
|
||||
|
||||
apply_btn = QPushButton("应用为模板")
|
||||
apply_btn.setObjectName("exportSecBtn")
|
||||
apply_btn.setToolTip("将当前印花参数另存为新模板")
|
||||
apply_btn.clicked.connect(lambda: self.apply_as_template.emit())
|
||||
|
||||
reset_btn = QPushButton("重置为模板")
|
||||
reset_btn.setObjectName("exportSecBtn")
|
||||
reset_btn.setToolTip("将印花参数还原为所选模板的默认值")
|
||||
reset_btn.clicked.connect(lambda: self.reset_to_template.emit())
|
||||
|
||||
row.addWidget(apply_btn)
|
||||
row.addWidget(reset_btn)
|
||||
return row
|
||||
|
||||
# ── public API ───────────────────────────────────────────────────────────
|
||||
|
||||
def set_garment(self, path: Optional[str]):
|
||||
@@ -284,13 +262,4 @@ class ExportPanel(QWidget):
|
||||
background-color: #c8c8c8;
|
||||
color: #888888;
|
||||
}
|
||||
#exportSecBtn {
|
||||
font-size: 12px;
|
||||
padding: 4px 8px;
|
||||
border: 1px solid #d6d6d6;
|
||||
border-radius: 3px;
|
||||
background: #f0f0f0;
|
||||
}
|
||||
#exportSecBtn:hover { background: #e0e0e0; }
|
||||
#exportSecBtn:pressed { background: #d0d0d0; }
|
||||
""")
|
||||
|
||||
@@ -171,7 +171,8 @@ class _ProgressDelegate(QStyledItemDelegate):
|
||||
class QueuePanel(QWidget):
|
||||
"""Bottom queue: batch mode selector, stats, action buttons, item table."""
|
||||
|
||||
item_activated = Signal(object) # QueueItem when a row is selected
|
||||
item_activated = Signal(object) # QueueItem when a row is selected
|
||||
batch_mode_changed = Signal(object) # BatchMode when the mode selector changes
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
@@ -208,7 +209,7 @@ class QueuePanel(QWidget):
|
||||
self._mode_combo.addItem("多衣服 × 单印花", BatchMode.MANY_GARMENTS)
|
||||
self._mode_combo.addItem("单衣服 × 多印花", BatchMode.MANY_PRINTS)
|
||||
self._mode_combo.addItem("一一匹配", BatchMode.ONE_TO_ONE)
|
||||
self._mode_combo.currentIndexChanged.connect(self._rebuild_queue)
|
||||
self._mode_combo.currentIndexChanged.connect(self._on_mode_changed)
|
||||
|
||||
self._stats_lbl = QLabel("共 0 项")
|
||||
self._stats_lbl.setObjectName("queueStatsLabel")
|
||||
@@ -279,6 +280,16 @@ class QueuePanel(QWidget):
|
||||
def set_export_options(self, options: ExportOptions):
|
||||
self._export_options = options
|
||||
|
||||
def set_batch_mode(self, mode: BatchMode):
|
||||
"""Restore the batch mode selector without re-triggering persistence."""
|
||||
idx = self._mode_combo.findData(mode)
|
||||
if idx < 0:
|
||||
return
|
||||
self._mode_combo.blockSignals(True)
|
||||
self._mode_combo.setCurrentIndex(idx)
|
||||
self._mode_combo.blockSignals(False)
|
||||
self._rebuild_queue()
|
||||
|
||||
def mark_item_fine_tuned(self, item: "QueueItem", state: TransformState):
|
||||
"""Save a per-item transform and mark it as fine-tuned in the table."""
|
||||
try:
|
||||
@@ -291,6 +302,10 @@ class QueuePanel(QWidget):
|
||||
|
||||
# ── internal slots ───────────────────────────────────────────────────────
|
||||
|
||||
def _on_mode_changed(self):
|
||||
self._rebuild_queue()
|
||||
self.batch_mode_changed.emit(self._mode_combo.currentData())
|
||||
|
||||
def _rebuild_queue(self):
|
||||
mode = self._mode_combo.currentData()
|
||||
sel_g = [g for g in self._garments if g.selected]
|
||||
|
||||
@@ -26,6 +26,7 @@ class TemplatePanel(QWidget):
|
||||
"""Right-panel section: template picker, save, save-as, reset."""
|
||||
|
||||
template_applied = Signal(object) # emits TransformState
|
||||
template_changed = Signal(str) # emits the selected template name (for persistence)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
@@ -151,6 +152,18 @@ class TemplatePanel(QWidget):
|
||||
self._current_state = state
|
||||
self.template_applied.emit(state)
|
||||
|
||||
def select_template(self, name: str):
|
||||
"""Select a template by name (used to restore the last-used template)."""
|
||||
if name:
|
||||
self._load_templates(select_name=name)
|
||||
|
||||
def current_template_state(self) -> Optional[TransformState]:
|
||||
"""Return the current template's pixel TransformState, or None if not computable."""
|
||||
t = self._current_template()
|
||||
if t and self._garment_w > 0 and self._garment_h > 0:
|
||||
return self._state_for_template(t)
|
||||
return None
|
||||
|
||||
def set_transform(self, state: TransformState):
|
||||
"""Store current print transform (used when saving a template)."""
|
||||
self._current_state = state
|
||||
@@ -162,6 +175,8 @@ class TemplatePanel(QWidget):
|
||||
return
|
||||
self._update_button_states()
|
||||
t = self._combo.itemData(index)
|
||||
if t:
|
||||
self.template_changed.emit(t.name) # persist selection
|
||||
if t and self._garment_w > 0 and self._garment_h > 0:
|
||||
state = self._state_for_template(t)
|
||||
logger.debug("Template applied: %s", t.name)
|
||||
@@ -196,6 +211,7 @@ class TemplatePanel(QWidget):
|
||||
add_template(self._state_to_template(t.name))
|
||||
logger.info("Template saved: %s", t.name)
|
||||
self._load_templates(select_name=t.name)
|
||||
self.template_changed.emit(t.name)
|
||||
|
||||
def _save_as_template(self):
|
||||
"""Create a new custom template from the current transform."""
|
||||
@@ -215,6 +231,7 @@ class TemplatePanel(QWidget):
|
||||
add_template(self._state_to_template(name))
|
||||
logger.info("Template saved-as: %s", name)
|
||||
self._load_templates(select_name=name)
|
||||
self.template_changed.emit(name)
|
||||
|
||||
# ── helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -23,12 +23,16 @@ class TransformPanel(QWidget):
|
||||
"""Right-panel section: position, size, aspect-ratio lock, rotation."""
|
||||
|
||||
transform_changed = Signal(object) # emits TransformState
|
||||
reset_to_template = Signal() # "重置位置/尺寸/角度" clicked
|
||||
# Per-section reset to the selected template's value for that aspect only
|
||||
reset_position = Signal()
|
||||
reset_size = Signal()
|
||||
reset_rotation = Signal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._updating = False # guard against signal loops
|
||||
self._aspect_ratio = 1.0
|
||||
self._reset_btns = {} # tag -> reset QPushButton (for enable/disable)
|
||||
self._setup_ui()
|
||||
self._apply_stylesheet()
|
||||
|
||||
@@ -39,20 +43,38 @@ class TransformPanel(QWidget):
|
||||
col.setContentsMargins(0, 0, 0, 0)
|
||||
col.setSpacing(0)
|
||||
|
||||
col.addWidget(self._make_section("位置", self._make_pos_form()))
|
||||
col.addWidget(self._make_section("尺寸", self._make_size_form()))
|
||||
col.addWidget(self._make_section("旋转", self._make_rot_form()))
|
||||
col.addWidget(self._make_footer())
|
||||
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"))
|
||||
|
||||
def _make_section(self, title: str, body: QWidget) -> QWidget:
|
||||
def _make_section(self, title: str, body: QWidget, reset_tag: str) -> QWidget:
|
||||
section = QWidget()
|
||||
section.setObjectName("transformSection")
|
||||
col = QVBoxLayout(section)
|
||||
col.setContentsMargins(0, 0, 0, 0)
|
||||
col.setSpacing(0)
|
||||
|
||||
header = QLabel(title)
|
||||
# Header row: title (left) + ghost reset button (right)
|
||||
header = QWidget()
|
||||
header.setObjectName("transformSectionHeader")
|
||||
hrow = QHBoxLayout(header)
|
||||
hrow.setContentsMargins(10, 2, 4, 2)
|
||||
hrow.setSpacing(0)
|
||||
|
||||
title_lbl = QLabel(title)
|
||||
title_lbl.setObjectName("transformSectionTitle")
|
||||
|
||||
reset_btn = QPushButton("↺ 重置")
|
||||
reset_btn.setObjectName("sectionResetBtn")
|
||||
reset_btn.setToolTip("将本组参数还原为当前选中模板的值")
|
||||
reset_btn.setEnabled(False) # enabled once a template + print are available
|
||||
reset_btn.clicked.connect(lambda: self._emit_reset(reset_tag))
|
||||
self._reset_btns[reset_tag] = reset_btn
|
||||
|
||||
hrow.addWidget(title_lbl)
|
||||
hrow.addStretch()
|
||||
hrow.addWidget(reset_btn)
|
||||
|
||||
col.addWidget(header)
|
||||
col.addWidget(body)
|
||||
return section
|
||||
@@ -116,20 +138,7 @@ class TransformPanel(QWidget):
|
||||
self._rot_spin.setSingleStep(1.0)
|
||||
self._rot_spin.valueChanged.connect(self._on_rot_changed)
|
||||
|
||||
# Angle field: spinbox + inline "归零" (reset angle to 0)
|
||||
rot_field = QWidget()
|
||||
rot_row = QHBoxLayout(rot_field)
|
||||
rot_row.setContentsMargins(0, 0, 0, 0)
|
||||
rot_row.setSpacing(4)
|
||||
self._rot_zero_btn = QPushButton("归零")
|
||||
self._rot_zero_btn.setObjectName("rotZeroBtn")
|
||||
self._rot_zero_btn.setFixedWidth(46)
|
||||
self._rot_zero_btn.setToolTip("将角度归零(拉直印花)")
|
||||
self._rot_zero_btn.clicked.connect(self._reset_rotation)
|
||||
rot_row.addWidget(self._rot_spin, 1)
|
||||
rot_row.addWidget(self._rot_zero_btn)
|
||||
|
||||
form.addRow("角度", rot_field)
|
||||
form.addRow("角度", self._rot_spin)
|
||||
col.addLayout(form)
|
||||
|
||||
btn_row = QHBoxLayout()
|
||||
@@ -148,19 +157,18 @@ 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)
|
||||
def _emit_reset(self, tag: str):
|
||||
if tag == "pos":
|
||||
self.reset_position.emit()
|
||||
elif tag == "size":
|
||||
self.reset_size.emit()
|
||||
elif tag == "rot":
|
||||
self.reset_rotation.emit()
|
||||
|
||||
self._reset_btn = QPushButton("重置位置 / 尺寸 / 角度")
|
||||
self._reset_btn.setObjectName("transformResetBtn")
|
||||
self._reset_btn.setToolTip("将印花的位置、尺寸、角度还原为所选模板")
|
||||
self._reset_btn.clicked.connect(lambda: self.reset_to_template.emit())
|
||||
col.addWidget(self._reset_btn)
|
||||
return footer
|
||||
def set_resets_enabled(self, enabled: bool):
|
||||
"""Enable/disable the three section reset buttons together."""
|
||||
for btn in self._reset_btns.values():
|
||||
btn.setEnabled(enabled)
|
||||
|
||||
@staticmethod
|
||||
def _make_spin(minimum: float, maximum: float, suffix: str) -> QDoubleSpinBox:
|
||||
@@ -178,14 +186,26 @@ class TransformPanel(QWidget):
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
#transformSectionHeader {
|
||||
background-color: #f7f7f7;
|
||||
border-bottom: 1px solid #eeeeee;
|
||||
}
|
||||
#transformSectionTitle {
|
||||
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;
|
||||
}
|
||||
#sectionResetBtn {
|
||||
font-size: 11px;
|
||||
color: #8a8a8a;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
padding: 2px 6px;
|
||||
}
|
||||
#sectionResetBtn:hover { background: #eaf3fc; color: #0078d4; }
|
||||
#sectionResetBtn:pressed { background: #d6e8f9; }
|
||||
#sectionResetBtn:disabled { color: #c0c0c0; background: transparent; }
|
||||
#transformForm {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
@@ -207,31 +227,6 @@ class TransformPanel(QWidget):
|
||||
}
|
||||
#rotBtn:hover { background: #e0e0e0; }
|
||||
#rotBtn:pressed { background: #d0d0d0; }
|
||||
#rotZeroBtn {
|
||||
font-size: 12px;
|
||||
padding: 2px 4px;
|
||||
border: 1px solid #d6d6d6;
|
||||
border-radius: 3px;
|
||||
background: #f0f0f0;
|
||||
color: #444444;
|
||||
}
|
||||
#rotZeroBtn:hover { background: #e0e0e0; }
|
||||
#rotZeroBtn:pressed { background: #d0d0d0; }
|
||||
#transformFooter {
|
||||
background-color: #ffffff;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
#transformResetBtn {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
color: #0078d4;
|
||||
background: #ffffff;
|
||||
border: 1px solid #0078d4;
|
||||
border-radius: 4px;
|
||||
padding: 6px 0;
|
||||
}
|
||||
#transformResetBtn:hover { background: #eaf3fc; }
|
||||
#transformResetBtn:pressed { background: #d6e8f9; }
|
||||
""")
|
||||
|
||||
# ── public API ────────────────────────────────────────────────────────────
|
||||
@@ -318,9 +313,6 @@ class TransformPanel(QWidget):
|
||||
self._aspect_ratio = w / h
|
||||
self._emit()
|
||||
|
||||
def _reset_rotation(self):
|
||||
self._rot_spin.setValue(0.0)
|
||||
|
||||
def _rotate_left(self):
|
||||
self._rot_spin.setValue(self._rot_spin.value() - 90.0)
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ DEFAULT_CONFIG = {
|
||||
"output_dir": "", # empty = use app output/ dir
|
||||
"last_garment_dir": "",
|
||||
"last_print_dir": "",
|
||||
"last_template": "", # name of the last-selected template
|
||||
"last_batch_mode": "full_combo", # BatchMode value of the last-used mode
|
||||
}
|
||||
|
||||
_CONFIG_FILENAME = "app_config.json"
|
||||
|
||||
Reference in New Issue
Block a user