新增二次元人物联动UI,新增键盘样式
This commit is contained in:
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"window_pos": {"x": 100, "y": 100},
|
||||||
|
"alpha": 0.8,
|
||||||
|
"monitored_keys": ["w", "a", "s", "d", "space", "shift"],
|
||||||
|
"counts": {
|
||||||
|
"w": 10,
|
||||||
|
"a": 5,
|
||||||
|
"s": 0,
|
||||||
|
"d": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"alpha": 0.8,
|
||||||
|
"position": "+100+100",
|
||||||
|
"monitored_keys": [
|
||||||
|
"w",
|
||||||
|
"a",
|
||||||
|
"s",
|
||||||
|
"d",
|
||||||
|
"space"
|
||||||
|
],
|
||||||
|
"counts": {
|
||||||
|
"w": 9,
|
||||||
|
"a": 0,
|
||||||
|
"s": 0,
|
||||||
|
"d": 0,
|
||||||
|
"space": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
+137
-67
@@ -3,7 +3,7 @@ from tkinter import simpledialog, messagebox
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from pynput import keyboard
|
from pynput import keyboard
|
||||||
import threading
|
from PIL import Image, ImageTk
|
||||||
|
|
||||||
# 配置文件路径
|
# 配置文件路径
|
||||||
CONFIG_FILE = "key_config.json"
|
CONFIG_FILE = "key_config.json"
|
||||||
@@ -11,148 +11,218 @@ CONFIG_FILE = "key_config.json"
|
|||||||
class KeyOverlayApp:
|
class KeyOverlayApp:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.root = tk.Tk()
|
self.root = tk.Tk()
|
||||||
self.root.title("KeyOverlay Py")
|
self.root.title("KeyOverlay Ultra")
|
||||||
|
|
||||||
# 初始配置
|
# 初始默认配置
|
||||||
self.config = {
|
self.config = {
|
||||||
"alpha": 0.8,
|
"alpha": 0.8,
|
||||||
"position": "+100+100",
|
"position": "+100+100",
|
||||||
"monitored_keys": ["w", "a", "s", "d", "space"],
|
"monitored_keys": ["w", "a", "s", "d", "space"],
|
||||||
"counts": {}
|
"counts": {},
|
||||||
|
"ui_mode": "list" # "list" 或 "keyboard"
|
||||||
}
|
}
|
||||||
|
|
||||||
self.load_config()
|
self.load_config()
|
||||||
|
|
||||||
# UI 设置
|
# 窗口基础设置
|
||||||
self.root.overrideredirect(True) # 无边框
|
self.root.overrideredirect(True)
|
||||||
self.root.attributes("-topmost", True) # 置顶
|
self.root.attributes("-topmost", True)
|
||||||
self.root.attributes("-alpha", self.config["alpha"])
|
self.root.attributes("-alpha", self.config["alpha"])
|
||||||
self.root.geometry(self.config["position"])
|
self.root.geometry(self.config["position"])
|
||||||
self.root.configure(bg='#282c34')
|
self.root.configure(bg='#282c34')
|
||||||
|
|
||||||
# 标签字典,用于动态更新界面
|
# 状态变量
|
||||||
self.labels = {}
|
self.key_labels = {} # 存储按键 UI 组件
|
||||||
|
self.char_image_label = None
|
||||||
|
self.idle_photo = None
|
||||||
|
self.pressed_photo = None
|
||||||
|
self.active_keys = set() # 当前按下的键
|
||||||
|
|
||||||
|
# 加载图片
|
||||||
|
self.load_images()
|
||||||
|
|
||||||
|
# 初始化 UI
|
||||||
self.setup_ui()
|
self.setup_ui()
|
||||||
|
|
||||||
# 拖动功能变量
|
# 拖动功能
|
||||||
self._drag_data = {"x": 0, "y": 0}
|
self._drag_data = {"x": 0, "y": 0}
|
||||||
self.root.bind("<Button-1>", self.start_drag)
|
self.root.bind("<Button-1>", self.start_drag)
|
||||||
self.root.bind("<B1-Motion>", self.do_drag)
|
self.root.bind("<B1-Motion>", self.do_drag)
|
||||||
|
|
||||||
# 右键菜单
|
# 右键菜单
|
||||||
self.menu = tk.Menu(self.root, tearoff=0)
|
self.menu = tk.Menu(self.root, tearoff=0)
|
||||||
|
self.menu.add_command(label="切换样式 (列表/键盘)", command=self.toggle_ui_mode)
|
||||||
|
self.menu.add_separator()
|
||||||
self.menu.add_command(label="添加按键", command=self.add_key_dialog)
|
self.menu.add_command(label="添加按键", command=self.add_key_dialog)
|
||||||
self.menu.add_command(label="删除按键", command=self.remove_key_dialog)
|
self.menu.add_command(label="删除按键", command=self.remove_key_dialog)
|
||||||
self.menu.add_command(label="设置透明度", command=self.set_alpha_dialog)
|
self.menu.add_command(label="设置透明度", command=self.set_alpha_dialog)
|
||||||
self.menu.add_command(label="重置计数", command=self.reset_counts)
|
self.menu.add_command(label="重置计数", command=self.reset_counts)
|
||||||
self.menu.add_separator()
|
self.menu.add_separator()
|
||||||
self.menu.add_command(label="退出并保存", command=self.quit_app)
|
self.menu.add_command(label="退出并保存", command=self.quit_app)
|
||||||
|
|
||||||
self.root.bind("<Button-3>", self.show_menu)
|
self.root.bind("<Button-3>", self.show_menu)
|
||||||
|
|
||||||
# 启动监听线程
|
# 启动监听 (包含按下和松开)
|
||||||
self.listener = keyboard.Listener(on_press=self.on_press)
|
self.listener = keyboard.Listener(on_press=self.on_press, on_release=self.on_release)
|
||||||
self.listener.start()
|
self.listener.start()
|
||||||
|
|
||||||
self.root.mainloop()
|
self.root.mainloop()
|
||||||
|
|
||||||
|
def load_images(self):
|
||||||
|
"""加载人物图片,如果不存在则显示占位符"""
|
||||||
|
try:
|
||||||
|
# 假设图片大小调整为 150x150
|
||||||
|
size = (150, 150)
|
||||||
|
if os.path.exists("idle.png"):
|
||||||
|
img = Image.open("idle.png").convert("RGBA").resize(size)
|
||||||
|
self.idle_photo = ImageTk.PhotoImage(img)
|
||||||
|
if os.path.exists("pressed.png"):
|
||||||
|
img = Image.open("pressed.png").convert("RGBA").resize(size)
|
||||||
|
self.pressed_photo = ImageTk.PhotoImage(img)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"图片加载失败: {e}")
|
||||||
|
|
||||||
def load_config(self):
|
def load_config(self):
|
||||||
if os.path.exists(CONFIG_FILE):
|
if os.path.exists(CONFIG_FILE):
|
||||||
with open(CONFIG_FILE, "r") as f:
|
with open(CONFIG_FILE, "r") as f:
|
||||||
try:
|
try:
|
||||||
saved_config = json.load(f)
|
self.config.update(json.load(f))
|
||||||
self.config.update(saved_config)
|
except: pass
|
||||||
except:
|
|
||||||
pass
|
|
||||||
# 初始化缺失的计数
|
|
||||||
for key in self.config["monitored_keys"]:
|
for key in self.config["monitored_keys"]:
|
||||||
if key not in self.config["counts"]:
|
if key not in self.config["counts"]:
|
||||||
self.config["counts"][key] = 0
|
self.config["counts"][key] = 0
|
||||||
|
|
||||||
def save_config(self):
|
def save_config(self):
|
||||||
# 保存位置信息
|
|
||||||
self.config["position"] = f"+{self.root.winfo_x()}+{self.root.winfo_y()}"
|
self.config["position"] = f"+{self.root.winfo_x()}+{self.root.winfo_y()}"
|
||||||
with open(CONFIG_FILE, "w") as f:
|
with open(CONFIG_FILE, "w") as f:
|
||||||
json.dump(self.config, f, indent=4)
|
json.dump(self.config, f, indent=4)
|
||||||
|
|
||||||
def setup_ui(self):
|
def setup_ui(self):
|
||||||
# 清除旧标签
|
"""核心 UI 构建,支持两种模式"""
|
||||||
for label in self.labels.values():
|
# 清空当前界面
|
||||||
label.destroy()
|
for widget in self.root.winfo_children():
|
||||||
self.labels = {}
|
if widget != self.menu: widget.destroy()
|
||||||
|
self.key_labels = {}
|
||||||
|
|
||||||
# 创建新标签
|
# 1. 人物显示区域 (始终在最上方)
|
||||||
|
self.char_image_label = tk.Label(self.root, bg='#282c34')
|
||||||
|
if self.idle_photo:
|
||||||
|
self.char_image_label.config(image=self.idle_photo)
|
||||||
|
else:
|
||||||
|
self.char_image_label.config(text="(No Image)", fg="gray")
|
||||||
|
self.char_image_label.pack(pady=5)
|
||||||
|
|
||||||
|
# 2. 按键显示区域
|
||||||
|
container = tk.Frame(self.root, bg='#282c34')
|
||||||
|
container.pack(padx=10, pady=5)
|
||||||
|
|
||||||
|
if self.config["ui_mode"] == "keyboard":
|
||||||
|
# 键盘样式:网格/横向排列
|
||||||
for i, key in enumerate(self.config["monitored_keys"]):
|
for i, key in enumerate(self.config["monitored_keys"]):
|
||||||
count = self.config["counts"].get(key, 0)
|
frame = tk.Frame(container, bg='#3e4451', highlightbackground="gray", highlightthickness=1)
|
||||||
lbl = tk.Label(self.root, text=f"{key.upper()}: {count}",
|
frame.pack(side=tk.LEFT, padx=2)
|
||||||
fg="white", bg="#282c34",
|
|
||||||
font=("Consolas", 12, "bold"),
|
name_lbl = tk.Label(frame, text=key.upper(), fg="white", bg='#3e4451', font=("Consolas", 10))
|
||||||
padx=10, pady=5)
|
name_lbl.pack()
|
||||||
|
count_lbl = tk.Label(frame, text=str(self.config["counts"].get(key, 0)), fg="#61afef", bg='#3e4451', font=("Consolas", 8))
|
||||||
|
count_lbl.pack()
|
||||||
|
|
||||||
|
self.key_labels[key] = {"frame": frame, "name": name_lbl, "count": count_lbl}
|
||||||
|
else:
|
||||||
|
# 列表样式
|
||||||
|
for key in self.config["monitored_keys"]:
|
||||||
|
lbl = tk.Label(container, text=f"{key.upper()}: {self.config['counts'].get(key, 0)}",
|
||||||
|
fg="white", bg='#282c34', font=("Consolas", 12, "bold"))
|
||||||
lbl.pack(fill=tk.X)
|
lbl.pack(fill=tk.X)
|
||||||
self.labels[key] = lbl
|
self.key_labels[key] = {"label": lbl}
|
||||||
|
|
||||||
def on_press(self, key):
|
def on_press(self, key):
|
||||||
try:
|
k = self._get_key_str(key)
|
||||||
# 尝试获取按键名称
|
|
||||||
if hasattr(key, 'char') and key.char is not None:
|
|
||||||
k = key.char.lower()
|
|
||||||
else:
|
|
||||||
k = key.name.lower()
|
|
||||||
except:
|
|
||||||
return
|
|
||||||
|
|
||||||
if k in self.config["monitored_keys"]:
|
if k in self.config["monitored_keys"]:
|
||||||
self.config["counts"][k] = self.config["counts"].get(k, 0) + 1
|
# 状态更新
|
||||||
# 在主线程更新 UI
|
if k not in self.active_keys:
|
||||||
count = self.config["counts"][k]
|
self.config["counts"][k] += 1
|
||||||
self.root.after(0, lambda: self.labels[k].config(text=f"{k.upper()}: {count}"))
|
self.active_keys.add(k)
|
||||||
|
|
||||||
# 拖动逻辑
|
# UI 实时反馈
|
||||||
|
self.root.after(0, lambda: self.update_key_ui(k, True))
|
||||||
|
|
||||||
|
def on_release(self, key):
|
||||||
|
k = self._get_key_str(key)
|
||||||
|
if k in self.active_keys:
|
||||||
|
self.active_keys.remove(k)
|
||||||
|
# UI 恢复反馈
|
||||||
|
self.root.after(0, lambda: self.update_key_ui(k, False))
|
||||||
|
|
||||||
|
def _get_key_str(self, key):
|
||||||
|
try:
|
||||||
|
if hasattr(key, 'char') and key.char: return key.char.lower()
|
||||||
|
return key.name.lower()
|
||||||
|
except: return None
|
||||||
|
|
||||||
|
def update_key_ui(self, key, is_pressed):
|
||||||
|
"""更新按键高亮和人物图片"""
|
||||||
|
# 更新人物图片
|
||||||
|
if self.char_image_label and self.idle_photo and self.pressed_photo:
|
||||||
|
if len(self.active_keys) > 0:
|
||||||
|
self.char_image_label.config(image=self.pressed_photo)
|
||||||
|
else:
|
||||||
|
self.char_image_label.config(image=self.idle_photo)
|
||||||
|
|
||||||
|
# 更新按键方块
|
||||||
|
if key in self.key_labels:
|
||||||
|
if self.config["ui_mode"] == "keyboard":
|
||||||
|
data = self.key_labels[key]
|
||||||
|
color = "#98c379" if is_pressed else "#3e4451" # 按下变绿
|
||||||
|
data["frame"].config(bg=color)
|
||||||
|
data["name"].config(bg=color)
|
||||||
|
data["count"].config(bg=color, text=str(self.config["counts"][key]))
|
||||||
|
else:
|
||||||
|
lbl = self.key_labels[key]["label"]
|
||||||
|
lbl.config(text=f"{key.upper()}: {self.config['counts'][key]}",
|
||||||
|
fg="#98c379" if is_pressed else "white")
|
||||||
|
|
||||||
|
# 基础交互函数
|
||||||
def start_drag(self, event):
|
def start_drag(self, event):
|
||||||
self._drag_data["x"] = event.x
|
self._drag_data["x"], self._drag_data["y"] = event.x, event.y
|
||||||
self._drag_data["y"] = event.y
|
|
||||||
|
|
||||||
def do_drag(self, event):
|
def do_drag(self, event):
|
||||||
x = self.root.winfo_x() - self._drag_data["x"] + event.x
|
x = self.root.winfo_x() - self._drag_data["x"] + event.x
|
||||||
y = self.root.winfo_y() - self._drag_data["y"] + event.y
|
y = self.root.winfo_y() - self._drag_data["y"] + event.y
|
||||||
self.root.geometry(f"+{x}+{y}")
|
self.root.geometry(f"+{x}+{y}")
|
||||||
|
|
||||||
# 菜单功能
|
|
||||||
def show_menu(self, event):
|
def show_menu(self, event):
|
||||||
self.menu.post(event.x_root, event.y_root)
|
self.menu.post(event.x_root, event.y_root)
|
||||||
|
|
||||||
|
def toggle_ui_mode(self):
|
||||||
|
self.config["ui_mode"] = "keyboard" if self.config["ui_mode"] == "list" else "list"
|
||||||
|
self.setup_ui()
|
||||||
|
|
||||||
def add_key_dialog(self):
|
def add_key_dialog(self):
|
||||||
new_key = simpledialog.askstring("添加按键", "输入按键名称 (如: q, space, shift):")
|
nk = simpledialog.askstring("添加", "按键名:")
|
||||||
if new_key:
|
if nk:
|
||||||
new_key = new_key.lower()
|
nk = nk.lower()
|
||||||
if new_key not in self.config["monitored_keys"]:
|
if nk not in self.config["monitored_keys"]:
|
||||||
self.config["monitored_keys"].append(new_key)
|
self.config["monitored_keys"].append(nk)
|
||||||
if new_key not in self.config["counts"]:
|
self.config["counts"][nk] = 0
|
||||||
self.config["counts"][new_key] = 0
|
|
||||||
self.setup_ui()
|
self.setup_ui()
|
||||||
|
|
||||||
def remove_key_dialog(self):
|
def remove_key_dialog(self):
|
||||||
key_to_remove = simpledialog.askstring("删除按键", "输入要删除的按键名称:")
|
rk = simpledialog.askstring("删除", "按键名:")
|
||||||
if key_to_remove and key_to_remove.lower() in self.config["monitored_keys"]:
|
if rk and rk.lower() in self.config["monitored_keys"]:
|
||||||
self.config["monitored_keys"].remove(key_to_remove.lower())
|
self.config["monitored_keys"].remove(rk.lower())
|
||||||
self.setup_ui()
|
self.setup_ui()
|
||||||
|
|
||||||
def set_alpha_dialog(self):
|
def set_alpha_dialog(self):
|
||||||
new_alpha = simpledialog.askfloat("设置透明度", "输入透明度 (0.1 - 1.0):", minvalue=0.1, maxvalue=1.0)
|
a = simpledialog.askfloat("透明度", "0.1-1.0:", minvalue=0.1, maxvalue=1.0)
|
||||||
if new_alpha:
|
if a: self.root.attributes("-alpha", a); self.config["alpha"] = a
|
||||||
self.config["alpha"] = new_alpha
|
|
||||||
self.root.attributes("-alpha", new_alpha)
|
|
||||||
|
|
||||||
def reset_counts(self):
|
def reset_counts(self):
|
||||||
if messagebox.askyesno("重置", "确定要清空所有按键计数吗?"):
|
if messagebox.askyesno("重置", "清空所有计数?"):
|
||||||
for key in self.config["counts"]:
|
for k in self.config["counts"]: self.config["counts"][k] = 0
|
||||||
self.config["counts"][key] = 0
|
|
||||||
self.setup_ui()
|
self.setup_ui()
|
||||||
|
|
||||||
def quit_app(self):
|
def quit_app(self):
|
||||||
self.save_config()
|
self.save_config(); self.root.destroy(); os._exit(0)
|
||||||
self.root.destroy()
|
|
||||||
os._exit(0)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
KeyOverlayApp()
|
KeyOverlayApp()
|
||||||
|
|||||||
Reference in New Issue
Block a user