# -*- coding: utf-8 -*- """会话模式弹窗 · 可视化调参工具 运行: C:\\Users\\14890\\miniconda3\\envs\\haocode\\python.exe tests\\tune_mode_popup.py - 左侧:调参窗(拖动/修改参数 → 右侧弹窗实时重渲染) - 右侧:真实的 SessionModePopup 本体(独立窗口,点调参窗不会消失) - 满意后点「确定」→ 参数 JSON 写入 data/mode_popup_tune.json → 自动退出 """ import os import sys import json import traceback sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) os.environ["QTWEBENGINE_CHROMIUM_FLAGS"] = "--disable-gpu" os.environ.setdefault("HAOCODE_RENDER", "software") from PyQt6.QtWidgets import ( # noqa: E402 QApplication, QWidget, QFormLayout, QHBoxLayout, QSpinBox, QPushButton, QCheckBox, QMessageBox, ) from PyQt6.QtCore import Qt # noqa: E402 from PyQt6.QtGui import QPixmap # noqa: E402 from ui.views.main_window import SessionModePopup, _ModeRow, _popup_svg_path # noqa: E402 DEFAULTS = { "icon_size": 14, # 图标尺寸 "name_font": 12, # 名称字号 "desc_font": 12, # 描述字号 "row_margin_v": 8, # 行上下内边距 "row_margin_h": 10, # 行左右内边距 "icon_text_gap": 12, # 图标-文字间距 "name_desc_gap": 4, # 名称/描述行间距 "row_radius": 8, # 行圆角 "popup_width": 320, # 弹窗宽度 "container_margin": 4, # 容器边距 } RANGES = { "icon_size": (8, 48), "name_font": (10, 20), "desc_font": (9, 18), "row_margin_v": (2, 20), "row_margin_h": (4, 24), "icon_text_gap": (4, 24), "name_desc_gap": (0, 8), "row_radius": (0, 16), "popup_width": (240, 420), "container_margin": (0, 16), } CN = { "icon_size": "图标尺寸 (px)", "name_font": "名称字号 (px)", "desc_font": "描述字号 (px)", "row_margin_v": "行上下内边距 (px)", "row_margin_h": "行左右内边距 (px)", "icon_text_gap": "图标-文字间距 (px)", "name_desc_gap": "名称/描述行距 (px)", "row_radius": "行圆角 (px)", "popup_width": "弹窗宽度 (px)", "container_margin": "容器边距 (px)", } FONT_STACK = '"HarmonyOS Sans SC", "Microsoft YaHei UI", "Microsoft YaHei", "微软雅黑", sans-serif' class Tuner: def __init__(self): self.app = QApplication(sys.argv) self.params = dict(DEFAULTS) # ---- 弹窗本体(真实类):去掉 Popup 标志 → 独立普通无边框窗,不随失焦消失 ---- self.popup = SessionModePopup(None) self.popup.setWindowFlags(Qt.WindowType.Window | Qt.WindowType.FramelessWindowHint) self.rows = self.popup.container.findChildren(_ModeRow) icon_by_mode = {m: _popup_svg_path(ic) for m, _, _, ic in SessionModePopup.MODES} for r in self.rows: r._tune_pix = QPixmap(icon_by_mode[r.mode]) # 原始分辨率,重缩不糊 screen = self.app.primaryScreen().availableGeometry() self.px = screen.center().x() - 300 self.py = screen.center().y() - 200 self.popup.move(self.px, self.py) self.popup.show() # ---- 调参窗 ---- self.win = QWidget() self.win.setWindowTitle("会话模式弹窗调参 —— 调好后点「确定」") form = QFormLayout(self.win) form.setSpacing(8) self.spins = {} for key, (lo, hi) in RANGES.items(): sp = QSpinBox() sp.setRange(lo, hi) sp.setValue(self.params[key]) sp.valueChanged.connect(lambda v, k=key: self.on_change(k, v)) self.spins[key] = sp form.addRow(CN[key], sp) self.chk_selected = QCheckBox("显示选中态(预览)") self.chk_selected.toggled.connect(lambda _: self.apply_all()) form.addRow(self.chk_selected) btns = QHBoxLayout() b_reset = QPushButton("重置默认") b_reset.clicked.connect(self.reset) b_ok = QPushButton("✅ 确定(写入日志并退出)") b_ok.setStyleSheet("font-weight: bold;") b_ok.clicked.connect(self.finish) btns.addWidget(b_reset) btns.addWidget(b_ok) form.addRow(btns) self.win.resize(360, self.win.sizeHint().height()) self.win.move(self.px - 380, self.py) self.win.show() self.apply_all() # ---------- 实时渲染 ---------- def on_change(self, k, v): self.params[k] = v self.apply_all() def apply_all(self): p = self.params popup = self.popup popup.setFixedWidth(p["popup_width"]) cl = popup.container.layout() cl.setContentsMargins(p["container_margin"], p["container_margin"], p["container_margin"], p["container_margin"]) sel = self.chk_selected.isChecked() for r in self.rows: r.lbl_icon.setFixedSize(p["icon_size"], p["icon_size"]) r.lbl_icon.setPixmap(r._tune_pix.scaled( p["icon_size"], p["icon_size"], Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation)) lay = r.layout() lay.setContentsMargins(p["row_margin_h"], p["row_margin_v"], p["row_margin_h"] + 2, p["row_margin_v"]) lay.setSpacing(p["icon_text_gap"]) txt = lay.itemAt(1).layout() if txt is not None: txt.setSpacing(p["name_desc_gap"]) name_color = "#1a73e8" if sel else "#333333" r.lbl_name.setStyleSheet( f"font-family: {FONT_STACK}; font-size: {p['name_font']}px;" f"font-weight: bold; color: {name_color}; background: transparent;") r.lbl_desc.setStyleSheet( f"font-family: {FONT_STACK}; font-size: {p['desc_font']}px;" f"color: #888888; background: transparent;") bg = "#e8f0fe" if sel else "transparent" r.setStyleSheet(f"background-color: {bg}; border-radius: {p['row_radius']}px;") self.app.processEvents() popup.adjust_popup_height() popup.adjustSize() def reset(self): self.params = dict(DEFAULTS) for k, sp in self.spins.items(): sp.blockSignals(True) sp.setValue(self.params[k]) sp.blockSignals(False) self.apply_all() # ---------- 确定 → 写日志 ---------- def finish(self): log = os.path.join(os.path.dirname(__file__), "..", "data", "mode_popup_tune.json") os.makedirs(os.path.dirname(log), exist_ok=True) with open(log, "w", encoding="utf-8") as f: json.dump(self.params, f, ensure_ascii=False, indent=2) print("TUNE_LOG=" + os.path.abspath(log), flush=True) print(json.dumps(self.params, ensure_ascii=False), flush=True) self.app.quit() def main(): try: Tuner() QApplication.instance().exec() except Exception: err = traceback.format_exc() try: with open(os.path.join(os.path.dirname(__file__), "..", "data", "tune_error.log"), "w", encoding="utf-8") as f: f.write(err) except OSError: pass QMessageBox.critical(None, "调参工具启动失败", err) sys.exit(1) if __name__ == "__main__": main()