2026-04-20 11:48:38 +08:00
|
|
|
|
"""面板管理器(Step 6 重构)。
|
|
|
|
|
|
|
|
|
|
|
|
register_panel / show_panel / hide_all_panels —— 在右侧栏不同浮动面板间切换。
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2026-06-22 10:31:19 +08:00
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
import tkinter as tk
|
2026-06-22 10:31:19 +08:00
|
|
|
|
import tkinter.font as tkfont
|
2026-04-20 11:48:38 +08:00
|
|
|
|
|
2026-06-22 10:31:19 +08:00
|
|
|
|
from typing import TYPE_CHECKING, Any, Callable
|
2026-05-27 11:26:28 +08:00
|
|
|
|
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
|
|
from pqAutomationApp import PQAutomationApp
|
|
|
|
|
|
|
2026-06-22 10:40:39 +08:00
|
|
|
|
# 叠层切换:lift/lower,避免 pack_forget 触发 Matplotlib 等大面板重绘。
|
|
|
|
|
|
_STACK_PANELS = frozenset(
|
|
|
|
|
|
{"log", "ai_image", "pantone_baseline", "gamma_pattern", "calman", "single_step"}
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-05-27 11:26:28 +08:00
|
|
|
|
|
2026-06-22 10:31:19 +08:00
|
|
|
|
def create_tool_panel_sidebar_item(
|
|
|
|
|
|
self: "PQAutomationApp", parent: tk.Misc, label: str, command: Callable[[], None]
|
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
|
"""创建工具面板侧栏项:名称左对齐,选中时 × 贴右端,整体可点击。"""
|
|
|
|
|
|
from app.views.modern_styles import _contrast_text, _is_dark, get_theme_palette
|
|
|
|
|
|
|
|
|
|
|
|
row = tk.Frame(parent, bd=0, highlightthickness=0, cursor="hand2")
|
|
|
|
|
|
name_lbl = tk.Label(row, text=label, anchor=tk.W, bd=0, padx=18, pady=9)
|
|
|
|
|
|
close_lbl = tk.Label(
|
|
|
|
|
|
row,
|
|
|
|
|
|
text="×",
|
|
|
|
|
|
anchor=tk.CENTER,
|
|
|
|
|
|
bd=0,
|
|
|
|
|
|
padx=0,
|
|
|
|
|
|
pady=9,
|
|
|
|
|
|
width=2,
|
|
|
|
|
|
font=("Segoe UI", 9),
|
|
|
|
|
|
)
|
|
|
|
|
|
name_lbl.pack(side=tk.LEFT, fill=tk.X, expand=True)
|
|
|
|
|
|
|
|
|
|
|
|
item: dict[str, Any] = {
|
|
|
|
|
|
"row": row,
|
|
|
|
|
|
"name_label": name_lbl,
|
|
|
|
|
|
"close_label": close_lbl,
|
|
|
|
|
|
"label": label,
|
|
|
|
|
|
"selected": False,
|
|
|
|
|
|
"hover": False,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def _apply_style() -> None:
|
|
|
|
|
|
palette = get_theme_palette()
|
|
|
|
|
|
sidebar_bg = palette["sidebar_bg"]
|
|
|
|
|
|
sidebar_hover = palette["sidebar_hover"]
|
|
|
|
|
|
sidebar_selected = palette["sidebar_selected"]
|
|
|
|
|
|
sidebar_fg = palette["sidebar_fg"]
|
|
|
|
|
|
selected_fg = _contrast_text(
|
|
|
|
|
|
sidebar_selected,
|
|
|
|
|
|
dark_text=palette["badge_fg"],
|
|
|
|
|
|
light_text=sidebar_fg,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if item["selected"]:
|
|
|
|
|
|
bg = sidebar_selected
|
|
|
|
|
|
fg = selected_fg
|
|
|
|
|
|
name_font = ("Segoe UI Semibold", 10)
|
|
|
|
|
|
elif item["hover"]:
|
|
|
|
|
|
bg = sidebar_hover
|
|
|
|
|
|
fg = "#ffffff" if _is_dark(sidebar_hover) else sidebar_fg
|
|
|
|
|
|
name_font = ("Segoe UI", 10)
|
|
|
|
|
|
else:
|
|
|
|
|
|
bg = sidebar_bg
|
|
|
|
|
|
fg = sidebar_fg
|
|
|
|
|
|
name_font = ("Segoe UI", 10)
|
|
|
|
|
|
|
|
|
|
|
|
row.configure(bg=bg)
|
|
|
|
|
|
for widget in (name_lbl, close_lbl):
|
|
|
|
|
|
widget.configure(
|
|
|
|
|
|
bg=bg,
|
|
|
|
|
|
fg=fg,
|
|
|
|
|
|
activebackground=bg,
|
|
|
|
|
|
activeforeground=fg,
|
|
|
|
|
|
)
|
|
|
|
|
|
name_lbl.configure(font=name_font)
|
|
|
|
|
|
|
|
|
|
|
|
def _truncate_name() -> None:
|
|
|
|
|
|
full = item["label"]
|
|
|
|
|
|
if not item["selected"]:
|
|
|
|
|
|
name_lbl.configure(text=full)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
row.update_idletasks()
|
|
|
|
|
|
close_w = close_lbl.winfo_width() if close_lbl.winfo_ismapped() else 20
|
|
|
|
|
|
avail = max(24, row.winfo_width() - close_w - 18 - 14)
|
|
|
|
|
|
font = tkfont.Font(font=name_lbl.cget("font"))
|
|
|
|
|
|
if font.measure(full) <= avail:
|
|
|
|
|
|
name_lbl.configure(text=full)
|
|
|
|
|
|
return
|
|
|
|
|
|
for i in range(len(full), 0, -1):
|
|
|
|
|
|
candidate = full[:i].rstrip() + "…"
|
|
|
|
|
|
if font.measure(candidate) <= avail:
|
|
|
|
|
|
name_lbl.configure(text=candidate)
|
|
|
|
|
|
return
|
|
|
|
|
|
name_lbl.configure(text="…")
|
|
|
|
|
|
|
|
|
|
|
|
def set_selected(selected: bool) -> None:
|
|
|
|
|
|
item["selected"] = selected
|
|
|
|
|
|
if selected:
|
|
|
|
|
|
close_lbl.pack(side=tk.RIGHT, padx=(0, 14))
|
|
|
|
|
|
else:
|
|
|
|
|
|
close_lbl.pack_forget()
|
|
|
|
|
|
_apply_style()
|
|
|
|
|
|
row.after_idle(_truncate_name)
|
|
|
|
|
|
|
|
|
|
|
|
def _on_click(_event=None) -> None:
|
|
|
|
|
|
command()
|
|
|
|
|
|
|
|
|
|
|
|
def _on_enter(_event=None) -> None:
|
|
|
|
|
|
if not item["selected"]:
|
|
|
|
|
|
item["hover"] = True
|
|
|
|
|
|
_apply_style()
|
|
|
|
|
|
|
|
|
|
|
|
def _on_leave(_event=None) -> None:
|
|
|
|
|
|
px, py = row.winfo_pointerxy()
|
|
|
|
|
|
rx, ry = row.winfo_rootx(), row.winfo_rooty()
|
|
|
|
|
|
rw, rh = row.winfo_width(), row.winfo_height()
|
|
|
|
|
|
if rx <= px < rx + rw and ry <= py < ry + rh:
|
|
|
|
|
|
return
|
|
|
|
|
|
item["hover"] = False
|
|
|
|
|
|
_apply_style()
|
|
|
|
|
|
|
|
|
|
|
|
def _on_configure(_event=None) -> None:
|
|
|
|
|
|
if item["selected"]:
|
|
|
|
|
|
_truncate_name()
|
|
|
|
|
|
|
|
|
|
|
|
for widget in (row, name_lbl, close_lbl):
|
|
|
|
|
|
widget.bind("<Button-1>", _on_click)
|
|
|
|
|
|
widget.bind("<Enter>", _on_enter)
|
|
|
|
|
|
widget.bind("<Leave>", _on_leave)
|
|
|
|
|
|
row.bind("<Configure>", _on_configure)
|
|
|
|
|
|
|
|
|
|
|
|
item["set_selected"] = set_selected
|
|
|
|
|
|
item["apply_theme"] = _apply_style
|
|
|
|
|
|
item["pack"] = lambda **kw: row.pack(fill=tk.X, padx=0, pady=1, **kw)
|
|
|
|
|
|
|
|
|
|
|
|
_apply_style()
|
|
|
|
|
|
return item
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _set_tool_panel_sidebar_selected(
|
|
|
|
|
|
panel_info: dict[str, Any], selected: bool
|
|
|
|
|
|
) -> None:
|
|
|
|
|
|
sidebar_item = panel_info.get("sidebar_item")
|
|
|
|
|
|
if sidebar_item is not None:
|
|
|
|
|
|
sidebar_item["set_selected"](selected)
|
|
|
|
|
|
return
|
|
|
|
|
|
button = panel_info.get("button")
|
|
|
|
|
|
if button is not None:
|
|
|
|
|
|
button.configure(
|
|
|
|
|
|
style="SidebarSelected.TButton" if selected else "Sidebar.TButton"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def refresh_tool_panel_sidebar_theme(self: "PQAutomationApp") -> None:
|
|
|
|
|
|
"""主题切换后刷新工具面板侧栏项配色。"""
|
|
|
|
|
|
for panel_info in self.panels.values():
|
|
|
|
|
|
sidebar_item = panel_info.get("sidebar_item")
|
|
|
|
|
|
if sidebar_item is not None:
|
|
|
|
|
|
sidebar_item["apply_theme"]()
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-27 11:26:28 +08:00
|
|
|
|
def register_panel(self: "PQAutomationApp", panel_name, frame, button, visible_attr):
|
2026-04-20 11:48:38 +08:00
|
|
|
|
"""注册一个面板到管理系统"""
|
|
|
|
|
|
self.panels[panel_name] = {
|
|
|
|
|
|
"frame": frame,
|
|
|
|
|
|
"button": button,
|
2026-06-22 10:31:19 +08:00
|
|
|
|
"button_label": None,
|
|
|
|
|
|
"sidebar_item": None,
|
2026-04-20 11:48:38 +08:00
|
|
|
|
"visible_attr": visible_attr,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-06-22 10:40:39 +08:00
|
|
|
|
def setup_view_stack(self: "PQAutomationApp") -> None:
|
|
|
|
|
|
"""将所有工具面板叠放在 view_stack 上,切换时仅调整 Z 序。"""
|
|
|
|
|
|
if getattr(self, "_view_stack_ready", False):
|
2026-04-20 11:48:38 +08:00
|
|
|
|
return
|
|
|
|
|
|
|
2026-06-22 10:40:39 +08:00
|
|
|
|
self.main_view.place(relx=0, rely=0, relwidth=1, relheight=1)
|
|
|
|
|
|
for panel_name, panel_info in self.panels.items():
|
|
|
|
|
|
if panel_name == "local_dimming":
|
|
|
|
|
|
continue
|
|
|
|
|
|
panel_info["frame"].place(relx=0, rely=0, relwidth=1, relheight=1)
|
|
|
|
|
|
panel_info["frame"].lower()
|
2026-04-20 11:48:38 +08:00
|
|
|
|
|
2026-06-22 10:40:39 +08:00
|
|
|
|
self._view_stack_ready = True
|
2026-04-20 11:48:38 +08:00
|
|
|
|
|
|
|
|
|
|
|
2026-06-22 10:40:39 +08:00
|
|
|
|
def _lower_stack_panels(self: "PQAutomationApp") -> None:
|
|
|
|
|
|
for panel_name, panel_info in self.panels.items():
|
|
|
|
|
|
if panel_name in _STACK_PANELS:
|
|
|
|
|
|
panel_info["frame"].lower()
|
2026-04-20 11:48:38 +08:00
|
|
|
|
|
|
|
|
|
|
|
2026-06-22 10:40:39 +08:00
|
|
|
|
def _clear_panel_selection(self: "PQAutomationApp") -> None:
|
|
|
|
|
|
for panel_info in self.panels.values():
|
2026-06-22 10:31:19 +08:00
|
|
|
|
_set_tool_panel_sidebar_selected(panel_info, False)
|
2026-04-20 11:48:38 +08:00
|
|
|
|
setattr(self, panel_info["visible_attr"], False)
|
|
|
|
|
|
|
2026-06-22 10:40:39 +08:00
|
|
|
|
|
|
|
|
|
|
def _restore_main_view_layout(self: "PQAutomationApp") -> None:
|
|
|
|
|
|
"""恢复主界面三区布局,并收起嵌入的 Local Dimming 面板。"""
|
|
|
|
|
|
if getattr(self, "_ld_embedded", False):
|
|
|
|
|
|
self.local_dimming_frame.pack_forget()
|
|
|
|
|
|
self._ld_embedded = False
|
|
|
|
|
|
|
|
|
|
|
|
self.control_frame_top.pack_forget()
|
|
|
|
|
|
self.control_frame_middle.pack_forget()
|
|
|
|
|
|
self.control_frame_bottom.pack_forget()
|
2026-04-20 11:48:38 +08:00
|
|
|
|
self.control_frame_top.pack(
|
|
|
|
|
|
side=tk.TOP, fill=tk.BOTH, expand=True, padx=0, pady=5
|
|
|
|
|
|
)
|
|
|
|
|
|
self.control_frame_middle.pack(
|
|
|
|
|
|
side=tk.TOP, fill=tk.BOTH, expand=True, padx=0, pady=5
|
|
|
|
|
|
)
|
|
|
|
|
|
self.control_frame_bottom.pack(
|
|
|
|
|
|
side=tk.TOP, fill=tk.BOTH, expand=True, padx=0, pady=5
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-06-22 10:40:39 +08:00
|
|
|
|
|
|
|
|
|
|
def _show_main_content(self: "PQAutomationApp") -> None:
|
|
|
|
|
|
"""显示主内容区并清除当前面板状态。"""
|
|
|
|
|
|
setup_view_stack(self)
|
|
|
|
|
|
_lower_stack_panels(self)
|
|
|
|
|
|
_restore_main_view_layout(self)
|
|
|
|
|
|
self.main_view.lift()
|
|
|
|
|
|
_clear_panel_selection(self)
|
2026-04-20 11:48:38 +08:00
|
|
|
|
self.current_panel = None
|
|
|
|
|
|
|
2026-06-15 10:58:51 +08:00
|
|
|
|
if hasattr(self, "_sync_save_button_state"):
|
|
|
|
|
|
try:
|
|
|
|
|
|
self._sync_save_button_state()
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
|
2026-06-22 10:40:39 +08:00
|
|
|
|
def _notify_panel_shown(self: "PQAutomationApp", panel_name: str) -> None:
|
|
|
|
|
|
"""面板显示后的轻量刷新(延迟执行,不阻塞切换)。"""
|
|
|
|
|
|
if panel_name == "calman":
|
|
|
|
|
|
refresh = getattr(self, "_on_calman_panel_shown", None)
|
|
|
|
|
|
if refresh is not None:
|
|
|
|
|
|
self.root.after_idle(refresh)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _show_stack_panel(self: "PQAutomationApp", panel_name: str) -> None:
|
|
|
|
|
|
"""叠层模式显示工具面板。"""
|
|
|
|
|
|
setup_view_stack(self)
|
|
|
|
|
|
panel_info = self.panels[panel_name]
|
|
|
|
|
|
|
|
|
|
|
|
_set_tool_panel_sidebar_selected(panel_info, True)
|
|
|
|
|
|
|
|
|
|
|
|
previous = self.current_panel
|
|
|
|
|
|
if previous == "local_dimming":
|
|
|
|
|
|
self.local_dimming_frame.pack_forget()
|
|
|
|
|
|
self._ld_embedded = False
|
|
|
|
|
|
elif previous is not None and previous in _STACK_PANELS:
|
|
|
|
|
|
prev_info = self.panels[previous]
|
|
|
|
|
|
_set_tool_panel_sidebar_selected(prev_info, False)
|
|
|
|
|
|
setattr(self, prev_info["visible_attr"], False)
|
|
|
|
|
|
prev_info["frame"].lower()
|
|
|
|
|
|
|
|
|
|
|
|
self.main_view.lower()
|
|
|
|
|
|
panel_info["frame"].lift()
|
|
|
|
|
|
setattr(self, panel_info["visible_attr"], True)
|
|
|
|
|
|
self.current_panel = panel_name
|
|
|
|
|
|
_notify_panel_shown(self, panel_name)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _show_local_dimming_panel(self: "PQAutomationApp") -> None:
|
|
|
|
|
|
"""Local Dimming:保留顶部配置区,下方嵌入 LD 面板。"""
|
|
|
|
|
|
setup_view_stack(self)
|
|
|
|
|
|
panel_info = self.panels["local_dimming"]
|
|
|
|
|
|
|
|
|
|
|
|
if self.current_panel in _STACK_PANELS:
|
|
|
|
|
|
prev_info = self.panels[self.current_panel]
|
|
|
|
|
|
_set_tool_panel_sidebar_selected(prev_info, False)
|
|
|
|
|
|
setattr(self, prev_info["visible_attr"], False)
|
|
|
|
|
|
prev_info["frame"].lower()
|
|
|
|
|
|
|
|
|
|
|
|
_set_tool_panel_sidebar_selected(panel_info, True)
|
|
|
|
|
|
_lower_stack_panels(self)
|
|
|
|
|
|
self.main_view.lift()
|
|
|
|
|
|
|
|
|
|
|
|
self.control_frame_top.pack_forget()
|
|
|
|
|
|
self.control_frame_top.pack(
|
|
|
|
|
|
side=tk.TOP, fill=tk.X, expand=False, padx=0, pady=5
|
|
|
|
|
|
)
|
|
|
|
|
|
self.control_frame_middle.pack_forget()
|
|
|
|
|
|
self.control_frame_bottom.pack_forget()
|
|
|
|
|
|
self.local_dimming_frame.pack(
|
|
|
|
|
|
side=tk.TOP, fill=tk.BOTH, expand=True, padx=5, pady=5
|
|
|
|
|
|
)
|
|
|
|
|
|
self._ld_embedded = True
|
|
|
|
|
|
|
|
|
|
|
|
setattr(self, panel_info["visible_attr"], True)
|
|
|
|
|
|
self.current_panel = "local_dimming"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _maybe_restore_local_dimming_panel(
|
|
|
|
|
|
self: "PQAutomationApp", closed_panel: str
|
|
|
|
|
|
) -> None:
|
|
|
|
|
|
"""关闭工具面板后,Local Dimming 测试类型下自动恢复 LD 面板。"""
|
|
|
|
|
|
if closed_panel == "local_dimming":
|
|
|
|
|
|
return
|
|
|
|
|
|
try:
|
|
|
|
|
|
if (
|
|
|
|
|
|
getattr(self, "config", None)
|
|
|
|
|
|
and getattr(self.config, "current_test_type", None) == "local_dimming"
|
|
|
|
|
|
):
|
|
|
|
|
|
self.show_panel("local_dimming")
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def show_panel(self: "PQAutomationApp", panel_name):
|
|
|
|
|
|
"""显示指定面板,隐藏其他所有面板"""
|
|
|
|
|
|
if panel_name not in self.panels:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
if self.current_panel == panel_name:
|
|
|
|
|
|
_show_main_content(self)
|
|
|
|
|
|
_maybe_restore_local_dimming_panel(self, panel_name)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
if panel_name == "local_dimming":
|
|
|
|
|
|
_show_local_dimming_panel(self)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
if panel_name in _STACK_PANELS:
|
|
|
|
|
|
_show_stack_panel(self, panel_name)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 未知面板类型:回退到叠层显示
|
|
|
|
|
|
_show_stack_panel(self, panel_name)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def hide_all_panels(self: "PQAutomationApp"):
|
|
|
|
|
|
"""隐藏所有面板,显示主内容区域"""
|
|
|
|
|
|
_show_main_content(self)
|
2026-05-27 11:26:28 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PanelManagerMixin:
|
|
|
|
|
|
"""由 tools/refactor_to_mixins.py 自动生成。
|
|
|
|
|
|
把本模块的自由函数挂到 PQAutomationApp 上,便于 F12 跳转与类型推断。
|
|
|
|
|
|
"""
|
2026-06-22 10:31:19 +08:00
|
|
|
|
create_tool_panel_sidebar_item = create_tool_panel_sidebar_item
|
2026-05-27 11:26:28 +08:00
|
|
|
|
register_panel = register_panel
|
2026-06-22 10:40:39 +08:00
|
|
|
|
setup_view_stack = setup_view_stack
|
2026-05-27 11:26:28 +08:00
|
|
|
|
show_panel = show_panel
|
|
|
|
|
|
hide_all_panels = hide_all_panels
|
2026-06-22 10:31:19 +08:00
|
|
|
|
refresh_tool_panel_sidebar_theme = refresh_tool_panel_sidebar_theme
|