Skip to content

Commit 91da6f6

Browse files
authored
Merge pull request Nuist666#26 from Nuist666/Nuist666-patch-12
[feat] integrate ttk themes for UI
2 parents 802cdc9 + 0d85701 commit 91da6f6

File tree

8 files changed

+174
-95
lines changed

8 files changed

+174
-95
lines changed

main_multi.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@
2222

2323
def main():
2424
admin_running()
25-
root = tk.Tk()
25+
try:
26+
import ttkbootstrap as ttkb
27+
root = ttkb.Window(themename="superhero")
28+
except Exception:
29+
root = tk.Tk()
2630
MultiApp(root)
2731
root.mainloop()
2832

main_single.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@
2121

2222
def main():
2323
admin_running()
24-
root = tk.Tk()
24+
try:
25+
import ttkbootstrap as ttkb
26+
root = ttkb.Window(themename="superhero")
27+
except Exception:
28+
root = tk.Tk()
2529
SingleApp(root)
2630
root.mainloop()
2731

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ librosa==0.11.0
77
mido==1.3.3
88
tqdm==4.66.1
99
matplotlib
10-
torchlibrosa==0.1.0
10+
torchlibrosa==0.1.0
11+
ttkbootstrap==1.10.1

src/app.py

Lines changed: 105 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,19 @@ def __init__(self, root: tk.Tk, title: str, create_key_display: bool = True):
1919
self.score_text: Optional[str] = None
2020
self.player: Optional[Player] = None
2121

22-
self.frm = tk.Frame(root, padx=10, pady=10)
22+
# 初始化 ttkbootstrap(如可用),默认主题 superhero
23+
self._ttkb = None
24+
self.style = None
25+
try:
26+
import ttkbootstrap as ttkb # type: ignore
27+
self._ttkb = ttkb
28+
self.style = ttkb.Style(theme='superhero')
29+
except Exception:
30+
self._ttkb = None
31+
self.style = None
32+
33+
# 主框架
34+
self.frm = ttk.Frame(root, padding=(10, 10))
2335
self.frm.pack(fill="both", expand=True)
2436

2537
# 乐器选择(钢琴/架子鼓)
@@ -56,46 +68,78 @@ def __init__(self, root: tk.Tk, title: str, create_key_display: bool = True):
5668
self._start_key_listener()
5769

5870
def _create_instrument_frame(self):
59-
frm = tk.LabelFrame(self.frm, text="乐器")
60-
frm.pack(fill="x")
71+
frm = ttk.LabelFrame(self.frm, text="乐器")
72+
frm.pack(fill="x", pady=(0, 8))
6173
self.instrument_var = tk.StringVar(value="piano")
62-
tk.Radiobutton(frm, text="钢琴 (.lrcp / .mid)", variable=self.instrument_var, value="piano").pack(side="left", padx=4)
63-
tk.Radiobutton(frm, text="架子鼓 (.lrcd / .mid)", variable=self.instrument_var, value="drum").pack(side="left", padx=8)
74+
ttk.Radiobutton(frm, text="钢琴 (.lrcp / .mid)", variable=self.instrument_var, value="piano").pack(side="left", padx=4)
75+
ttk.Radiobutton(frm, text="架子鼓 (.lrcd / .mid)", variable=self.instrument_var, value="drum").pack(side="left", padx=8)
6476

6577
def get_instrument(self) -> str:
6678
v = self.instrument_var.get().strip().lower()
6779
return "drum" if v == "drum" else "piano"
6880

6981
def _create_file_bar(self):
70-
file_bar = tk.Frame(self.frm)
82+
file_bar = ttk.Frame(self.frm)
7183
file_bar.pack(fill="x")
72-
tk.Button(file_bar, text="载入乐谱", command=self.load_score).pack(side="left")
73-
self.lbl_file = tk.Label(file_bar, text="未载入")
84+
ttk.Button(file_bar, text="载入乐谱", command=self.load_score).pack(side="left")
85+
self.lbl_file = ttk.Label(file_bar, text="未载入")
7486
self.lbl_file.pack(side="left", padx=8)
7587

