File size: 5,505 Bytes
9f5687a 7c651d5 9f5687a 7c651d5 9f5687a 7c651d5 9f5687a 7c651d5 9f5687a 7c651d5 9f5687a 7c651d5 9f5687a 7c651d5 9f5687a 7c651d5 9f5687a 7c651d5 9f5687a 7c651d5 9f5687a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 | """Native control-panel UI for a WatcheRobot Application."""
from __future__ import annotations
import tkinter as tk
from tkinter import scrolledtext, ttk
# 机器人动作清单:动作ID -> 按钮显示名(与桌面端 desktop-expression-resources 一致)
ACTIONS: list[tuple[str, str]] = [
("happy", "开心"),
("smile", "微笑"),
("get", "打招呼"),
("sunglasses", "得意"),
("thinking", "思考"),
("listening", "聆听"),
("speaking", "说话"),
("focus", "专注"),
("sad", "难过"),
("shock", "惊讶"),
("fondle_love", "摸摸头"),
("fondle_anger", "生气"),
]
# 灯光控制:颜色 -> 显示名(关灯用 lights.off)
LIGHTS: list[tuple[str, str]] = [
("#ff4d4f", "红"),
("#52c41a", "绿"),
("#4096ff", "蓝"),
("#ffd666", "黄"),
]
FONT_MAIN = ("PingFang SC", 13)
FONT_TITLE = ("PingFang SC", 18, "bold")
FONT_SMALL = ("PingFang SC", 11)
class ControlPanel:
"""Button control panel: robot actions, lights, stop, and a live log."""
def __init__(
self,
*,
on_action=None,
on_light=None,
on_lights_off=None,
on_stop=None,
) -> None:
self._is_open = True
self._on_action = on_action
self._on_light = on_light
self._on_lights_off = on_lights_off
self._on_stop = on_stop
self._root = tk.Tk()
self._root.title("机器人控制台 - WatcheRobot")
self._root.geometry("680x560")
self._root.minsize(600, 480)
self._root.protocol("WM_DELETE_WINDOW", self.close)
container = ttk.Frame(self._root, padding=20)
container.pack(fill="both", expand=True)
# 标题与连接状态
header = ttk.Frame(container)
header.pack(fill="x")
ttk.Label(header, text="机器人控制台", font=FONT_TITLE).pack(side="left")
self._status_var = tk.StringVar(value="正在连接机器人…")
ttk.Label(header, textvariable=self._status_var, font=FONT_SMALL).pack(
side="right", pady=(6, 0)
)
ttk.Separator(container).pack(fill="x", pady=(12, 10))
# 动作按钮区
ttk.Label(container, text="表情 / 动作", font=FONT_MAIN).pack(anchor="w")
grid = ttk.Frame(container)
grid.pack(fill="x", pady=(6, 10))
for index, (action_id, label) in enumerate(ACTIONS):
row, col = divmod(index, 4)
button = ttk.Button(
grid,
text=f"{label}\n{action_id}",
command=lambda aid=action_id, name=label: self._fire_action(aid, name),
)
button.grid(row=row, column=col, sticky="nsew", padx=5, pady=5, ipady=6)
for col in range(4):
grid.columnconfigure(col, weight=1)
# 灯光按钮区
ttk.Label(container, text="灯光", font=FONT_MAIN).pack(anchor="w")
light_row = ttk.Frame(container)
light_row.pack(fill="x", pady=(6, 10))
for color, label in LIGHTS:
ttk.Button(
light_row,
text=label,
command=lambda c=color, name=label: self._fire_light(c, name),
).pack(side="left", padx=5, ipady=4, expand=True, fill="x")
ttk.Button(
light_row, text="关灯", command=self._fire_lights_off
).pack(side="left", padx=5, ipady=4, expand=True, fill="x")
# 停止按钮
ttk.Button(
container, text="■ 立即停止所有动作", command=self._fire_stop
).pack(fill="x", ipady=6, pady=(4, 10))
# 日志区
ttk.Label(container, text="运行日志", font=FONT_MAIN).pack(anchor="w")
self._log = scrolledtext.ScrolledText(
container, height=8, font=("Menlo", 11), state="disabled"
)
self._log.pack(fill="both", expand=True)
# ---- 对外接口 ----
def set_status(self, text: str) -> None:
self._status_var.set(text)
def log(self, text: str) -> None:
self._log.configure(state="normal")
self._log.insert("end", text + "\n")
self._log.see("end")
self._log.configure(state="disabled")
@property
def is_open(self) -> bool:
return self._is_open
def pump(self) -> None:
"""Process one native UI event cycle without blocking asyncio."""
if not self._is_open:
return
try:
self._root.update_idletasks()
self._root.update()
except tk.TclError:
self._is_open = False
def close(self) -> None:
if not self._is_open:
return
self._is_open = False
try:
self._root.destroy()
except tk.TclError:
pass
# ---- 按钮事件转发(回调由 app.py 注入) ----
def _fire_action(self, action_id: str, name: str) -> None:
self.log(f"▶ 请求动作:{name}({action_id})")
if self._on_action:
self._on_action(action_id, name)
def _fire_light(self, color: str, name: str) -> None:
self.log(f"◆ 请求灯光:{name}({color})")
if self._on_light:
self._on_light(color, name)
def _fire_lights_off(self) -> None:
self.log("◇ 请求关灯")
if self._on_lights_off:
self._on_lights_off()
def _fire_stop(self) -> None:
self.log("■ 请求停止所有动作")
if self._on_stop:
self._on_stop()
|