Skip to content

Commit bf9696d

Browse files
committed
improved ui add requiments.txt
1 parent d43f814 commit bf9696d

2 files changed

Lines changed: 130 additions & 80 deletions

File tree

main.py

Lines changed: 128 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,18 @@
33
import subprocess
44
import threading
55
import tkinter as tk
6-
from tkinter import ttk, messagebox
7-
import requests
6+
from tkinter import messagebox
7+
import sys
8+
import time
9+
10+
try:
11+
import requests
12+
import ttkbootstrap as ttk
13+
from ttkbootstrap.constants import *
14+
except ImportError:
15+
# Just in case dependencies aren't installed yet, fail gracefully or fallback
16+
import tkinter.ttk as ttk
17+
from tkinter import TOP, BOTTOM, LEFT, RIGHT, BOTH, X, Y, END
818

919
# Constants
1020
LUA_FILES_DIR = r"d:\antigravity\luapatcher\All Games Files"
@@ -15,191 +25,229 @@ class SteamPatcherApp:
1525
def __init__(self, root):
1626
self.root = root
1727
self.root.title("Steam Lua Patcher")
18-
self.root.geometry("600x500")
19-
20-
# Style
21-
style = ttk.Style()
22-
style.theme_use('clam')
28+
self.root.geometry("800x600")
2329

2430
# UI Elements
2531
self.create_widgets()
2632

2733
# Data
2834
self.search_results = []
35+
self.debounce_timer = None
36+
self.current_search_id = 0
2937

3038
def create_widgets(self):
31-
# Search Frame
32-
search_frame = ttk.LabelFrame(self.root, text="Search Game", padding=(10, 10))
33-
search_frame.pack(fill="x", padx=10, pady=5)
39+
# Main Container with padding
40+
main_container = ttk.Frame(self.root, padding=20)
41+
main_container.pack(fill=BOTH, expand=True)
42+
43+
# Header
44+
header_frame = ttk.Frame(main_container)
45+
header_frame.pack(fill=X, pady=(0, 20))
46+
47+
title_lbl = ttk.Label(header_frame, text="Steam Lua Patcher", font=("Helvetica", 24, "bold"), bootstyle="primary")
48+
title_lbl.pack(side=LEFT)
49+
50+
# Search Section
51+
search_frame = ttk.Labelframe(main_container, text="Game Search", padding=15, bootstyle="info")
52+
search_frame.pack(fill=X, pady=(0, 20))
3453

3554
self.search_var = tk.StringVar()
3655
self.search_var.trace_add("write", self.on_search_change)
37-
self.search_entry = ttk.Entry(search_frame, textvariable=self.search_var)
38-
self.search_entry.pack(side="left", fill="x", expand=True, padx=5)
39-
# self.search_entry.bind("<Return>", lambda e: self.start_search()) # Enter key still works but not strictly needed with auto-search
4056

41-
search_btn = ttk.Button(search_frame, text="Search", command=self.start_search)
42-
search_btn.pack(side="right", padx=5)
57+
entry_frame = ttk.Frame(search_frame)
58+
entry_frame.pack(fill=X)
4359

44-
# Debounce timer
45-
self.debounce_timer = None
60+
search_icon_lbl = ttk.Label(entry_frame, text="🔍", font=("Segoe UI Emoji", 12))
61+
search_icon_lbl.pack(side=LEFT, padx=(0, 10))
62+
63+
self.search_entry = ttk.Entry(entry_frame, textvariable=self.search_var, font=("Helvetica", 11))
64+
self.search_entry.pack(side=LEFT, fill=X, expand=True)
65+
self.search_entry.focus_set()
4666

47-
# Results Frame
48-
results_frame = ttk.LabelFrame(self.root, text="Results", padding=(10, 10))
49-
results_frame.pack(fill="both", expand=True, padx=10, pady=5)
67+
# Results Section
68+
results_frame = ttk.Labelframe(main_container, text="Search Results", padding=15, bootstyle="default")
69+
results_frame.pack(fill=BOTH, expand=True, pady=(0, 20))
5070

5171
columns = ("name", "appid", "status")
52-
self.tree = ttk.Treeview(results_frame, columns=columns, show="headings", selectmode="browse")
72+
self.tree = ttk.Treeview(results_frame, columns=columns, show="headings", selectmode="browse", bootstyle="info")
73+
5374
self.tree.heading("name", text="Game Name")
5475
self.tree.heading("appid", text="App ID")
55-
self.tree.heading("status", text="Lua File Status")
56-
self.tree.column("name", width=300)
57-
self.tree.column("appid", width=100)
58-
self.tree.column("status", width=120)
59-
self.tree.pack(fill="both", expand=True, side="left")
76+
self.tree.heading("status", text="Lua Patch Status")
77+
78+
self.tree.column("name", width=400, anchor="w")
79+
self.tree.column("appid", width=100, anchor="center")
80+
self.tree.column("status", width=150, anchor="center")
6081