88+
# 右上角:主题切换(ttkbootstrap 可用时)
89+
if self.style is not None:
90+
try:
91+
# 仅列出 ttkbootstrap 主题
92+
theme_names = [t for t in self.style.theme_names() if t]
93+
current_theme = None
94+
try:
95+
current_theme = self.style.theme_use()
96+
except Exception:
97+
current_theme = theme_names[0] if theme_names else ""
98+
99+
right_box = ttk.Frame(file_bar)
100+
right_box.pack(side="right")
101+
ttk.Label(right_box, text="主题:").pack(side="left")
102+
self.theme_var = tk.StringVar(value=current_theme)
103+
self.cbo_theme = ttk.Combobox(right_box, width=16, state="readonly", values=theme_names, textvariable=self.theme_var)
104+
self.cbo_theme.pack(side="left", padx=4)
105+
106+
def on_theme_changed(event=None):
107+
name = self.theme_var.get()
108+
try:
109+
self.style.theme_use(name)
110+
except Exception:
111+
pass
112+
self.cbo_theme.bind("<<ComboboxSelected>>", on_theme_changed)
113+
except Exception:
114+
pass
115+
76116
def _create_params_frame(self):
77-
params = tk.LabelFrame(self.frm, text="参数")
117+
params = ttk.LabelFrame(self.frm, text="参数")
78118
params.pack(fill="x", pady=8)
79-
tk.Label(params, text="速度比例(1.0为原速):").grid(row=0, column=0, sticky="e")
119+
ttk.Label(params, text="速度比例(1.0为原速):").grid(row=0, column=0, sticky="e")
80120
self.ent_speed = ttk.Combobox(params, width=8, state="readonly", values=["0.5", "0.75", "1.0", "1.25", "1.5", "1.75", "2.0", "2.25", "2.5"])
81121
self.ent_speed.set("1.0")
82122
self.ent_speed.grid(row=0, column=1, sticky="w", padx=6)
83-
tk.Label(params, text="起始倒计时(秒):").grid(row=0, column=2, sticky="e")
123+
ttk.Label(params, text="起始倒计时(秒):").grid(row=0, column=2, sticky="e")
84124
self.ent_countin = ttk.Combobox(params, width=8, state="readonly", values=["0", "1", "2", "3", "4", "5"])
85125
self.ent_countin.set("2")
86126
self.ent_countin.grid(row=0, column=3, sticky="w", padx=6)
87-
tk.Label(params, text="全局延迟(毫秒):").grid(row=0, column=4, sticky="e")
88-
self.ent_latency = tk.Spinbox(params, width=8, from_=-200, to=200, increment=5)
127+
ttk.Label(params, text="全局延迟(毫秒):").grid(row=0, column=4, sticky="e")
128+
# Spinbox 使用 ttkbootstrap(如可用)
129+
if self._ttkb is not None and hasattr(self._ttkb, 'Spinbox'):
130+
self.ent_latency = self._ttkb.Spinbox(params, width=8, from_=-200, to=200, increment=5)
131+
else:
132+
self.ent_latency = tk.Spinbox(params, width=8, from_=-200, to=200, increment=5)
89133
self.ent_latency.delete(0, "end")
90134
self.ent_latency.insert(0, "0")
91135
self.ent_latency.grid(row=0, column=5, sticky="w", padx=6)
92136

93137
# 添加进度更新频率配置
94-
tk.Label(params, text="进度更新频率:").grid(row=1, column=0, sticky="e")
138+
ttk.Label(params, text="进度更新频率:").grid(row=1, column=0, sticky="e")
95139
self.ent_progress_freq = ttk.Combobox(params, width=8, state="readonly", values=["1", "2", "3", "5", "10"])
96140
self.ent_progress_freq.set("1")
97141
self.ent_progress_freq.grid(row=1, column=1, sticky="w", padx=6)
98-
tk.Label(params, text="(1=每个动作都更新, 2=每2个动作更新, 以此类推)").grid(row=1, column=2, columnspan=4, sticky="w")
142+
ttk.Label(params, text="(1=每个动作都更新, 2=每2个动作更新, 以此类推)").grid(row=1, column=2, columnspan=4, sticky="w")
99143

