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()