82+
# Scrollbar
6183
scrollbar = ttk.Scrollbar(results_frame, orient="vertical", command=self.tree.yview)
62-
scrollbar.pack(side="right", fill="y")
84+
scrollbar.pack(side=RIGHT, fill=Y)
6385
self.tree.configure(yscrollcommand=scrollbar.set)
6486

65-
# self.tree.bind("<<TreeviewSelect>>", self.on_select)
87+
self.tree.pack(fill=BOTH, expand=True, side=LEFT)
88+
self.tree.bind("<<TreeviewSelect>>", self.on_select)
6689

67-
# Actions Frame
68-
actions_frame = ttk.Frame(self.root, padding=(10, 10))
69-
actions_frame.pack(fill="x", padx=10, pady=5)
90+
# Actions Section
91+
actions_frame = ttk.Frame(main_container)
92+
actions_frame.pack(fill=X)
7093

71-
self.patch_btn = ttk.Button(actions_frame, text="Patch (Copy Lua)", command=self.patch_selected)
72-
self.patch_btn.pack(side="left", padx=5)
94+
self.patch_btn = ttk.Button(actions_frame, text="Patch Selected Game", command=self.patch_selected, state="disabled", bootstyle="success-outline", width=25)
95+
self.patch_btn.pack(side=LEFT, padx=(0, 10))
7396

74-
self.restart_btn = ttk.Button(actions_frame, text="Restart Steam", command=self.restart_steam)
75-
self.restart_btn.pack(side="right", padx=5)
76-
77-
# Status Bar
78-
self.status_var = tk.StringVar(value="Ready")
79-
status_bar = ttk.Label(self.root, textvariable=self.status_var, relief="sunken", anchor="w")
80-
status_bar.pack(fill="x", side="bottom")
97+
self.restart_btn = ttk.Button(actions_frame, text="Restart Steam", command=self.restart_steam, bootstyle="danger-outline", width=20)
98+
self.restart_btn.pack(side=RIGHT)
99+
100+
# Footer / Status
101+
self.status_var = tk.StringVar(value="Ready to search")
102+
status_lbl = ttk.Label(self.root, textvariable=self.status_var, relief="sunken", anchor="w", padding=(10, 5), bootstyle="secondary-inverse")
103+
status_lbl.pack(fill=X, side=BOTTOM)
81104

82105
def on_search_change(self, *args):
83106
if self.debounce_timer:
84107
self.root.after_cancel(self.debounce_timer)
85-
self.debounce_timer = self.root.after(600, self.start_search)
108+
self.debounce_timer = self.root.after(400, self.start_search) # Reduced debounce to 400ms
86109

87110
def start_search(self):
88111
query = self.search_var.get().strip()
89112
if not query:
90113
return
91114

92-
# Debounce cleanup if called manually
93115
if self.debounce_timer:
94116
self.root.after_cancel(self.debounce_timer)
95117
self.debounce_timer = None
118+
119+
self.status_var.set(f"Searching for '{query}'...")
96120

97-
self.patch_btn.config(state="disabled")
98-
self.status_var.set("Searching...")
99-
self.tree.delete(*self.tree.get_children())
121+
# Increment search ID to handle race conditions
122+
self.current_search_id += 1
123+
search_id = self.current_search_id
124+
125+
# Clear previous results immediately if new search starts?
126+
# Optional: keeping old results until new ones arrive looks smoother.
127+
# self.tree.delete(*self.tree.get_children())
100128

101-
# Run in thread to not freeze UI
102-
threading.Thread(target=self.search_logic, args=(query,), daemon=True).start()
129+
threading.Thread(target=self.search_logic, args=(query, search_id), daemon=True).start()
103130

104-
def search_logic(self, query):
131+
def search_logic(self, query, search_id):
105132
try:
106133
url = "https://store.steampowered.com/api/storesearch"
107134
params = {
108135
"term": query,
109136
"l": "english",
110137
"cc": "US"
111138
}
112-
response = requests.get(url, params=params)
139+
response = requests.get(url, params=params, timeout=10)
113140
response.raise_for_status()
114141
data = response.json()
115142

116143
items = data.get("items", [])
117-
self.root.after(0, self.update_results, items)
118144

145+
# Check if this thread is still relevant
146+
if search_id == self.current_search_id:
147+
self.root.after(0, self.update_results, items)
148+
else:
149+
print(f"Ignoring stale search result (ID: {search_id})")
150+
151+
except requests.RequestException as e:
152+
if search_id == self.current_search_id:
153+
self.root.after(0, lambda: self.status_var.set(f"Network Error: {e}"))
119154
except Exception as e:
120-
self.root.after(0, lambda: self.status_var.set(f"Error: {e}"))
155+
if search_id == self.current_search_id:
156+
self.root.after(0, lambda: self.status_var.set(f"Error: {e}"))
121157

122158
def update_results(self, items):
159+
self.tree.delete(*self.tree.get_children())
123160
self.search_results = items
161+
162+
if not items:
163+
self.status_var.set("No results found.")
164+
return
165+
124166
for item in items:
125167
name = item.get("name")
126168
appid = item.get("id")
127169