100144
# 记录参数控件,便于统一禁用/启用
101145
self.param_widgets = [
@@ -106,34 +150,34 @@ def _create_params_frame(self):
106150
]
107151

108152
def _create_control_frame(self):
109-
ctrl = tk.Frame(self.frm)
153+
ctrl = ttk.Frame(self.frm)
110154
ctrl.pack(fill="x", pady=6)
111-
self.btn_start = tk.Button(ctrl, text="开始演奏", command=self.toggle_play_pause, state="disabled")
155+
self.btn_start = ttk.Button(ctrl, text="开始演奏", command=self.toggle_play_pause, state="disabled")
112156
self.btn_start.pack(side="left", padx=4)
113-
self.btn_stop = tk.Button(ctrl, text="停止", command=self.stop_play, state="disabled")
157+
self.btn_stop = ttk.Button(ctrl, text="停止", command=self.stop_play, state="disabled")
114158
self.btn_stop.pack(side="left", padx=4)
115159
# 新增:按键显示设置按钮
116-
self.btn_keycast = tk.Button(ctrl, text="按键显示设置", command=self.open_keycast_settings)
160+
self.btn_keycast = ttk.Button(ctrl, text="按键显示设置", command=self.open_keycast_settings)
117161
self.btn_keycast.pack(side="left", padx=4)
118162
# 新增:动作录制按钮(按当前乐器类型)
119-
self.btn_record = tk.Button(ctrl, text="动作录制", command=lambda: open_recorder_window(self.root, self.get_instrument()))
163+
self.btn_record = ttk.Button(ctrl, text="动作录制", command=lambda: open_recorder_window(self.root, self.get_instrument()))
120164
self.btn_record.pack(side="left", padx=4)
121165

122-
self.lbl_status = tk.Label(ctrl, text="状态:等待载入乐谱")
166+
self.lbl_status = ttk.Label(ctrl, text="状态:等待载入乐谱")
123167
self.lbl_status.pack(side="left", padx=10)
124168

125169
# 添加进度条框架
126-
progress_frame = tk.Frame(self.frm)
170+
progress_frame = ttk.Frame(self.frm)
127171
progress_frame.pack(fill="x", pady=4)
128-
self.lbl_progress = tk.Label(progress_frame, text="进度:0%")
172+
self.lbl_progress = ttk.Label(progress_frame, text="进度:0%")
129173
self.lbl_progress.pack(side="left", padx=10)
130174
self.progress_bar = ttk.Progressbar(progress_frame, mode='determinate', length=300)
131175
self.progress_bar.pack(side="left", padx=4, fill="x", expand=True)
132176

