Files
pqAutomationApp/app/views/panel_manager.py
2026-06-22 10:52:51 +08:00

371 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""面板管理器Step 6 重构)。
register_panel / show_panel / hide_all_panels —— 在右侧栏不同浮动面板间切换。
"""
from __future__ import annotations
import tkinter as tk
import tkinter.font as tkfont
from typing import TYPE_CHECKING, Any, Callable
if TYPE_CHECKING:
from pqAutomationApp import PQAutomationApp
# 叠层切换lift/lower避免 pack_forget 触发 Matplotlib 等大面板重绘。
_STACK_PANELS = frozenset(
{"log", "ai_image", "pantone_baseline", "gamma_pattern", "calman", "single_step"}
)
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"]()
def register_panel(self: "PQAutomationApp", panel_name, frame, button, visible_attr):
"""注册一个面板到管理系统"""
self.panels[panel_name] = {
"frame": frame,
"button": button,
"button_label": None,
"sidebar_item": None,
"visible_attr": visible_attr,
}
def setup_view_stack(self: "PQAutomationApp") -> None:
"""将所有工具面板叠放在 view_stack 上,切换时仅调整 Z 序。"""
if getattr(self, "_view_stack_ready", False):
return
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()
self._view_stack_ready = True
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()
def _clear_panel_selection(self: "PQAutomationApp") -> None:
for panel_info in self.panels.values():
_set_tool_panel_sidebar_selected(panel_info, False)
setattr(self, panel_info["visible_attr"], False)
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()
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
)
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)
self.current_panel = None
if hasattr(self, "_sync_save_button_state"):
try:
self._sync_save_button_state()
except Exception:
pass
def _notify_panel_shown(self: "PQAutomationApp", panel_name: str) -> None:
"""面板显示后的轻量刷新(延迟执行,不阻塞切换)。"""
if getattr(self, "_theme_transition", False):
return
if panel_name == "calman":
refresh = getattr(self, "_on_calman_panel_shown", None)
if refresh is not None:
self.root.after_idle(refresh)
return
if panel_name == "ai_image" and getattr(self, "_ai_image_theme_dirty", False):
self.root.after_idle(lambda: self.refresh_ai_image_theme())
return
if panel_name == "log" and hasattr(self, "log_gui") and hasattr(
self.log_gui, "refresh_log_theme"
):
self.root.after_idle(self.log_gui.refresh_log_theme)
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)
class PanelManagerMixin:
"""由 tools/refactor_to_mixins.py 自动生成。
把本模块的自由函数挂到 PQAutomationApp 上,便于 F12 跳转与类型推断。
"""
create_tool_panel_sidebar_item = create_tool_panel_sidebar_item
register_panel = register_panel
setup_view_stack = setup_view_stack
show_panel = show_panel
hide_all_panels = hide_all_panels
refresh_tool_panel_sidebar_theme = refresh_tool_panel_sidebar_theme