128170
# Check if lua file exists
129171
lua_path = os.path.join(LUA_FILES_DIR, f"{appid}.lua")
130-
status = "Found" if os.path.exists(lua_path) else "Not Found"
131-
132-
self.tree.insert("", "end", values=(name, appid, status))
172+
exists = os.path.exists(lua_path)
173+
status = "AVAILABLE" if exists else "Missing"
133174

175+
# Insert with tags for coloring
176+
# We need to map boolean to a tag if using ttkbootstrap specific row colors,
177+
# but simpler to just use text for now or configure tags.
178+
self.tree.insert("", "end", values=(name, appid, status), tags=("found" if exists else "missing",))
179+
180+
# Configure tag colors (if standard ttk, bootstyle handles defaults differently)
181+
# self.tree.tag_configure("found", foreground="green") # bootstyle might override
182+
134183
self.status_var.set(f"Found {len(items)} results.")
135-
self.patch_btn.config(state="normal")
184+
185+
def on_select(self, event):
186+
selected = self.tree.selection()
187+
if selected:
188+
item_values = self.tree.item(selected[0])['values']
189+
status = item_values[2] # "AVAILABLE" or "Missing"
190+
if status == "AVAILABLE":
191+
self.patch_btn.config(state="normal")
192+
self.status_var.set(f"Selected: {item_values[0]}")
193+
else:
194+
self.patch_btn.config(state="disabled")
195+
self.status_var.set(f"Lua file missing for: {item_values[0]}")
196+
else:
197+
self.patch_btn.config(state="disabled")
136198

137199
def patch_selected(self):
138200
selected = self.tree.selection()
139201
if not selected:
140-
messagebox.showwarning("No Selection", "Please select a game to patch.")
141202
return
142203

143204
item_values = self.tree.item(selected[0])['values']
144205
name = item_values[0]
145206
appid = str(item_values[1])
146-
status = item_values[2]
147207

148-
if status != "Found":
149-
messagebox.showerror("Error", f"Lua file for '{name}' (AppID: {appid}) not found in repository.")
150-
return
151-
152208
src_file = os.path.join(LUA_FILES_DIR, f"{appid}.lua")
153209
dest_file = os.path.join(STEAM_PLUGIN_DIR, f"{appid}.lua")
154210

155211
try:
156-
# Ensure destination dir exists
157212
if not os.path.exists(STEAM_PLUGIN_DIR):
158213
os.makedirs(STEAM_PLUGIN_DIR)
159214

160215
shutil.copy2(src_file, dest_file)
161-
messagebox.showinfo("Success", f"Patched '{name}' successfully!\nCopied to: {dest_file}")
162-
self.status_var.set(f"Patched {name}")
216+
messagebox.showinfo("Success", f"Patched '{name}' successfully!", parent=self.root)
217+
self.status_var.set(f"Successfully patched {name}")
163218
except Exception as e:
164-
messagebox.showerror("Error", f"Failed to copy file: {e}")
219+
messagebox.showerror("Error", f"Failed to copy file: {e}", parent=self.root)
165220

166221
def restart_steam(self):
167-
if not messagebox.askyesno("Confirm Restart", "This will close Steam and all running games. Continue?"):
222+
if not messagebox.askyesno("Confirm Restart", "This will close Steam and all running games. Continue?", parent=self.root):
168223
return
169224

170225
self.status_var.set("Restarting Steam...")
171226

172227
def restart_thread():
173228
try:
174-
# Kill Steam
229+
# Taskkill is reliable
175230
subprocess.run("taskkill /F /IM steam.exe", shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
176-
177-
# Wait a bit
178-
import time
179231
time.sleep(2)
180232

181-
# Start Steam
182233
if os.path.exists(STEAM_EXE_PATH):
183234
subprocess.Popen([STEAM_EXE_PATH])
184235
self.root.after(0, lambda: self.status_var.set("Steam restarted."))
185236
else:
186-
# Try protocol handler
187237
subprocess.run("start steam://open/main", shell=True)
188238
self.root.after(0, lambda: self.status_var.set("Steam restart command sent."))
189239

190240
except Exception as e:
191-
self.root.after(0, lambda: messagebox.showerror("Error", f"Failed to restart Steam: {e}"))
241+
self.root.after(0, lambda: messagebox.showerror("Error", f"Failed to restart Steam: {e}", parent=self.root))
192242

193243
threading.Thread(target=restart_thread, daemon=True).start()
194244

195245
if __name__ == "__main__":
196-
# Check dependencies check
246+
# Theme setup
197247
try:
198-
import requests
199-
except ImportError:
200-
messagebox.showerror("Error", "Missing 'requests' library. Please run 'pip install requests'")
201-
sys.exit(1)
202-
203-
root = tk.Tk()
248+
root = ttk.Window(themename="darkly") # modern dark theme
249+
except NameError:
250+
root = tk.Tk()
251+
204252
app = SteamPatcherApp(root)
205253
root.mainloop()

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
requests
2+
ttkbootstrap

0 commit comments

Comments
 (0)