133177
def _create_tips_frame(self):
134-
tips = tk.LabelFrame(self.frm, text="使用提示")
178+
tips = ttk.LabelFrame(self.frm, text="使用提示")
135179
tips.pack(fill="x", pady=8)
136-
tk.Label(tips, justify="left", anchor="w", text=(
180+
ttk.Label(tips, justify="left", anchor="w", text=(
137181
"1) 乐谱支持延长音:写法 [起始时间][结束时间] TOKENS\n"
138182
"2) 单时间戳仍可用作短音:[时间] TOKENS\n"
139183
"3) 载入后切换到游戏窗口,回到本工具点击开始;\n"
@@ -143,25 +187,30 @@ def _create_tips_frame(self):
143187

144188
def _create_key_display_frame(self):
145189
"""创建按键显示框架(窗口内)"""
146-
key_frame = tk.LabelFrame(self.frm, text="按键显示")
190+
key_frame = ttk.LabelFrame(self.frm, text="按键显示")
147191
key_frame.pack(fill="x", pady=8)
148192

149-
# 创建按键显示标签
150-
self.lbl_keys = tk.Label(
151-
key_frame,
152-
text="等待按键...",
153-
font=("Consolas", 16, "bold"),
154-
fg="white",
155-
bg="#C0C0C0", # 使用深灰色模拟半透明效果
156-
height=2,
157-
anchor="center",
158-
relief="flat", # 去掉边框,让背景更平滑
159-
borderwidth=0
160-
)
193+
# 创建按键显示标签(使用主题样式)
194+
if self._ttkb is not None:
195+
self.lbl_keys = self._ttkb.Label(
196+
key_frame,
197+
text="等待按键...",
198+
font=("Consolas", 16, "bold"),
199+
bootstyle="secondary inverse",
200+
anchor="center",
201+
padding=6,
202+
)
203+
else:
204+
self.lbl_keys = ttk.Label(
205+
key_frame,
206+
text="等待按键...",
207+
font=("Consolas", 16, "bold"),
208+
anchor="center",
209+
)
161210
self.lbl_keys.pack(fill="x", padx=4, pady=4)
162211

163212
# 添加说明文字
164-
tk.Label(key_frame, text="实时显示当前按下的按键 (窗口内)", font=("微软雅黑", 9), fg="gray").pack(anchor="w", padx=4)
213+
ttk.Label(key_frame, text="实时显示当前按下的按键 (窗口内)", font=("微软雅黑", 9)).pack(anchor="w", padx=4)
165214

166215
def open_keycast_settings(self):
167216
"""打开覆盖层设置窗口"""
@@ -196,23 +245,35 @@ def open_keycast_settings(self):
196245
row += 1
197246

198247
ttk.Label(win, text="透明度(0.2~1.0):").grid(row=row, column=0, sticky="e", padx=6, pady=6)
199-
tk.Scale(win, from_=0.2, to=1.0, orient="horizontal", resolution=0.05, variable=var_opacity, length=200).grid(row=row, column=1, sticky="w", padx=6)
248+
if self._ttkb is not None and hasattr(self._ttkb, 'Scale'):
249+
opacity_scale = self._ttkb.Scale(win, from_=0.2, to=1.0, orient="horizontal", length=200, variable=var_opacity)
250+
else:
251+
opacity_scale = tk.Scale(win, from_=0.2, to=1.0, orient="horizontal", resolution=0.05, variable=var_opacity, length=200)
252+
opacity_scale.grid(row=row, column=1, sticky="w", padx=6)
200253
row += 1
201254

202255
ttk.Label(win, text="显示最近几个按键:").grid(row=row, column=0, sticky="e", padx=6, pady=6)
203-
tk.Spinbox(win, from_=1, to=20, textvariable=var_max_keys, width=8).grid(row=row, column=1, sticky="w", padx=6)
256+
if self._ttkb is not None and hasattr(self._ttkb, 'Spinbox'):
257+
maxkeys_spin = self._ttkb.Spinbox(win, from_=1, to=20, textvariable=var_max_keys, width=8)
258+
else:
259+
maxkeys_spin = tk.Spinbox(win, from_=1, to=20, textvariable=var_max_keys, width=8)
260+
maxkeys_spin.grid(row=row, column=1, sticky="w", padx=6)
204261
row += 1
205262

206263
ttk.Label(win, text="每个按键显示(秒):").grid(row=row, column=0, sticky="e", padx=6, pady=6)
207-
tk.Spinbox(win, from_=0.5, to=10.0, increment=0.5, textvariable=var_disp_time, width=8).grid(row=row, column=1, sticky="w", padx=6)
264+
if self._ttkb is not None and hasattr(self._ttkb, 'Spinbox'):
265+
disptime_spin = self._ttkb.Spinbox(win, from_=0.5, to=10.0, increment=0.5, textvariable=var_disp_time, width=8)
266+
else:
267+
disptime_spin = tk.Spinbox(win, from_=0.5, to=10.0, increment=0.5, textvariable=var_disp_time, width=8)
268+
disptime_spin.grid(row=row, column=1, sticky="w", padx=6)
208269
row += 1
209270

210271
ttk.Label(win, text="位置:").grid(row=row, column=0, sticky="e", padx=6, pady=6)
211272
ttk.Combobox(win, state="readonly", values=list(pos_map.keys()), textvariable=var_position, width=14).grid(row=row, column=1, sticky="w", padx=6)
212273
row += 1
213274

214275
# 按钮
215-
btns = tk.Frame(win)
276+
btns = ttk.Frame(win)
216277
btns.grid(row=row, column=0, columnspan=2, pady=10)
217278

218279
def on_ok():
@@ -265,10 +326,7 @@ def on_press(key):
265326
# 如果没有安装pynput,显示提示信息
266327
if hasattr(self, 'lbl_keys'):
267328
self.lbl_keys.config(
268-
text="需要安装 pynput 模块\npip install pynput",
269-
font=("微软雅黑", 10),
270-
fg="red",
271-
bg="lightgray"
329+
text="需要安装 pynput 模块\npip install pynput"
272330
)
273331

274332
def _update_key_display(self):

src/app_multi.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import os
22
import re
33
import tkinter as tk
4+
from tkinter import ttk
45
from typing import List
56

67
from src.app import BaseApp
@@ -18,7 +19,7 @@ def __init__(self, root: tk.Tk):
1819
# 修改提示信息为多人模式特有
1920
tips = self.frm.winfo_children()[-1] # 获取最后一个子元素(tips)
2021
tips.config(text="说明")
21-
tk.Label(tips, justify="left", anchor="w", text=(
22+
ttk.Label(tips, justify="left", anchor="w", text=(
2223
'多人模式策略:\n'
2324
'1) 钢琴:去掉所有和弦,只保留单音。\n'
2425
'2) 对同一事件中的多个单音按顺序施加时间偏移以分散密度。\n'
@@ -32,11 +33,11 @@ def __init__(self, root: tk.Tk):
3233

3334
# 添加多人模式特有的参数 - 使用正确的行数
3435
params = self.frm.winfo_children()[2] # 注意:加了乐器框后,params位序为第3个
35-
tk.Label(params, text="多音偏移(ms):").grid(row=2, column=0, sticky="e")
36-
self.ent_offsets = tk.Entry(params, width=11)
36+
ttk.Label(params, text="多音偏移(ms):").grid(row=2, column=0, sticky="e")
37+
self.ent_offsets = ttk.Entry(params, width=11)
3738
self.ent_offsets.insert(0, "-15,0,15")
3839
self.ent_offsets.grid(row=2, column=1, columnspan=3, sticky="w", padx=4)
39-
tk.Label(params, text="范围-50~50,按顺序循环应用到同一时间的多个音").grid(row=2, column=2, columnspan=2, sticky="w")
40+
ttk.Label(params, text="范围-50~50,按顺序循环应用到同一时间的多个音").grid(row=2, column=2, columnspan=2, sticky="w")
4041
# 把偏移输入框加入可统一控制的参数控件
4142
if hasattr(self, 'param_widgets'):
4243
self.param_widgets.append(self.ent_offsets)

src/app_single.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22
import tkinter as tk
3+
from tkinter import ttk
34
from typing import List
45

56
from src.app import BaseApp
@@ -14,7 +15,7 @@ def __init__(self, root: tk.Tk):
1415
self.events: List[Event] = []
1516

1617
# 键位映射提示(根据乐器切换刷新)
17-
self.mapping_frame = tk.LabelFrame(self.frm, text="键位映射(请确保与游戏一致)")
18+
self.mapping_frame = ttk.LabelFrame(self.frm, text="键位映射(请确保与游戏一致)")
1819
self.mapping_frame.pack(fill="x", pady=8)
1920
self._mapping_rows: List[tk.Widget] = []
2021
self._render_mapping()
@@ -33,10 +34,10 @@ def _clear_mapping(self):
3334
def _render_mapping(self):
3435
self._clear_mapping()
3536
def row(lbl, txt):
36-
r = tk.Frame(self.mapping_frame)
37+
r = ttk.Frame(self.mapping_frame)
3738
r.pack(fill="x", pady=1)
38-
tk.Label(r, text=lbl, width=10, anchor="w").pack(side="left")
39-
tk.Label(r, text=txt, anchor="w").pack(side="left")
39+
ttk.Label(r, text=lbl, width=10, anchor="w").pack(side="left")
40+
ttk.Label(r, text=txt, anchor="w").pack(side="left")
4041
self._mapping_rows.append(r)
4142
if self.get_instrument() == 'piano':
4243
row("低音 L:", "L1-L7 -> a s d f g h j")

0 commit comments

Comments
 (0)