From 515773273272606695f1a0e2507c390c4887bc4a Mon Sep 17 00:00:00 2001 From: Benny <83777519+bennyscripts@users.noreply.github.com> Date: Tue, 20 Jan 2026 10:41:08 +0000 Subject: [PATCH 01/37] Add custom titlebar with custom window controls --- gui/components/titlebar.py | 119 ++++++++++++++++++++++++++++++------- gui/helpers/layout.py | 6 +- gui/main.py | 25 ++++++-- 3 files changed, 123 insertions(+), 27 deletions(-) diff --git a/gui/components/titlebar.py b/gui/components/titlebar.py index 2568279..9a682ed 100644 --- a/gui/components/titlebar.py +++ b/gui/components/titlebar.py @@ -1,40 +1,117 @@ +import sys import ttkbootstrap as ttk +from gui.components import RoundedFrame class Titlebar: def __init__(self, root): self.root = root self._offset_x = 0 self._offset_y = 0 + self._dragging = False + self.bg_color = "#171616" def _on_press(self, event): + self._dragging = True self._offset_x = event.x_root - self.root.winfo_x() self._offset_y = event.y_root - self.root.winfo_y() - - event.widget.grab_set() - - self.root.bind("", self._move_window) - self.root.bind("", self._on_release) - def _move_window(self, event): + def _on_motion(self, event): + if not self._dragging: + return + x = event.x_root - self._offset_x y = event.y_root - self._offset_y self.root.geometry(f"+{x}+{y}") def _on_release(self, event): - self.root.unbind("") - self.root.unbind("") - self.root.grab_release() - - def draw(self): - titlebar = ttk.Frame(self.root, style="dark.TFrame") - inner_wrapper = ttk.Frame(titlebar, style="dark.TFrame") - inner_wrapper.pack(fill=ttk.BOTH, expand=True, pady=5, padx=10) - - titlebar.bind("", self._on_press) - inner_wrapper.bind("", self._on_press) + self._dragging = False + + def _reset_hover_state(self): + x, y = self.root.winfo_pointerxy() + self.root.event_generate("", warp=True, x=x+1, y=y) + self.root.event_generate("", warp=True, x=x, y=y) + + def _minimize(self): + self.root.withdraw() + self.root.bind("", self._restore_once, add="+") + + def _restore_once(self, event=None): + self.root.unbind("") + self.root.deiconify() + + self.root.after(10, lambda: self.root.overrideredirect(True)) + self.root.after(20, self._reset_hover_state) + + def _maximize(self): + screen_height = self.root.winfo_screenheight() + screen_width = self.root.winfo_screenwidth() - title = ttk.Label(inner_wrapper, text="Ghost") - title.configure(background=self.root.style.colors.get("dark")) - title.pack(side=ttk.LEFT) + window_geometry = self.root.winfo_geometry() + window_size = window_geometry.split("+")[0] + window_width = int(window_size.split("x")[0]) + window_height = int(window_size.split("x")[1]) - return titlebar \ No newline at end of file + if window_width > self.root.size[0] or window_height > self.root.size[1]: + self.root.geometry(f"{self.root.size[0]}x{self.root.size[1]}") + self.root.update_idletasks() + self.root.after(10, lambda: self.root.overrideredirect(True)) + self.root.after(20, lambda: self.root.geometry(f"+{(screen_width - self.root.size[0]) // 2}+{(screen_height - self.root.size[1]) // 2}")) + else: + self.root.geometry(f"{screen_width}x{screen_height - 40}+0+0") + self.root.update_idletasks() + self.root.after(10, lambda: self.root.overrideredirect(True)) + + def draw(self): + titlebar = RoundedFrame( + self.root, + radius=(25, 25, 0, 0), + background=self.bg_color + ) + + inner_wrapper = RoundedFrame(titlebar, background=self.bg_color, radius=0) + inner_wrapper.pack(fill=ttk.BOTH, expand=True, pady=0, padx=8) + + # Bind to all titlebar surfaces + for widget in (titlebar, inner_wrapper): + widget.bind("", self._on_press) + widget.bind("", self._on_motion) + widget.bind("", self._on_release) + + if sys.platform == "darwin": + close_btn = ttk.Label(inner_wrapper, text="●", foreground="#FF5F57", font=("Arial", 25)) + close_btn.configure(background=self.bg_color) + close_btn.pack(side=ttk.LEFT, padx=(0, 0)) + close_btn.bind("", lambda e: self.root.quit()) + close_btn.bind("", lambda e: close_btn.configure(foreground="#CC4940")) + close_btn.bind("", lambda e: close_btn.configure(foreground="#FF5F57")) + + minimize_btn = ttk.Label(inner_wrapper, text="●", foreground="#FFBD2E", font=("Arial", 25)) + minimize_btn.configure(background=self.bg_color) + minimize_btn.pack(side=ttk.LEFT, padx=(0, 0)) + minimize_btn.bind("", lambda e: self._minimize()) + minimize_btn.bind("", lambda e: minimize_btn.configure(foreground="#CC9A26")) + minimize_btn.bind("", lambda e: minimize_btn.configure(foreground="#FFBD2E")) + + maximize_btn = ttk.Label(inner_wrapper, text="●", foreground="#28C940", font=("Arial", 25)) + maximize_btn.configure(background=self.bg_color) + maximize_btn.pack(side=ttk.LEFT, padx=(0, 5)) + maximize_btn.bind("", lambda e: maximize_btn.configure(foreground="#20A833")) + maximize_btn.bind("", lambda e: maximize_btn.configure(foreground="#28C940")) + maximize_btn.bind("", lambda e: self._maximize()) + + else: + title = ttk.Label(inner_wrapper, text="Ghost") + title.configure(background=self.bg_color) + title.pack(side=ttk.LEFT, padx=(5, 0)) + + close_btn = ttk.Label(inner_wrapper, text="✕") + close_btn.configure(background=self.bg_color) + close_btn.pack(side=ttk.RIGHT, padx=(0, 5)) + close_btn.bind("", lambda e: self.root.quit()) + + minimize_btn = ttk.Label(inner_wrapper, text="—") + minimize_btn.configure(background=self.bg_color) + minimize_btn.pack(side=ttk.RIGHT, padx=(0, 7)) + minimize_btn.bind("", lambda e: self._minimize()) + + return titlebar diff --git a/gui/helpers/layout.py b/gui/helpers/layout.py index 5254ae1..7c2210d 100644 --- a/gui/helpers/layout.py +++ b/gui/helpers/layout.py @@ -18,11 +18,12 @@ def center_window(root, width, height): root.focus_force() class Layout: - def __init__(self, root, sidebar): + def __init__(self, root, sidebar, titlebar): self.root = root self.width = root.winfo_width() self.height = root.winfo_height() self.sidebar = sidebar + self.titlebar = titlebar def main(self, scrollable=False, padx=25, pady=25): width = self.width - (self.width // 100) @@ -49,6 +50,9 @@ def clear(self): if isinstance(widget, ttk.Frame): widget.destroy() + titlebar = self.titlebar.draw() + titlebar.pack(fill=ttk.X, side=ttk.TOP) + sidebar = self.sidebar.draw() sidebar.pack(side=ttk.LEFT, fill=ttk.BOTH) diff --git a/gui/main.py b/gui/main.py index df5f80f..51ebacd 100644 --- a/gui/main.py +++ b/gui/main.py @@ -13,17 +13,18 @@ from utils import uninstall_fonts from gui.pages import HomePage, LoadingPage, SettingsPage, OnboardingPage, ScriptsPage, ToolsPage -from gui.components import Sidebar, Console +from gui.components import Sidebar, Console, Titlebar from gui.helpers import Images, Layout class GhostGUI: def __init__(self, bot_controller): - self.size = (600, 530) + self.size = (650, 530) self.bot_controller = bot_controller enable_high_dpi_awareness() self.root = ttk.tk.Tk() + self.root.size = self.size self.root.title("Ghost") # self.root.resizable(False, False) if os.name == "nt": @@ -45,23 +46,30 @@ def __init__(self, bot_controller): self.root.style.configure("TLabel", font=("Host Grotesk",)) self.root.style.configure("TButton", font=("Host Grotesk",)) + self.root.overrideredirect(True) + self.root.attributes("-transparent", True) + self.root.configure(bg="systemTransparent") + + self.root.focus() + self.cfg = Config() self.notifier = Notifier() self.images = Images() self.sidebar = Sidebar(self.root) self.sidebar.add_button("home", self.draw_home) - self.sidebar.add_button("console", self.draw_console) + # self.sidebar.add_button("console", self.draw_console) self.sidebar.add_button("settings", self.draw_settings) self.sidebar.add_button("scripts", self.draw_scripts) self.sidebar.add_button("tools", self.draw_tools) self.sidebar.add_button("logout", self.quit) - self.layout = Layout(self.root, self.sidebar) + self.titlebar = Titlebar(self.root) + self.layout = Layout(self.root, self.sidebar, self.titlebar) self.loading_page = LoadingPage(self.root) self.onboarding_page = OnboardingPage(self.root, self.run, self.bot_controller) self.console = Console(self.root, self.bot_controller) - self.home_page = HomePage(self.root, self.bot_controller, self._restart_bot) + self.home_page = HomePage(self.root, self.bot_controller, self._restart_bot, self.console) self.settings_page = SettingsPage(self.root, self.bot_controller) self.scripts_page = ScriptsPage(self, self.bot_controller, self.images) self.tools_page = ToolsPage(self.root, self.bot_controller, self.images, self.layout) @@ -70,6 +78,13 @@ def __init__(self, bot_controller): if bot_controller: self.bot_controller.set_gui(self) + + self.root.overrideredirect(False) + self.layout.center_window(self.size[0], self.size[1]) + self.root.update_idletasks() + + # When restored, remove decorations again + self.root.after(10, lambda: self.root.overrideredirect(True)) def _show_window(self): self.root.deiconify() From dfe219c47cec9f07e7c551fe1cf42008d2e6d67e Mon Sep 17 00:00:00 2001 From: Benny <83777519+bennyscripts@users.noreply.github.com> Date: Tue, 20 Jan 2026 10:42:04 +0000 Subject: [PATCH 02/37] Move console into home page and remove discord and ghost details cards --- gui/components/sidebar.py | 2 +- gui/main.py | 12 ++++++------ gui/pages/home.py | 17 ++++++++++------- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/gui/components/sidebar.py b/gui/components/sidebar.py index 22edcc6..9a97041 100644 --- a/gui/components/sidebar.py +++ b/gui/components/sidebar.py @@ -73,7 +73,7 @@ def draw(self): self.buttons = { "home": self._create_button(self.images.get("home"), "home", self.button_cmds["home"], 0), - "console": self._create_button(self.images.get("console"), "console", self.button_cmds["console"], 1), + # "console": self._create_button(self.images.get("console"), "console", self.button_cmds["console"], 1), "settings": self._create_button(self.images.get("settings"), "settings", self.button_cmds["settings"], 2), "scripts": self._create_button(self.images.get("scripts"), "scripts", self.button_cmds["scripts"], 3), "tools": self._create_button(self.images.get("tools"), "tools", self.button_cmds["tools"], 4), diff --git a/gui/main.py b/gui/main.py index 51ebacd..e3ae8a3 100644 --- a/gui/main.py +++ b/gui/main.py @@ -95,11 +95,11 @@ def draw_home(self, restart=False, start=False): main = self.layout.main() self.home_page.draw(main, restart=restart, start=start) - def draw_console(self): - self.sidebar.set_current_page("console") - self.layout.clear() - main = self.layout.main() - self.console.draw(main) + # def draw_console(self): + # self.sidebar.set_current_page("console") + # self.layout.clear() + # main = self.layout.main() + # self.console.draw(main) def draw_settings(self): self.sidebar.set_current_page("settings") @@ -110,7 +110,7 @@ def draw_settings(self): def draw_scripts(self): self.sidebar.set_current_page("scripts") self.layout.clear() - main = self.layout.main(padx=(23, 24)) + main = self.layout.main() self.scripts_page.draw(main) def draw_tools(self): diff --git a/gui/pages/home.py b/gui/pages/home.py index 4109f6d..1042109 100644 --- a/gui/pages/home.py +++ b/gui/pages/home.py @@ -7,10 +7,11 @@ from utils.config import VERSION, CHANGELOG, MOTD, Config class HomePage: - def __init__(self, root, bot_controller, _restart_bot): + def __init__(self, root, bot_controller, _restart_bot, console): self.root = root self.bot_controller = bot_controller self._restart_bot = _restart_bot + self.console = console self.width = root.winfo_width() self.height = root.winfo_height() self.restart = False @@ -90,7 +91,7 @@ def _hover_leave(_): def _draw_header(self, parent): wrapper = RoundedFrame(parent, radius=(15, 15, 15, 15), bootstyle="secondary.TFrame") - wrapper.pack(fill=ttk.BOTH, expand=False) + wrapper.pack(fill=ttk.BOTH, expand=False, pady=(0, 10)) if self.avatar and not self.restart: avatar = ttk.Label(wrapper, image=self.avatar) @@ -189,9 +190,11 @@ def draw(self, parent, restart=False, start=False): self.avatar = self.bot_controller.get_avatar() self._draw_header(parent) - self.details_wrapper = self._draw_details_wrapper(parent) - self._draw_account_details(self.details_wrapper) - self._draw_bot_details(self.details_wrapper) + # self.details_wrapper = self._draw_details_wrapper(parent) + # self._draw_account_details(self.details_wrapper) + # self._draw_bot_details(self.details_wrapper) - self._update_bot_details() - self._update_account_details() \ No newline at end of file + # self._update_bot_details() + # self._update_account_details() + + self.console.draw(parent) \ No newline at end of file From 1f2c55f26416bacd563c5c633e29ba50e55381b4 Mon Sep 17 00:00:00 2001 From: Benny <83777519+bennyscripts@users.noreply.github.com> Date: Tue, 20 Jan 2026 10:43:54 +0000 Subject: [PATCH 03/37] Make dark colours darker and remove dedicated quit button in the sidebar --- data/gui_theme.json | 4 +- gui/components/rounded_button.py | 2 +- gui/components/sidebar.py | 44 +++++++++++++++------ gui/helpers/layout.py | 65 ++++++++++++++++++++++++++------ 4 files changed, 90 insertions(+), 25 deletions(-) diff --git a/data/gui_theme.json b/data/gui_theme.json index 5f8fc60..bc1e53b 100644 --- a/data/gui_theme.json +++ b/data/gui_theme.json @@ -5,13 +5,13 @@ "type": "dark", "colors": { "primary": "#433dfb", - "secondary": "#222324", + "secondary": "#1e1d1d", "success": "#0abf34", "info": "#2b6eff", "warning": "#f39c12", "danger": "#ff341f", "light": "#ADB5BD", - "dark": "#1a1c1c", + "dark": "#171616", "bg": "#121111", "fg": "#ffffff", "selectbg": "#555555", diff --git a/gui/components/rounded_button.py b/gui/components/rounded_button.py index 1f05e8f..08b74ff 100644 --- a/gui/components/rounded_button.py +++ b/gui/components/rounded_button.py @@ -73,4 +73,4 @@ def _hover_enter(self, event=None): def _hover_leave(self, event=None): """ Reset to original color """ self.frame.set_background(self.original_bg) - self.button.configure(background=self.original_bg) + self.button.configure(background=self.original_bg) \ No newline at end of file diff --git a/gui/components/sidebar.py b/gui/components/sidebar.py index 9a97041..fcd6b02 100644 --- a/gui/components/sidebar.py +++ b/gui/components/sidebar.py @@ -2,6 +2,7 @@ import ttkbootstrap as ttk from ttkbootstrap.dialogs import Messagebox from gui.helpers import Images +from gui.components import RoundedFrame, RoundedButton class Sidebar: def __init__(self, root): @@ -11,6 +12,7 @@ def __init__(self, root): self.button_cmds = {} self.tk_buttons = [] self.sidebar = None + self.bg_color = "#171616" root_width = 600 self.width = root_width // (root_width // 65) @@ -21,23 +23,40 @@ def add_button(self, page_name, command): def set_current_page(self, page_name): self.current_page = page_name - def _hover_enter(self, button, page_name): + def _hover_enter(self, button_wrapper, button, page_name): background = "#242424" if self.current_page != page_name else self.root.style.colors.get("secondary") button.configure(background=background) + button_wrapper.set_background(background) - def _hover_leave(self, button, page_name): - background = self.root.style.colors.get("dark") if self.current_page != page_name else self.root.style.colors.get("secondary") + def _hover_leave(self, button_wrapper, button, page_name): + background = self.bg_color if self.current_page != page_name else self.root.style.colors.get("secondary") button.configure(background=background) + button_wrapper.set_background(background) def _create_button(self, image, page_name, command, row): is_selected = self.current_page == page_name - bg_color = self.root.style.colors.get("secondary") if is_selected else self.root.style.colors.get("dark") + bg_color = self.root.style.colors.get("secondary") if is_selected else self.bg_color + + button_wrapper = RoundedFrame( + self.sidebar, + radius=15, + background=bg_color + ) + + button_wrapper.grid(row=row, column=0, sticky=ttk.NSEW, pady=(10, 2) if row == 0 else 2, ipady=8, padx=10) + + button = ttk.Label(button_wrapper, image=image, anchor="center", background=bg_color) + + button_wrapper.bind("", lambda e: self._update_page(command, page_name)) + button_wrapper.bind("", lambda e: self._hover_enter(button_wrapper, button, page_name)) + button_wrapper.bind("", lambda e: self._hover_leave(button_wrapper, button, page_name)) - button = ttk.Label(self.sidebar, image=image, background=bg_color, anchor="center") button.bind("", lambda e: self._update_page(command, page_name)) - button.bind("", lambda e: self._hover_enter(button, page_name)) - button.bind("", lambda e: self._hover_leave(button, page_name)) - button.grid(row=row, column=0, sticky=ttk.NSEW, pady=(10, 2) if row == 0 else 2, ipady=12) + button.bind("", lambda e: self._hover_enter(button_wrapper, button, page_name)) + button.bind("", lambda e: self._hover_leave(button_wrapper, button, page_name)) + + # button.grid(row=row, column=0, sticky=ttk.NSEW, pady=(10, 2) if row == 0 else 2, ipady=12) + button.pack(fill=ttk.BOTH, expand=True, padx=5, pady=5) self.tk_buttons.append(button) @@ -67,7 +86,10 @@ def disable(self): button.unbind("") def draw(self): - self.sidebar = ttk.Frame(self.root, width=self.width, height=self.root.winfo_height(), style="dark.TFrame") + # self.sidebar = ttk.Frame(self.root, width=self.width, height=self.root.winfo_height(), style="dark.TFrame") + self.sidebar = RoundedFrame(self.root, radius=(0, 0, 0, 25), background=self.bg_color) + self.sidebar.set_height(self.root.winfo_height()) + self.sidebar.set_width(self.width + 7) # self.sidebar.pack(side=ttk.LEFT, fill=ttk.BOTH) self.sidebar.grid_propagate(False) @@ -79,11 +101,11 @@ def draw(self): "tools": self._create_button(self.images.get("tools"), "tools", self.button_cmds["tools"], 4), } - logout_btn = ttk.Label(self.sidebar, image=self.images.get("logout"), background=self.root.style.colors.get("dark"), anchor="center") + logout_btn = ttk.Label(self.sidebar, image=self.images.get("logout"), background=self.bg_color, anchor="center") logout_btn.bind("", lambda e: self._quit()) logout_btn.bind("", lambda e: self._hover_enter(logout_btn, "logout")) logout_btn.bind("", lambda e: self._hover_leave(logout_btn, "logout")) - logout_btn.grid(row=len(self.buttons) + 2, column=0, sticky=ttk.NSEW, pady=10, ipady=12) + # logout_btn.grid(row=len(self.buttons) + 2, column=0, sticky=ttk.NSEW, pady=10, ipady=12) self.sidebar.grid_rowconfigure(len(self.buttons) + 1, weight=1) self.sidebar.grid_columnconfigure(0, weight=1) diff --git a/gui/helpers/layout.py b/gui/helpers/layout.py index 7c2210d..2e20587 100644 --- a/gui/helpers/layout.py +++ b/gui/helpers/layout.py @@ -1,6 +1,7 @@ import sys import ttkbootstrap as ttk from ttkbootstrap.scrolled import ScrolledFrame +from gui.components import RoundedFrame def resize(root, width, height): root.minsize(width, height) @@ -24,22 +25,64 @@ def __init__(self, root, sidebar, titlebar): self.height = root.winfo_height() self.sidebar = sidebar self.titlebar = titlebar + self.border_color = "#171616" - def main(self, scrollable=False, padx=25, pady=25): + def main(self, scrollable=False, padx=10, pady=10): width = self.width - (self.width // 100) - wrapper = self.root - + + border = RoundedFrame( + self.root, + radius=(0, 0, 25, 0), # ALL corners here + background=self.border_color + ) + border.pack(fill=ttk.BOTH, expand=True) + + outer = RoundedFrame( + border, + radius=(25, 25, 25, 25), # only internal shaping + background=self.root.style.colors.get("bg") + ) + outer.pack( + fill=ttk.BOTH, + expand=True, + padx=(0, 8), + pady=(0, 8) + ) + + # SAFE ZONE: keeps native widgets away from rounded corners + safe = ttk.Frame( + outer, + style="TFrame" + ) + safe.pack(fill=ttk.BOTH, expand=True, padx=15, pady=15) + + # INNER: scrolling container (optional) if scrollable: - wrapper = ScrolledFrame(self.root, width=width, height=self.height) - wrapper.pack(fill=ttk.BOTH, expand=True) - - # main = ttk.Frame(wrapper) - # main.pack(fill=ttk.BOTH, expand=True, padx=23, pady=23) + inner = ScrolledFrame( + safe, + width=width, + height=self.height + ) + inner.pack(fill=ttk.BOTH, expand=True) + content_parent = inner + else: + content_parent = safe - main = ttk.Frame(wrapper, width=width, height=self.height) - main.pack(fill=ttk.BOTH, expand=True, padx=(23, 32) if scrollable else padx, pady=23 if scrollable else pady) + # CONTENT FRAME + main = ttk.Frame( + content_parent, + width=width, + height=self.height + ) + main.pack( + fill=ttk.BOTH, + expand=True, + padx=(8, 22) if scrollable else padx, + pady=8 if scrollable else pady + ) return main + def clear_everything(self): for widget in self.root.winfo_children(): @@ -47,7 +90,7 @@ def clear_everything(self): def clear(self): for widget in self.root.winfo_children(): - if isinstance(widget, ttk.Frame): + if isinstance(widget, ttk.Frame) or isinstance(widget, ScrolledFrame) or isinstance(widget, ttk.Canvas) or isinstance(widget, RoundedFrame): widget.destroy() titlebar = self.titlebar.draw() From c4a44f4e7c846e89b149720f67a1f77e9869e097 Mon Sep 17 00:00:00 2001 From: Benny <83777519+bennyscripts@users.noreply.github.com> Date: Tue, 20 Jan 2026 10:44:02 +0000 Subject: [PATCH 04/37] Fix padding issues in scripts page --- gui/pages/scripts.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gui/pages/scripts.py b/gui/pages/scripts.py index b48ce5c..4848ee6 100644 --- a/gui/pages/scripts.py +++ b/gui/pages/scripts.py @@ -60,7 +60,7 @@ def _new_scripts_listener(self): if current_scripts != previous_scripts: try: if not self.restart_warning.winfo_ismapped(): - self.restart_warning.pack(fill=ttk.X, padx=2, side=ttk.TOP, pady=(10, 0)) + self.restart_warning.pack(fill=ttk.X, side=ttk.TOP, pady=(10, 0)) except: pass else: @@ -314,11 +314,11 @@ def _draw_restart_warning(self, parent): def draw(self, parent): self.restart_warning = self._draw_restart_warning(parent) - self.restart_warning.pack(fill=ttk.X, padx=2, side=ttk.TOP, pady=(10, 0)) + self.restart_warning.pack(fill=ttk.X, side=ttk.TOP, pady=(10, 0)) self.restart_warning.pack_forget() header = self._draw_header(parent) - header.pack(fill=ttk.X, padx=2) + header.pack(fill=ttk.X) ttk.Separator(parent, orient="horizontal").pack(fill=ttk.X, pady=(20, 16), padx=4) From 589f231778ab28b3ea77eb31937af032f624438f Mon Sep 17 00:00:00 2001 From: Benny <83777519+bennyscripts@users.noreply.github.com> Date: Tue, 20 Jan 2026 10:54:46 +0000 Subject: [PATCH 05/37] Disable maximise button --- gui/components/titlebar.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/gui/components/titlebar.py b/gui/components/titlebar.py index 9a682ed..cc63e0f 100644 --- a/gui/components/titlebar.py +++ b/gui/components/titlebar.py @@ -92,12 +92,14 @@ def draw(self): minimize_btn.bind("", lambda e: minimize_btn.configure(foreground="#CC9A26")) minimize_btn.bind("", lambda e: minimize_btn.configure(foreground="#FFBD2E")) - maximize_btn = ttk.Label(inner_wrapper, text="●", foreground="#28C940", font=("Arial", 25)) + maximize_btn = ttk.Label(inner_wrapper, text="●", foreground="#4f4c4c", font=("Arial", 25)) maximize_btn.configure(background=self.bg_color) maximize_btn.pack(side=ttk.LEFT, padx=(0, 5)) - maximize_btn.bind("", lambda e: maximize_btn.configure(foreground="#20A833")) - maximize_btn.bind("", lambda e: maximize_btn.configure(foreground="#28C940")) - maximize_btn.bind("", lambda e: self._maximize()) + # maximize_btn.bind("", lambda e: maximize_btn.configure(foreground="#20A833")) + # maximize_btn.bind("", lambda e: maximize_btn.configure(foreground="#28C940")) + maximize_btn.bind("", lambda e: maximize_btn.configure(foreground="#3b3a3a")) + maximize_btn.bind("", lambda e: maximize_btn.configure(foreground="#4f4c4c")) + # maximize_btn.bind("", lambda e: self._maximize()) else: title = ttk.Label(inner_wrapper, text="Ghost") From aa2b9ca860b6ee8f08178ab8fb1ace4e9ad34cc5 Mon Sep 17 00:00:00 2001 From: Benny <83777519+bennyscripts@users.noreply.github.com> Date: Tue, 20 Jan 2026 11:31:09 +0000 Subject: [PATCH 06/37] Add resizing - VERY BUGGY --- gui/main.py | 50 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/gui/main.py b/gui/main.py index e3ae8a3..1599bf1 100644 --- a/gui/main.py +++ b/gui/main.py @@ -13,12 +13,14 @@ from utils import uninstall_fonts from gui.pages import HomePage, LoadingPage, SettingsPage, OnboardingPage, ScriptsPage, ToolsPage -from gui.components import Sidebar, Console, Titlebar +from gui.components import Sidebar, Console, Titlebar, RoundedFrame from gui.helpers import Images, Layout class GhostGUI: def __init__(self, bot_controller): + self.resize_grip_size = 10 self.size = (650, 530) + self.border_color = "#171616" self.bot_controller = bot_controller enable_high_dpi_awareness() @@ -86,6 +88,48 @@ def __init__(self, bot_controller): # When restored, remove decorations again self.root.after(10, lambda: self.root.overrideredirect(True)) + def _add_resize_grips(self): + bottom_resize_zone = RoundedFrame(self.root, radius=(0, 0, 25, 25), background=self.border_color) + bottom_resize_zone.set_height(self.resize_grip_size) + bottom_resize_zone.set_width(self.root.winfo_width()) + bottom_resize_zone.place(x=0, y=self.root.winfo_height() - self.resize_grip_size) + ttk.tk.Misc.lift(bottom_resize_zone) + bottom_resize_zone.bind("", self._resize_window) + bottom_resize_zone.bind("", lambda e: self.root.config(cursor="sb_v_double_arrow")) + bottom_resize_zone.bind("", lambda e: self.root.config(cursor="")) + + # top_resize_zone = RoundedFrame(self.root, radius=(25, 25, 0, 0), background=self.border_color) + # top_resize_zone.set_height(self.resize_grip_size // 2) + # top_resize_zone.set_width(self.root.winfo_width()) + # top_resize_zone.place(x=0, y=0) + # ttk.tk.Misc.lift(top_resize_zone) + # top_resize_zone.bind("", self._resize_window) + + # left_resize_zone = RoundedFrame(self.root, radius=(25, 0, 0, 25), background=self.border_color) + # left_resize_zone.set_height(self.root.winfo_height()) + # left_resize_zone.set_width(self.resize_grip_size) + # left_resize_zone.place(x=0, y=0) + # ttk.tk.Misc.lift(left_resize_zone) + # left_resize_zone.bind("", self._resize_window) + + right_resize_zone = RoundedFrame(self.root, radius=(0, 25, 25, 0), background=self.border_color) + right_resize_zone.set_height(self.root.winfo_height()) + right_resize_zone.set_width(self.resize_grip_size) + right_resize_zone.place(x=self.root.winfo_width() - self.resize_grip_size, y=0) + ttk.tk.Misc.lift(right_resize_zone) + right_resize_zone.bind("", self._resize_window) + right_resize_zone.bind("", lambda e: self.root.config(cursor="sb_h_double_arrow")) + right_resize_zone.bind("", lambda e: self.root.config(cursor="")) + + def _resize_window(self, event): + # resize the window based on mouse position, save the new positions so resizing continues smoothly + x = self.root.winfo_pointerx() + y = self.root.winfo_pointery() + self.root.geometry(f"{x - self.root.winfo_x()}x{y - self.root.winfo_y()}") + self.root.update_idletasks() + + self.size = (self.root.winfo_width(), self.root.winfo_height()) + def _show_window(self): self.root.deiconify() @@ -94,6 +138,7 @@ def draw_home(self, restart=False, start=False): self.layout.clear() main = self.layout.main() self.home_page.draw(main, restart=restart, start=start) + self._add_resize_grips() # def draw_console(self): # self.sidebar.set_current_page("console") @@ -106,18 +151,21 @@ def draw_settings(self): self.layout.clear() main = self.layout.main(scrollable=True) self.settings_page.draw(main) + self._add_resize_grips() def draw_scripts(self): self.sidebar.set_current_page("scripts") self.layout.clear() main = self.layout.main() self.scripts_page.draw(main) + self._add_resize_grips() def draw_tools(self): self.sidebar.set_current_page("tools") self.layout.clear() main = self.layout.main(scrollable=True) self.tools_page.draw(main) + self._add_resize_grips() # def draw_loading(self): # self.layout.hide_titlebar() From cd4da364df8fe505c944c4331cc848f5bbc27229 Mon Sep 17 00:00:00 2001 From: bennyscripts Date: Tue, 20 Jan 2026 12:01:23 +0000 Subject: [PATCH 07/37] Change ttkbootstrap version to 1.14.2 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 2a2c056..aac4015 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ pillow pystyle psutil pycryptodome -ttkbootstrap +ttkbootstrap==1.14.2 pyinstaller # git+https://github.com/tomlin7/cupcake.git cupcake-editor From ef4a39e68fb8dca85b4076bf57a2322eb1720289 Mon Sep 17 00:00:00 2001 From: Benny <> Date: Tue, 20 Jan 2026 12:48:51 +0000 Subject: [PATCH 08/37] Fix transparency on Windows --- gui/main.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/gui/main.py b/gui/main.py index 1599bf1..90d07d2 100644 --- a/gui/main.py +++ b/gui/main.py @@ -49,8 +49,12 @@ def __init__(self, bot_controller): self.root.style.configure("TButton", font=("Host Grotesk",)) self.root.overrideredirect(True) - self.root.attributes("-transparent", True) - self.root.configure(bg="systemTransparent") + if sys.platform == "darwin": + self.root.attributes("-transparent", True) + self.root.configure(bg="systemTransparent") + else: + self.root.configure(bg="#ff00ff") + self.root.attributes("-transparentcolor", "#ff00ff") self.root.focus() @@ -66,7 +70,7 @@ def __init__(self, bot_controller): self.sidebar.add_button("tools", self.draw_tools) self.sidebar.add_button("logout", self.quit) - self.titlebar = Titlebar(self.root) + self.titlebar = Titlebar(self.root, self.images) self.layout = Layout(self.root, self.sidebar, self.titlebar) self.loading_page = LoadingPage(self.root) self.onboarding_page = OnboardingPage(self.root, self.run, self.bot_controller) @@ -81,12 +85,13 @@ def __init__(self, bot_controller): if bot_controller: self.bot_controller.set_gui(self) - self.root.overrideredirect(False) - self.layout.center_window(self.size[0], self.size[1]) - self.root.update_idletasks() + if sys.platform == "darwin": + self.root.overrideredirect(False) + self.layout.center_window(self.size[0], self.size[1]) + self.root.update_idletasks() - # When restored, remove decorations again - self.root.after(10, lambda: self.root.overrideredirect(True)) + # When restored, remove decorations again + self.root.after(10, lambda: self.root.overrideredirect(True)) def _add_resize_grips(self): bottom_resize_zone = RoundedFrame(self.root, radius=(0, 0, 25, 25), background=self.border_color) From 52bdf3785eab71eb431d82dc4b5aa9462207959f Mon Sep 17 00:00:00 2001 From: Benny <> Date: Tue, 20 Jan 2026 12:49:36 +0000 Subject: [PATCH 09/37] Fix padding, fonts and added icon to titlebar for windows --- gui/components/titlebar.py | 30 +++++++++++++++++++++++------- gui/helpers/images.py | 3 ++- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/gui/components/titlebar.py b/gui/components/titlebar.py index cc63e0f..92271b6 100644 --- a/gui/components/titlebar.py +++ b/gui/components/titlebar.py @@ -3,8 +3,9 @@ from gui.components import RoundedFrame class Titlebar: - def __init__(self, root): + def __init__(self, root, images): self.root = root + self.images = images self._offset_x = 0 self._offset_y = 0 self._dragging = False @@ -32,8 +33,14 @@ def _reset_hover_state(self): self.root.event_generate("", warp=True, x=x, y=y) def _minimize(self): - self.root.withdraw() - self.root.bind("", self._restore_once, add="+") + if sys.platform == "darwin": + self.root.withdraw() + self.root.bind("", self._restore_once, add="+") + else: + self.root.overrideredirect(False) + self.root.deiconify() + self.root.overrideredirect(True) + self.root.update_idletasks() def _restore_once(self, event=None): self.root.unbind("") @@ -69,7 +76,8 @@ def draw(self): ) inner_wrapper = RoundedFrame(titlebar, background=self.bg_color, radius=0) - inner_wrapper.pack(fill=ttk.BOTH, expand=True, pady=0, padx=8) + padx = 8 + pady = 8 # Bind to all titlebar surfaces for widget in (titlebar, inner_wrapper): @@ -78,6 +86,8 @@ def draw(self): widget.bind("", self._on_release) if sys.platform == "darwin": + pady = 0 + close_btn = ttk.Label(inner_wrapper, text="●", foreground="#FF5F57", font=("Arial", 25)) close_btn.configure(background=self.bg_color) close_btn.pack(side=ttk.LEFT, padx=(0, 0)) @@ -102,18 +112,24 @@ def draw(self): # maximize_btn.bind("", lambda e: self._maximize()) else: - title = ttk.Label(inner_wrapper, text="Ghost") + ico = ttk.Label(inner_wrapper, image=self.images.images["titlebar-ico"]) + ico.configure(background=self.bg_color) + ico.pack(side=ttk.LEFT, padx=(5, 0)) + + title = ttk.Label(inner_wrapper, text="Ghost", font=("Host Grotesk", 10)) title.configure(background=self.bg_color) title.pack(side=ttk.LEFT, padx=(5, 0)) - close_btn = ttk.Label(inner_wrapper, text="✕") + close_btn = ttk.Label(inner_wrapper, text="✕", font=("Host Grotesk", 10)) close_btn.configure(background=self.bg_color) close_btn.pack(side=ttk.RIGHT, padx=(0, 5)) close_btn.bind("", lambda e: self.root.quit()) - minimize_btn = ttk.Label(inner_wrapper, text="—") + minimize_btn = ttk.Label(inner_wrapper, text="—", font=("Host Grotesk", 10)) minimize_btn.configure(background=self.bg_color) minimize_btn.pack(side=ttk.RIGHT, padx=(0, 7)) minimize_btn.bind("", lambda e: self._minimize()) + + inner_wrapper.pack(fill=ttk.BOTH, expand=True, pady=pady, padx=padx) return titlebar diff --git a/gui/helpers/images.py b/gui/helpers/images.py index a2fdc3a..01e1729 100644 --- a/gui/helpers/images.py +++ b/gui/helpers/images.py @@ -73,7 +73,7 @@ def _load_images(self): ICON_CONFIG = { "bigger": ["scripts"], - "small": ["trash", "github", "restart", "checkmark", "left-chevron", "file-signature", "trash-white", "right-chevron"], + "small": ["trash", "github", "restart", "checkmark", "left-chevron", "file-signature", "trash-white", "right-chevron", "titlebar-ico"], "tiny": ["submit", "max", "min", "search"], "smaller": ["folder-open", "plus", "reset", "play", "stop"], "logo": ["ghost-logo"], @@ -109,6 +109,7 @@ def _load_images(self): "reset": "data/icons/rotate-left-solid.png", "play": "data/icons/play-solid.png", "stop": "data/icons/stop-solid.png", + "titlebar-ico": "data/icon-win.png" } for key, path in ICON_PATHS.items(): From 491765d71fd9b4c10c400663dd63393d9046e2af Mon Sep 17 00:00:00 2001 From: bennyscripts <83777519+bennyscripts@users.noreply.github.com> Date: Tue, 20 Jan 2026 13:03:33 +0000 Subject: [PATCH 10/37] Fix transparency on Linux --- gui/main.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gui/main.py b/gui/main.py index 90d07d2..171d4cf 100644 --- a/gui/main.py +++ b/gui/main.py @@ -52,9 +52,11 @@ def __init__(self, bot_controller): if sys.platform == "darwin": self.root.attributes("-transparent", True) self.root.configure(bg="systemTransparent") - else: + elif sys.platform == "win32": self.root.configure(bg="#ff00ff") self.root.attributes("-transparentcolor", "#ff00ff") + else: + self.root.attributes("-alpha", 0) self.root.focus() From 3351c4fed7d7366c976cd3bba194faf61ac0dab3 Mon Sep 17 00:00:00 2001 From: Benny <83777519+bennyscripts@users.noreply.github.com> Date: Tue, 20 Jan 2026 13:29:57 +0000 Subject: [PATCH 11/37] Fixed visual bugs when resizing, resize grips are now made once and only repositioned not recreated --- gui/helpers/layout.py | 6 ++- gui/main.py | 87 +++++++++++++++++++++++++------------------ 2 files changed, 55 insertions(+), 38 deletions(-) diff --git a/gui/helpers/layout.py b/gui/helpers/layout.py index 2e20587..6c1a40f 100644 --- a/gui/helpers/layout.py +++ b/gui/helpers/layout.py @@ -19,12 +19,13 @@ def center_window(root, width, height): root.focus_force() class Layout: - def __init__(self, root, sidebar, titlebar): + def __init__(self, root, sidebar, titlebar, resize_grips): self.root = root self.width = root.winfo_width() self.height = root.winfo_height() self.sidebar = sidebar self.titlebar = titlebar + self.resize_grips = resize_grips self.border_color = "#171616" def main(self, scrollable=False, padx=10, pady=10): @@ -90,6 +91,9 @@ def clear_everything(self): def clear(self): for widget in self.root.winfo_children(): + # ignore resize grips + if widget in self.resize_grips.values(): + continue if isinstance(widget, ttk.Frame) or isinstance(widget, ScrolledFrame) or isinstance(widget, ttk.Canvas) or isinstance(widget, RoundedFrame): widget.destroy() diff --git a/gui/main.py b/gui/main.py index 171d4cf..7aff5e2 100644 --- a/gui/main.py +++ b/gui/main.py @@ -60,6 +60,8 @@ def __init__(self, bot_controller): self.root.focus() + self._create_resize_grips() + self.cfg = Config() self.notifier = Notifier() self.images = Images() @@ -73,7 +75,7 @@ def __init__(self, bot_controller): self.sidebar.add_button("logout", self.quit) self.titlebar = Titlebar(self.root, self.images) - self.layout = Layout(self.root, self.sidebar, self.titlebar) + self.layout = Layout(self.root, self.sidebar, self.titlebar, self.resize_grips) self.loading_page = LoadingPage(self.root) self.onboarding_page = OnboardingPage(self.root, self.run, self.bot_controller) self.console = Console(self.root, self.bot_controller) @@ -95,39 +97,49 @@ def __init__(self, bot_controller): # When restored, remove decorations again self.root.after(10, lambda: self.root.overrideredirect(True)) - def _add_resize_grips(self): - bottom_resize_zone = RoundedFrame(self.root, radius=(0, 0, 25, 25), background=self.border_color) - bottom_resize_zone.set_height(self.resize_grip_size) - bottom_resize_zone.set_width(self.root.winfo_width()) - bottom_resize_zone.place(x=0, y=self.root.winfo_height() - self.resize_grip_size) - ttk.tk.Misc.lift(bottom_resize_zone) - bottom_resize_zone.bind("", self._resize_window) - bottom_resize_zone.bind("", lambda e: self.root.config(cursor="sb_v_double_arrow")) - bottom_resize_zone.bind("", lambda e: self.root.config(cursor="")) - - # top_resize_zone = RoundedFrame(self.root, radius=(25, 25, 0, 0), background=self.border_color) - # top_resize_zone.set_height(self.resize_grip_size // 2) - # top_resize_zone.set_width(self.root.winfo_width()) - # top_resize_zone.place(x=0, y=0) - # ttk.tk.Misc.lift(top_resize_zone) - # top_resize_zone.bind("", self._resize_window) - - # left_resize_zone = RoundedFrame(self.root, radius=(25, 0, 0, 25), background=self.border_color) - # left_resize_zone.set_height(self.root.winfo_height()) - # left_resize_zone.set_width(self.resize_grip_size) - # left_resize_zone.place(x=0, y=0) - # ttk.tk.Misc.lift(left_resize_zone) - # left_resize_zone.bind("", self._resize_window) - - right_resize_zone = RoundedFrame(self.root, radius=(0, 25, 25, 0), background=self.border_color) - right_resize_zone.set_height(self.root.winfo_height()) - right_resize_zone.set_width(self.resize_grip_size) - right_resize_zone.place(x=self.root.winfo_width() - self.resize_grip_size, y=0) - ttk.tk.Misc.lift(right_resize_zone) - right_resize_zone.bind("", self._resize_window) - right_resize_zone.bind("", lambda e: self.root.config(cursor="sb_h_double_arrow")) - right_resize_zone.bind("", lambda e: self.root.config(cursor="")) + def _create_resize_grips(self): + self.resize_grips = {} + + # Bottom grip + bottom = RoundedFrame( + self.root, + radius=(0, 0, 25, 25), + background=self.border_color + ) + bottom.bind("", self._resize_window) + bottom.bind("", lambda e: self.root.config(cursor="sb_v_double_arrow")) + bottom.bind("", lambda e: self.root.config(cursor="")) + self.resize_grips["bottom"] = bottom + + # Right grip + right = RoundedFrame( + self.root, + radius=(0, 25, 25, 0), + background=self.border_color + ) + right.bind("", self._resize_window) + right.bind("", lambda e: self.root.config(cursor="sb_h_double_arrow")) + right.bind("", lambda e: self.root.config(cursor="")) + self.resize_grips["right"] = right + + self._position_resize_grips() + def _position_resize_grips(self): + w = self.root.winfo_width() + h = self.root.winfo_height() + s = self.resize_grip_size + + self.resize_grips["bottom"].place( + x=0, y=h - s, width=w, height=s + ) + + self.resize_grips["right"].place( + x=w - s, y=0, width=s, height=h + ) + + for grip in self.resize_grips.values(): + ttk.tk.Misc.lift(grip) + def _resize_window(self, event): # resize the window based on mouse position, save the new positions so resizing continues smoothly x = self.root.winfo_pointerx() @@ -136,6 +148,7 @@ def _resize_window(self, event): self.root.update_idletasks() self.size = (self.root.winfo_width(), self.root.winfo_height()) + self._position_resize_grips() def _show_window(self): self.root.deiconify() @@ -145,7 +158,7 @@ def draw_home(self, restart=False, start=False): self.layout.clear() main = self.layout.main() self.home_page.draw(main, restart=restart, start=start) - self._add_resize_grips() + self._position_resize_grips() # def draw_console(self): # self.sidebar.set_current_page("console") @@ -158,21 +171,21 @@ def draw_settings(self): self.layout.clear() main = self.layout.main(scrollable=True) self.settings_page.draw(main) - self._add_resize_grips() + self._position_resize_grips() def draw_scripts(self): self.sidebar.set_current_page("scripts") self.layout.clear() main = self.layout.main() self.scripts_page.draw(main) - self._add_resize_grips() + self._position_resize_grips() def draw_tools(self): self.sidebar.set_current_page("tools") self.layout.clear() main = self.layout.main(scrollable=True) self.tools_page.draw(main) - self._add_resize_grips() + self._position_resize_grips() # def draw_loading(self): # self.layout.hide_titlebar() From f26879c0734cce6ab348a0ea025242c7611443cf Mon Sep 17 00:00:00 2001 From: Benny <83777519+bennyscripts@users.noreply.github.com> Date: Tue, 20 Jan 2026 13:59:08 +0000 Subject: [PATCH 12/37] Hide window until fully ready --- gui/main.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/gui/main.py b/gui/main.py index 7aff5e2..9c1e9d7 100644 --- a/gui/main.py +++ b/gui/main.py @@ -26,6 +26,10 @@ def __init__(self, bot_controller): enable_high_dpi_awareness() self.root = ttk.tk.Tk() + + # hide the window until ready + self.root.withdraw() + self.root.size = self.size self.root.title("Ghost") # self.root.resizable(False, False) @@ -56,7 +60,7 @@ def __init__(self, bot_controller): self.root.configure(bg="#ff00ff") self.root.attributes("-transparentcolor", "#ff00ff") else: - self.root.attributes("-alpha", 0) + self.root.attributes("-alpha", 1) self.root.focus() @@ -96,6 +100,10 @@ def __init__(self, bot_controller): # When restored, remove decorations again self.root.after(10, lambda: self.root.overrideredirect(True)) + + self.root.update_idletasks() + # bring window to front + self.root.after(500, lambda: self.root.deiconify()) def _create_resize_grips(self): self.resize_grips = {} From 1e919cc145245ca3ee31810099232203b2ce990d Mon Sep 17 00:00:00 2001 From: Benny <83777519+bennyscripts@users.noreply.github.com> Date: Tue, 20 Jan 2026 14:36:12 +0000 Subject: [PATCH 13/37] Add rounded corners --- gui/pages/onboarding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/pages/onboarding.py b/gui/pages/onboarding.py index 6630491..194fee0 100644 --- a/gui/pages/onboarding.py +++ b/gui/pages/onboarding.py @@ -172,7 +172,7 @@ def _draw_webhook_setup(self, parent): return wrapper def draw(self): - wrapper = ttk.Frame(self.root) + wrapper = RoundedFrame(self.root, radius=(25, 25, 25, 25), background=self.root.style.colors.get("bg")) wrapper.place(relx=0.5, rely=0.5, anchor="center") token_entry = self._draw_token_entry(wrapper) From dd047edf5ab9b4c5a95151fe0fac870dba9b0499 Mon Sep 17 00:00:00 2001 From: Benny <83777519+bennyscripts@users.noreply.github.com> Date: Tue, 20 Jan 2026 14:36:35 +0000 Subject: [PATCH 14/37] Fix problems when minimising window --- gui/components/titlebar.py | 18 ++++++++------- gui/main.py | 45 ++++++++++++++++++++------------------ 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/gui/components/titlebar.py b/gui/components/titlebar.py index 92271b6..ac4dc3d 100644 --- a/gui/components/titlebar.py +++ b/gui/components/titlebar.py @@ -33,14 +33,16 @@ def _reset_hover_state(self): self.root.event_generate("", warp=True, x=x, y=y) def _minimize(self): - if sys.platform == "darwin": - self.root.withdraw() - self.root.bind("", self._restore_once, add="+") - else: - self.root.overrideredirect(False) - self.root.deiconify() - self.root.overrideredirect(True) - self.root.update_idletasks() + self.root.overrideredirect(False) + self.root.withdraw() + # if sys.platform == "darwin": + # self.root.withdraw() + # self.root.bind("", self._restore_once, add="+") + # else: + # self.root.overrideredirect(False) + # self.root.deiconify() + # self.root.overrideredirect(True) + # self.root.update_idletasks() def _restore_once(self, event=None): self.root.unbind("") diff --git a/gui/main.py b/gui/main.py index 9c1e9d7..a1f3b44 100644 --- a/gui/main.py +++ b/gui/main.py @@ -18,27 +18,26 @@ class GhostGUI: def __init__(self, bot_controller): - self.resize_grip_size = 10 + self.resize_grip_size = 5 self.size = (650, 530) self.border_color = "#171616" self.bot_controller = bot_controller + self.resize_grips = {} enable_high_dpi_awareness() self.root = ttk.tk.Tk() + self.root.size = self.size + self.root.title("Ghost") - # hide the window until ready + self.root.overrideredirect(False) self.root.withdraw() - self.root.size = self.size - self.root.title("Ghost") - # self.root.resizable(False, False) if os.name == "nt": self.root.iconbitmap(resource_path("data/icon.ico")) - self.root.geometry(f"{self.size[0]}x{self.size[1]}") + self.root.minsize(self.size[0], self.size[1]) self.root.protocol("WM_DELETE_WINDOW", self.quit) - self.root.createcommand('::tk::mac::ReopenApplication', self._show_window) self.root.style = ttk.Style() # self.root.style.theme_use("darkly") @@ -52,7 +51,6 @@ def __init__(self, bot_controller): self.root.style.configure("TLabel", font=("Host Grotesk",)) self.root.style.configure("TButton", font=("Host Grotesk",)) - self.root.overrideredirect(True) if sys.platform == "darwin": self.root.attributes("-transparent", True) self.root.configure(bg="systemTransparent") @@ -62,10 +60,6 @@ def __init__(self, bot_controller): else: self.root.attributes("-alpha", 1) - self.root.focus() - - self._create_resize_grips() - self.cfg = Config() self.notifier = Notifier() self.images = Images() @@ -78,6 +72,9 @@ def __init__(self, bot_controller): self.sidebar.add_button("tools", self.draw_tools) self.sidebar.add_button("logout", self.quit) + if self.cfg.get("token") != "": + self._create_resize_grips() + self.titlebar = Titlebar(self.root, self.images) self.layout = Layout(self.root, self.sidebar, self.titlebar, self.resize_grips) self.loading_page = LoadingPage(self.root) @@ -94,17 +91,26 @@ def __init__(self, bot_controller): self.bot_controller.set_gui(self) if sys.platform == "darwin": - self.root.overrideredirect(False) self.layout.center_window(self.size[0], self.size[1]) self.root.update_idletasks() - - # When restored, remove decorations again - self.root.after(10, lambda: self.root.overrideredirect(True)) self.root.update_idletasks() - # bring window to front - self.root.after(500, lambda: self.root.deiconify()) + self.root.createcommand('::tk::mac::ReopenApplication', self._show_window) + self.root.bind("", lambda _: self._window_mapped()) + + self.root.after(450, self._show_window) + self.root.after(500, self._window_mapped) + def _window_mapped(self): + self.root.update_idletasks() + self.root.overrideredirect(True) + self.root.state("normal") + + def _show_window(self): + self.root.update_idletasks() + self.root.deiconify() + self.root.overrideredirect(True) + def _create_resize_grips(self): self.resize_grips = {} @@ -157,9 +163,6 @@ def _resize_window(self, event): self.size = (self.root.winfo_width(), self.root.winfo_height()) self._position_resize_grips() - - def _show_window(self): - self.root.deiconify() def draw_home(self, restart=False, start=False): self.sidebar.set_current_page("home") From af2b0b97f509aad50fb82fce7b564c2ca2e7ce09 Mon Sep 17 00:00:00 2001 From: Benny <83777519+bennyscripts@users.noreply.github.com> Date: Tue, 20 Jan 2026 15:18:29 +0000 Subject: [PATCH 15/37] Change header font to match title in home page --- gui/components/settings_frame.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/components/settings_frame.py b/gui/components/settings_frame.py index 9eff226..6207e7c 100644 --- a/gui/components/settings_frame.py +++ b/gui/components/settings_frame.py @@ -43,7 +43,7 @@ def _draw_header(self, parent): self.header = RoundedFrame(parent, radius=(15, 15, 0, 0), bootstyle="secondary.TFrame") self.header.pack(fill=ttk.BOTH, expand=False) - self.title = ttk.Label(self.header, text=self.header_text, font=("Host Grotesk", 14 if sys.platform != "darwin" else 20, "bold")) + self.title = ttk.Label(self.header, text=self.header_text, font=("Host Grotesk", 14 if sys.platform != "darwin" else 18, "bold")) self.title.configure(background=self.root.style.colors.get("secondary")) self.title.grid(row=0, column=0, sticky=ttk.NSEW, padx=15, pady=15) From 807306eef04de478e5a55c06de420b69568f8164 Mon Sep 17 00:00:00 2001 From: Benny <83777519+bennyscripts@users.noreply.github.com> Date: Tue, 20 Jan 2026 15:19:06 +0000 Subject: [PATCH 16/37] Add global Style enum for globally shared colours that are not part of the ttk style --- gui/components/sidebar.py | 16 ++++++++-------- gui/components/titlebar.py | 25 ++++++++++++------------- gui/helpers/__init__.py | 3 ++- gui/helpers/layout.py | 4 ++-- gui/helpers/style.py | 5 +++++ gui/main.py | 30 ++++++++++++------------------ 6 files changed, 41 insertions(+), 42 deletions(-) create mode 100644 gui/helpers/style.py diff --git a/gui/components/sidebar.py b/gui/components/sidebar.py index fcd6b02..e730f4d 100644 --- a/gui/components/sidebar.py +++ b/gui/components/sidebar.py @@ -3,6 +3,7 @@ from ttkbootstrap.dialogs import Messagebox from gui.helpers import Images from gui.components import RoundedFrame, RoundedButton +from gui.helpers.style import Style class Sidebar: def __init__(self, root): @@ -12,7 +13,6 @@ def __init__(self, root): self.button_cmds = {} self.tk_buttons = [] self.sidebar = None - self.bg_color = "#171616" root_width = 600 self.width = root_width // (root_width // 65) @@ -29,13 +29,13 @@ def _hover_enter(self, button_wrapper, button, page_name): button_wrapper.set_background(background) def _hover_leave(self, button_wrapper, button, page_name): - background = self.bg_color if self.current_page != page_name else self.root.style.colors.get("secondary") + background = Style.WINDOW_BORDER.value if self.current_page != page_name else self.root.style.colors.get("secondary") button.configure(background=background) button_wrapper.set_background(background) def _create_button(self, image, page_name, command, row): is_selected = self.current_page == page_name - bg_color = self.root.style.colors.get("secondary") if is_selected else self.bg_color + bg_color = self.root.style.colors.get("secondary") if is_selected else Style.WINDOW_BORDER.value button_wrapper = RoundedFrame( self.sidebar, @@ -87,7 +87,7 @@ def disable(self): def draw(self): # self.sidebar = ttk.Frame(self.root, width=self.width, height=self.root.winfo_height(), style="dark.TFrame") - self.sidebar = RoundedFrame(self.root, radius=(0, 0, 0, 25), background=self.bg_color) + self.sidebar = RoundedFrame(self.root, radius=(0, 0, 0, 25), background=Style.WINDOW_BORDER.value) self.sidebar.set_height(self.root.winfo_height()) self.sidebar.set_width(self.width + 7) # self.sidebar.pack(side=ttk.LEFT, fill=ttk.BOTH) @@ -96,12 +96,12 @@ def draw(self): self.buttons = { "home": self._create_button(self.images.get("home"), "home", self.button_cmds["home"], 0), # "console": self._create_button(self.images.get("console"), "console", self.button_cmds["console"], 1), - "settings": self._create_button(self.images.get("settings"), "settings", self.button_cmds["settings"], 2), - "scripts": self._create_button(self.images.get("scripts"), "scripts", self.button_cmds["scripts"], 3), - "tools": self._create_button(self.images.get("tools"), "tools", self.button_cmds["tools"], 4), + "settings": self._create_button(self.images.get("settings"), "settings", self.button_cmds["settings"], 1), + "scripts": self._create_button(self.images.get("scripts"), "scripts", self.button_cmds["scripts"], 2), + "tools": self._create_button(self.images.get("tools"), "tools", self.button_cmds["tools"], 3), } - logout_btn = ttk.Label(self.sidebar, image=self.images.get("logout"), background=self.bg_color, anchor="center") + logout_btn = ttk.Label(self.sidebar, image=self.images.get("logout"), background=Style.WINDOW_BORDER.value, anchor="center") logout_btn.bind("", lambda e: self._quit()) logout_btn.bind("", lambda e: self._hover_enter(logout_btn, "logout")) logout_btn.bind("", lambda e: self._hover_leave(logout_btn, "logout")) diff --git a/gui/components/titlebar.py b/gui/components/titlebar.py index ac4dc3d..88bd33b 100644 --- a/gui/components/titlebar.py +++ b/gui/components/titlebar.py @@ -1,6 +1,8 @@ +from logging import root import sys import ttkbootstrap as ttk from gui.components import RoundedFrame +from gui.helpers.style import Style class Titlebar: def __init__(self, root, images): @@ -9,7 +11,6 @@ def __init__(self, root, images): self._offset_x = 0 self._offset_y = 0 self._dragging = False - self.bg_color = "#171616" def _on_press(self, event): self._dragging = True @@ -74,10 +75,10 @@ def draw(self): titlebar = RoundedFrame( self.root, radius=(25, 25, 0, 0), - background=self.bg_color + background=Style.WINDOW_BORDER.value ) - inner_wrapper = RoundedFrame(titlebar, background=self.bg_color, radius=0) + inner_wrapper = RoundedFrame(titlebar, background=Style.WINDOW_BORDER.value, radius=0) padx = 8 pady = 8 @@ -91,47 +92,45 @@ def draw(self): pady = 0 close_btn = ttk.Label(inner_wrapper, text="●", foreground="#FF5F57", font=("Arial", 25)) - close_btn.configure(background=self.bg_color) + close_btn.configure(background=Style.WINDOW_BORDER.value) close_btn.pack(side=ttk.LEFT, padx=(0, 0)) close_btn.bind("", lambda e: self.root.quit()) close_btn.bind("", lambda e: close_btn.configure(foreground="#CC4940")) close_btn.bind("", lambda e: close_btn.configure(foreground="#FF5F57")) minimize_btn = ttk.Label(inner_wrapper, text="●", foreground="#FFBD2E", font=("Arial", 25)) - minimize_btn.configure(background=self.bg_color) + minimize_btn.configure(background=Style.WINDOW_BORDER.value) minimize_btn.pack(side=ttk.LEFT, padx=(0, 0)) minimize_btn.bind("", lambda e: self._minimize()) minimize_btn.bind("", lambda e: minimize_btn.configure(foreground="#CC9A26")) minimize_btn.bind("", lambda e: minimize_btn.configure(foreground="#FFBD2E")) maximize_btn = ttk.Label(inner_wrapper, text="●", foreground="#4f4c4c", font=("Arial", 25)) - maximize_btn.configure(background=self.bg_color) + maximize_btn.configure(background=Style.WINDOW_BORDER.value) maximize_btn.pack(side=ttk.LEFT, padx=(0, 5)) # maximize_btn.bind("", lambda e: maximize_btn.configure(foreground="#20A833")) # maximize_btn.bind("", lambda e: maximize_btn.configure(foreground="#28C940")) - maximize_btn.bind("", lambda e: maximize_btn.configure(foreground="#3b3a3a")) - maximize_btn.bind("", lambda e: maximize_btn.configure(foreground="#4f4c4c")) # maximize_btn.bind("", lambda e: self._maximize()) else: ico = ttk.Label(inner_wrapper, image=self.images.images["titlebar-ico"]) - ico.configure(background=self.bg_color) + ico.configure(background=Style.WINDOW_BORDER.value) ico.pack(side=ttk.LEFT, padx=(5, 0)) title = ttk.Label(inner_wrapper, text="Ghost", font=("Host Grotesk", 10)) - title.configure(background=self.bg_color) + title.configure(background=Style.WINDOW_BORDER.value) title.pack(side=ttk.LEFT, padx=(5, 0)) close_btn = ttk.Label(inner_wrapper, text="✕", font=("Host Grotesk", 10)) - close_btn.configure(background=self.bg_color) + close_btn.configure(background=Style.WINDOW_BORDER.value) close_btn.pack(side=ttk.RIGHT, padx=(0, 5)) close_btn.bind("", lambda e: self.root.quit()) minimize_btn = ttk.Label(inner_wrapper, text="—", font=("Host Grotesk", 10)) - minimize_btn.configure(background=self.bg_color) + minimize_btn.configure(background=Style.WINDOW_BORDER.value) minimize_btn.pack(side=ttk.RIGHT, padx=(0, 7)) minimize_btn.bind("", lambda e: self._minimize()) inner_wrapper.pack(fill=ttk.BOTH, expand=True, pady=pady, padx=padx) - return titlebar + return titlebar \ No newline at end of file diff --git a/gui/helpers/__init__.py b/gui/helpers/__init__.py index 3f24a63..de20785 100644 --- a/gui/helpers/__init__.py +++ b/gui/helpers/__init__.py @@ -1,2 +1,3 @@ from .images import Images -from .layout import Layout \ No newline at end of file +from .layout import Layout +from .style import Style \ No newline at end of file diff --git a/gui/helpers/layout.py b/gui/helpers/layout.py index 6c1a40f..a754443 100644 --- a/gui/helpers/layout.py +++ b/gui/helpers/layout.py @@ -2,6 +2,7 @@ import ttkbootstrap as ttk from ttkbootstrap.scrolled import ScrolledFrame from gui.components import RoundedFrame +from gui.helpers.style import Style def resize(root, width, height): root.minsize(width, height) @@ -26,7 +27,6 @@ def __init__(self, root, sidebar, titlebar, resize_grips): self.sidebar = sidebar self.titlebar = titlebar self.resize_grips = resize_grips - self.border_color = "#171616" def main(self, scrollable=False, padx=10, pady=10): width = self.width - (self.width // 100) @@ -34,7 +34,7 @@ def main(self, scrollable=False, padx=10, pady=10): border = RoundedFrame( self.root, radius=(0, 0, 25, 0), # ALL corners here - background=self.border_color + background=Style.WINDOW_BORDER.value ) border.pack(fill=ttk.BOTH, expand=True) diff --git a/gui/helpers/style.py b/gui/helpers/style.py new file mode 100644 index 0000000..2d91980 --- /dev/null +++ b/gui/helpers/style.py @@ -0,0 +1,5 @@ +import json +from enum import Enum + +class Style(Enum): + WINDOW_BORDER = "#171616" \ No newline at end of file diff --git a/gui/main.py b/gui/main.py index a1f3b44..c9ef770 100644 --- a/gui/main.py +++ b/gui/main.py @@ -3,28 +3,22 @@ os.environ["SSL_CERT_FILE"] = certifi.where() import ttkbootstrap as ttk -from ttkbootstrap.dialogs import Messagebox -from ttkbootstrap.utility import enable_high_dpi_awareness from utils.notifier import Notifier from utils.config import Config import utils.console as logging from utils.files import resource_path -from utils import uninstall_fonts from gui.pages import HomePage, LoadingPage, SettingsPage, OnboardingPage, ScriptsPage, ToolsPage from gui.components import Sidebar, Console, Titlebar, RoundedFrame -from gui.helpers import Images, Layout +from gui.helpers import Images, Layout, Style class GhostGUI: def __init__(self, bot_controller): self.resize_grip_size = 5 self.size = (650, 530) - self.border_color = "#171616" self.bot_controller = bot_controller self.resize_grips = {} - - enable_high_dpi_awareness() self.root = ttk.tk.Tk() self.root.size = self.size @@ -43,13 +37,13 @@ def __init__(self, bot_controller): # self.root.style.theme_use("darkly") self.root.style.load_user_themes(resource_path("data/gui_theme.json")) self.root.style.theme_use("ghost") - self.root.style.configure("TEntry", background=self.root.style.colors.get("dark"), fieldbackground=self.root.style.colors.get("secondary")) + self.root.style.configure("TEntry", background=self.root.style.colors.get("dark"), fieldbackground=self.root.style.colors.get("secondary")) self.root.style.configure("TCheckbutton", background=self.root.style.colors.get("dark")) - self.root.style.configure("TMenubutton", font=("Host Grotesk",)) + self.root.style.configure("TMenubutton", font=("Host Grotesk",)) self.root.style.configure("TCheckbutton", font=("Host Grotesk",)) - self.root.style.configure("TEntry", font=("Host Grotesk",)) - self.root.style.configure("TLabel", font=("Host Grotesk",)) - self.root.style.configure("TButton", font=("Host Grotesk",)) + self.root.style.configure("TEntry", font=("Host Grotesk",)) + self.root.style.configure("TLabel", font=("Host Grotesk",)) + self.root.style.configure("TButton", font=("Host Grotesk",)) if sys.platform == "darwin": self.root.attributes("-transparent", True) @@ -65,12 +59,12 @@ def __init__(self, bot_controller): self.images = Images() self.sidebar = Sidebar(self.root) - self.sidebar.add_button("home", self.draw_home) + self.sidebar.add_button("home", self.draw_home) # self.sidebar.add_button("console", self.draw_console) self.sidebar.add_button("settings", self.draw_settings) - self.sidebar.add_button("scripts", self.draw_scripts) - self.sidebar.add_button("tools", self.draw_tools) - self.sidebar.add_button("logout", self.quit) + self.sidebar.add_button("scripts", self.draw_scripts) + self.sidebar.add_button("tools", self.draw_tools) + self.sidebar.add_button("logout", self.quit) if self.cfg.get("token") != "": self._create_resize_grips() @@ -118,7 +112,7 @@ def _create_resize_grips(self): bottom = RoundedFrame( self.root, radius=(0, 0, 25, 25), - background=self.border_color + background=Style.WINDOW_BORDER.value ) bottom.bind("", self._resize_window) bottom.bind("", lambda e: self.root.config(cursor="sb_v_double_arrow")) @@ -129,7 +123,7 @@ def _create_resize_grips(self): right = RoundedFrame( self.root, radius=(0, 25, 25, 0), - background=self.border_color + background=Style.WINDOW_BORDER.value ) right.bind("", self._resize_window) right.bind("", lambda e: self.root.config(cursor="sb_h_double_arrow")) From bc9ca05d603b8fafc2b18531368550617ed25488 Mon Sep 17 00:00:00 2001 From: Benny <83777519+bennyscripts@users.noreply.github.com> Date: Tue, 20 Jan 2026 16:01:37 +0000 Subject: [PATCH 17/37] Fix minimising window on Windows --- gui/components/titlebar.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/gui/components/titlebar.py b/gui/components/titlebar.py index 88bd33b..a467fc9 100644 --- a/gui/components/titlebar.py +++ b/gui/components/titlebar.py @@ -34,17 +34,20 @@ def _reset_hover_state(self): self.root.event_generate("", warp=True, x=x, y=y) def _minimize(self): + self.root.update_idletasks() self.root.overrideredirect(False) - self.root.withdraw() - # if sys.platform == "darwin": - # self.root.withdraw() - # self.root.bind("", self._restore_once, add="+") - # else: - # self.root.overrideredirect(False) - # self.root.deiconify() - # self.root.overrideredirect(True) - # self.root.update_idletasks() - + + if sys.platform == "darwin": + self.root.withdraw() + elif sys.platform == "win32": + self.root.iconify() + def restore_override(): + if not self.root.state() == 'iconic': + self.root.overrideredirect(True) + else: + self.root.after(100, restore_override) + self.root.after(100, restore_override) + def _restore_once(self, event=None): self.root.unbind("") self.root.deiconify() From 83d97c0a3e1cbfbb16074b40523c9be7ce727b31 Mon Sep 17 00:00:00 2001 From: Benny <83777519+bennyscripts@users.noreply.github.com> Date: Tue, 20 Jan 2026 16:04:32 +0000 Subject: [PATCH 18/37] Change font sizes --- gui/components/tool_page.py | 3 ++- gui/pages/tools/tools.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/gui/components/tool_page.py b/gui/components/tool_page.py index 34dd2bf..21f60ac 100644 --- a/gui/components/tool_page.py +++ b/gui/components/tool_page.py @@ -1,4 +1,5 @@ import abc +import sys import ttkbootstrap as ttk from gui.components import RoundedFrame @@ -26,7 +27,7 @@ def draw_navigation(self, parent): back_button.bind("", lambda e: self.go_back()) back_button.grid(row=0, column=1, sticky=ttk.W, padx=(0, 10)) - page_name = ttk.Label(wrapper, text=self.title, font=("Host Grotesk", 16, "bold")) + page_name = ttk.Label(wrapper, text=self.title, font=("Host Grotesk", 14 if sys.platform != "darwin" else 16, "bold")) page_name.grid(row=0, column=2, sticky=ttk.W) return wrapper diff --git a/gui/pages/tools/tools.py b/gui/pages/tools/tools.py index cd166fc..618c4f4 100644 --- a/gui/pages/tools/tools.py +++ b/gui/pages/tools/tools.py @@ -84,7 +84,7 @@ def draw(self, parent): page_wrapper.pack(fill="x", expand=True, pady=(0, 10)) page_wrapper.bind("", lambda e, cmd=page["command"]: cmd()) - page_title = ttk.Label(page_wrapper, text=page["name"], font=("Host Grotesk", 14 if sys.platform != "darwin" else 20, "bold")) + page_title = ttk.Label(page_wrapper, text=page["name"], font=("Host Grotesk", 14 if sys.platform != "darwin" else 18, "bold")) page_title.configure(background=self.root.style.colors.get("secondary")) page_title.grid(row=0, column=0, sticky=ttk.NSEW, padx=15, pady=(15, 15)) page_title.bind("", lambda e, cmd=page["command"]: cmd()) From 3edf99d9ab04e6d0259736971114de80c4b70186 Mon Sep 17 00:00:00 2001 From: Benny <83777519+bennyscripts@users.noreply.github.com> Date: Tue, 20 Jan 2026 16:33:16 +0000 Subject: [PATCH 19/37] Add dedicated close function and make mac os close button withdraw to dock instead of quitting app so its actually expected mac os behaviour --- gui/components/titlebar.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/gui/components/titlebar.py b/gui/components/titlebar.py index a467fc9..6bb832c 100644 --- a/gui/components/titlebar.py +++ b/gui/components/titlebar.py @@ -33,12 +33,20 @@ def _reset_hover_state(self): self.root.event_generate("", warp=True, x=x+1, y=y) self.root.event_generate("", warp=True, x=x, y=y) + def _close(self): + if sys.platform == "darwin": + self.root.update_idletasks() + self.root.overrideredirect(False) + self.root.withdraw() + else: + self.root.quit() + def _minimize(self): self.root.update_idletasks() self.root.overrideredirect(False) if sys.platform == "darwin": - self.root.withdraw() + self.root.iconify() elif sys.platform == "win32": self.root.iconify() def restore_override(): @@ -97,16 +105,16 @@ def draw(self): close_btn = ttk.Label(inner_wrapper, text="●", foreground="#FF5F57", font=("Arial", 25)) close_btn.configure(background=Style.WINDOW_BORDER.value) close_btn.pack(side=ttk.LEFT, padx=(0, 0)) - close_btn.bind("", lambda e: self.root.quit()) + close_btn.bind("", lambda e: self._close()) close_btn.bind("", lambda e: close_btn.configure(foreground="#CC4940")) close_btn.bind("", lambda e: close_btn.configure(foreground="#FF5F57")) - minimize_btn = ttk.Label(inner_wrapper, text="●", foreground="#FFBD2E", font=("Arial", 25)) + minimize_btn = ttk.Label(inner_wrapper, text="●", foreground="#4f4c4c", font=("Arial", 25)) minimize_btn.configure(background=Style.WINDOW_BORDER.value) minimize_btn.pack(side=ttk.LEFT, padx=(0, 0)) - minimize_btn.bind("", lambda e: self._minimize()) - minimize_btn.bind("", lambda e: minimize_btn.configure(foreground="#CC9A26")) - minimize_btn.bind("", lambda e: minimize_btn.configure(foreground="#FFBD2E")) + # minimize_btn.bind("", lambda e: self._minimize()) + # minimize_btn.bind("", lambda e: minimize_btn.configure(foreground="#CC9A26")) + # minimize_btn.bind("", lambda e: minimize_btn.configure(foreground="#FFBD2E")) maximize_btn = ttk.Label(inner_wrapper, text="●", foreground="#4f4c4c", font=("Arial", 25)) maximize_btn.configure(background=Style.WINDOW_BORDER.value) From 4254f50c0475a361267b57f72597b226916c5e61 Mon Sep 17 00:00:00 2001 From: Benny <83777519+bennyscripts@users.noreply.github.com> Date: Wed, 21 Jan 2026 16:07:14 +0000 Subject: [PATCH 20/37] Custom dropdown menu widget --- gui/components/__init__.py | 3 +- gui/components/dropdown_menu.py | 111 ++++++++++++++++++++ gui/components/settings/general.py | 20 +++- gui/components/settings/session_spoofing.py | 29 ++--- gui/components/settings/theming.py | 49 +++++---- 5 files changed, 176 insertions(+), 36 deletions(-) create mode 100644 gui/components/dropdown_menu.py diff --git a/gui/components/__init__.py b/gui/components/__init__.py index 0bd6d45..62ff3c8 100644 --- a/gui/components/__init__.py +++ b/gui/components/__init__.py @@ -5,4 +5,5 @@ from .settings_frame import SettingsFrame from .settings_panel import SettingsPanel from .titlebar import Titlebar -from .tool_page import ToolPage \ No newline at end of file +from .tool_page import ToolPage +from .dropdown_menu import DropdownMenu \ No newline at end of file diff --git a/gui/components/dropdown_menu.py b/gui/components/dropdown_menu.py new file mode 100644 index 0000000..27d4088 --- /dev/null +++ b/gui/components/dropdown_menu.py @@ -0,0 +1,111 @@ +import ttkbootstrap as ttk +from gui.components import RoundedFrame + +class DropdownMenu: + def __init__(self, parent, options, command=None): + self.parent = parent + self.options = options + self.selected_option = ttk.StringVar(value=options[0] if options else "") + self.command = command + + def _hover_enter(self, wrapper, label): + wrapper.set_background("#282828") + label.configure(background="#282828") + + def _hover_leave(self, wrapper, label): + wrapper.set_background(self.parent.style.colors.get("secondary")) + label.configure(background=self.parent.style.colors.get("secondary")) + + def _rearrange_options(self): + selected = self.selected_option.get() + self.options.remove(selected) + self.options.insert(0, selected) + + def _open_menu(self, event): + if not self._alive(): + return + + for widget in self.frame.winfo_children(): + widget.destroy() + + self._rearrange_options() + index = 0 + for option in self.options: + index += 1 + + wrapper = RoundedFrame(self.frame, radius=8, background=self.parent.style.colors.get("secondary")) + wrapper.pack(fill=ttk.X, padx=5, pady=(4, 5 if index == len(self.options) else 0)) + wrapper.bind("", lambda e, opt=option: self._on_option_selected(opt)) + + label = ttk.Label(wrapper, text=option, background=self.parent.style.colors.get("secondary"), anchor="w") + label.pack(fill=ttk.X, padx=5, pady=2) + label.bind("", lambda e, opt=option: self._on_option_selected(opt)) + + label.bind("", lambda e, w=wrapper, l=label: self._hover_enter(w, l)) + label.bind("", lambda e, w=wrapper, l=label: self._hover_leave(w, l)) + wrapper.bind("", lambda e, w=wrapper, l=label: self._hover_enter(w, l)) + wrapper.bind("", lambda e, w=wrapper, l=label: self._hover_leave(w, l)) + + def _close_menu(self): + if not self._alive(): + return + + for widget in self.frame.winfo_children(): + widget.destroy() + + label = ttk.Label(self.frame, textvariable=self.selected_option, anchor="w", background=self.parent.style.colors.get("secondary")) + label.pack(fill=ttk.X, padx=10, pady=5) + label.bind("", self._open_menu) + self.frame.bind("", self._open_menu) + + self.down_arrow = ttk.Label(self.frame, text="▼", background=self.parent.style.colors.get("secondary"), font=("Host Grotesk", 10)) + self.down_arrow.place(relx=1.0, rely=0.5, x=-10, y=0, anchor="e") + self.down_arrow.bind("", self._open_menu) + + def _on_option_selected(self, option): + if not self._alive(): + return + + self.selected_option.set(option) + if self.command: + self.command(option) + + if self._alive(): + self._close_menu() + + def _outside_click(self, event): + if not self._alive(): + return + if not self.frame.winfo_containing(event.x_root, event.y_root): + self._close_menu() + + def draw(self): + self.parent.bind("", self._outside_click, add="+") + self.frame = RoundedFrame(self.parent, radius=5, bootstyle="secondary.TFrame") + + label = ttk.Label(self.frame, textvariable=self.selected_option, anchor="w", background=self.parent.style.colors.get("secondary")) + label.pack(fill=ttk.X, padx=10, pady=5) + label.bind("", self._open_menu) + self.frame.bind("", self._open_menu) + + self.down_arrow = ttk.Label(self.frame, text="▼", background=self.parent.style.colors.get("secondary"), font=("Host Grotesk", 10)) + self.down_arrow.place(relx=1.0, rely=0.5, x=-10, y=0, anchor="e") + self.down_arrow.bind("", self._open_menu) + + return self.frame + + def value(self): + return self.selected_option.get() + + def set_selected(self, option): + if option in self.options: + self.selected_option.set(option) + if self._alive(): + self._close_menu() + + def destroy(self): + if self._alive(): + self.frame.destroy() + + def _alive(self): + return hasattr(self, "frame") and self.frame.winfo_exists() diff --git a/gui/components/settings/general.py b/gui/components/settings/general.py index 76a6d94..121f51c 100644 --- a/gui/components/settings/general.py +++ b/gui/components/settings/general.py @@ -1,6 +1,6 @@ import ttkbootstrap as ttk import utils.console as console -from gui.components import SettingsPanel +from gui.components import SettingsPanel, DropdownMenu, RoundedButton class GeneralPanel(SettingsPanel): def __init__(self, root, parent, bot_controller, images, config): @@ -14,6 +14,7 @@ def __init__(self, root, parent, bot_controller, images, config): "message_settings.auto_delete_delay": "Auto delete delay", "rich_embed_webhook": "Rich embed webhook", } + self.message_style_entry = None def _save_cfg(self): for index, (key, value) in enumerate(self.config_entries.items()): @@ -32,12 +33,21 @@ def _save_cfg(self): self.cfg.set(key, tkinter_entry.get(), save=False) + try: + self.cfg.set("message_settings.style", self.message_style_entry.value(), save=False) + except Exception as e: + console.error(f"Failed to set message style: {e}") + self.cfg.save(notify=False) def _only_numeric(self, event): if not event.char.isnumeric() and event.char != "" and event.keysym != "BackSpace": return "break" + def _set_message_style(self, style): + # self.message_style_entry.configure(text=style) + self._save_cfg() + def draw(self): for index, (key, value) in enumerate(self.config_entries.items()): padding = (10, 2) @@ -64,6 +74,14 @@ def draw(self): self.body.grid_columnconfigure(1, weight=1) self.config_tk_entries[key] = entry + message_style_label = ttk.Label(self.body, text="Message style") + message_style_label.configure(background=self.root.style.colors.get("dark")) + message_style_label.grid(row=len(self.config_entries) + 1, column=0, sticky=ttk.NW, padx=(10, 0), pady=(5, 10)) + + self.message_style_entry = DropdownMenu(self.body, options=["codeblock", "image", "embed"], command=self._set_message_style) + self.message_style_entry.set_selected(self.cfg.get("message_settings.style")) + self.message_style_entry.draw().grid(row=len(self.config_entries) + 1, column=1, sticky="we", padx=(10, 10), pady=(2, 10), columnspan=3) + return self.wrapper def save(self): diff --git a/gui/components/settings/session_spoofing.py b/gui/components/settings/session_spoofing.py index b54194c..d13681a 100644 --- a/gui/components/settings/session_spoofing.py +++ b/gui/components/settings/session_spoofing.py @@ -1,10 +1,11 @@ +import sys import ttkbootstrap as ttk import utils.console as console -from gui.components import SettingsPanel +from gui.components import SettingsPanel, RoundedFrame, DropdownMenu class SessionSpoofingPanel(SettingsPanel): - def __init__(self, root, parent, images, config): - super().__init__(root, parent, "Session Spoofing", images.get("session_spoofing")) + def __init__(self, root, parent, images, config, width=None): + super().__init__(root, parent, "Session Spoofing", images.get("session_spoofing"), width=width, collapsed=False) self.cfg = config self.selected_device = ttk.StringVar(value=self.cfg.get("session_spoofing.device")) self.last_saved_state = { @@ -41,23 +42,25 @@ def draw(self): device_label = ttk.Label(self.body, text="Session spoofing device") device_label.configure(background=self.root.style.colors.get("dark")) - device_label.grid(row=1, column=0, sticky=ttk.W, padx=(10, 0), pady=(5, 0)) + device_label.grid(row=1, column=0, sticky=ttk.NW, padx=(10, 0), pady=(10, 10)) - device_select_menu = ttk.Menubutton(self.body, textvariable=self.selected_device, bootstyle="secondary") - device_select_menu.menu = ttk.Menu(device_select_menu, tearoff=0) - device_select_menu["menu"] = device_select_menu.menu + # device_select_menu = ttk.Menubutton(self.body, textvariable=self.selected_device, bootstyle="secondary") + # device_select_menu.menu = ttk.Menu(device_select_menu, tearoff=0) + # device_select_menu["menu"] = device_select_menu.menu - for device in ["mobile", "desktop", "web", "embedded"]: - device_select_menu.menu.add_command(label=device, command=lambda device=device: self._select_and_save_device(device)) + # for device in ["mobile", "desktop", "web", "embedded"]: + # device_select_menu.menu.add_command(label=device, command=lambda device=device: self._select_and_save_device(device)) - device_select_menu.grid(row=1, column=1, sticky="we", padx=(10, 10), pady=(5, 0)) + device_select_menu = DropdownMenu(self.body, options=["mobile", "desktop", "web", "embedded"], command=self._select_and_save_device) + device_select_menu.set_selected(self.selected_device.get()) + device_select_menu.draw().grid(row=1, column=1, sticky="we", padx=(10, 10), pady=(5, 10)) # save_button = ttk.Button(self.body, text="Save", style="success.TButton", command=self._save_session_spoofing) # save_button.grid(row=2, column=1, sticky=ttk.E, padx=(0, 11), pady=10) - restart_required_label = ttk.Label(self.body, text="A restart is required to apply changes!", font=("Host Grotesk", 12, "italic")) - restart_required_label.configure(background=self.root.style.colors.get("dark"), foreground="#cccccc") - restart_required_label.grid(row=2, column=0, sticky=ttk.W, padx=(10, 0), pady=10) + # restart_required_label = ttk.Label(self.body, text="A restart is required to apply changes!", font=("Host Grotesk", 12, "italic")) + # restart_required_label.configure(background=self.root.style.colors.get("dark"), foreground="#cccccc") + # restart_required_label.grid(row=2, column=0, sticky=ttk.W, padx=(10, 0), pady=10) self.body.grid_columnconfigure(1, weight=1) diff --git a/gui/components/settings/theming.py b/gui/components/settings/theming.py index 141f1c1..62d1089 100644 --- a/gui/components/settings/theming.py +++ b/gui/components/settings/theming.py @@ -1,16 +1,17 @@ import ttkbootstrap as ttk import utils.console as console from utils.files import open_path_in_explorer, get_themes_path -from gui.components import SettingsPanel, RoundedButton, RoundedFrame +from gui.components import SettingsPanel, RoundedButton, RoundedFrame, DropdownMenu class ThemingPanel(SettingsPanel): - def __init__(self, root, parent, images, config): - super().__init__(root, parent, "Theming", images.get("theming")) + def __init__(self, root, parent, images, config, width=None): + super().__init__(root, parent, "Theming", images.get("theming"), width=width) self.cfg = config self.root = root self.images = images self.theme_tk_entries = [] self.themes = self.cfg.get_themes() + self.menu_themes = [str(theme) for theme in self.themes] self.theme_dict = self.cfg.theme.to_dict() def _save_theme(self, _=None): @@ -23,9 +24,13 @@ def _save_theme(self, _=None): self.cfg.save(notify=False) def _set_theme(self, theme): - self.select_menu.configure(text=theme) + print(f"Setting theme to: {theme}") + # self.select_menu.set_selected(theme) self.cfg.set_theme(theme, save=False) self.cfg.save(notify=True) + # self.select_menu.configure(text=theme) + # self.cfg.set_theme(theme, save=False) + # self.cfg.save(notify=True) # self.create_entry.delete(0, "end") # self.select_menu.configure(text=theme) @@ -111,33 +116,35 @@ def draw(self): #------- - select_label = ttk.Label(self.body, text="Select a theme") + select_label = ttk.Label(self.body, text="Select theme") select_label.configure(background=self.root.style.colors.get("dark")) - select_label.grid(row=1, column=0, sticky=ttk.W, padx=(10, 0), pady=(10, 0)) + select_label.grid(row=1, column=0, sticky=ttk.NW, padx=(10, 0), pady=(15, 0)) - self.select_menu = ttk.Menubutton(self.body, text=self.cfg.theme.name, bootstyle="secondary") - self.select_menu.menu = ttk.Menu(self.select_menu, tearoff=0) - self.select_menu["menu"] = self.select_menu.menu + # self.select_menu = ttk.Menubutton(self.body, text=self.cfg.theme.name, bootstyle="secondary") + # self.select_menu.menu = ttk.Menu(self.select_menu, tearoff=0) + # self.select_menu["menu"] = self.select_menu.menu - for theme in self.themes: - self.select_menu.menu.add_command(label=str(theme), command=lambda theme=theme.name: self._set_theme(theme)) + # for theme in self.themes: + # self.select_menu.menu.add_command(label=str(theme), command=lambda theme=theme.name: self._set_theme(theme)) - self.select_menu.grid(row=1, column=1, columnspan=2, sticky="we", padx=(10, 10), pady=(10, 0)) + self.select_menu = DropdownMenu(self.body, options=self.menu_themes, command=self._set_theme) + self.select_menu.set_selected(self.cfg.theme.name) + self.select_menu.draw().grid(row=1, column=1, columnspan=2, sticky="we", padx=(10, 10), pady=(10, 0)) #------- - message_style_label = ttk.Label(self.body, text="Global message style") - message_style_label.configure(background=self.root.style.colors.get("dark")) - message_style_label.grid(row=2, column=0, sticky=ttk.W, padx=(10, 0), pady=(10, 0)) + # message_style_label = ttk.Label(self.body, text="Message style") + # message_style_label.configure(background=self.root.style.colors.get("dark")) + # message_style_label.grid(row=2, column=0, sticky=ttk.W, padx=(10, 0), pady=(10, 0)) - self.message_style_entry = ttk.Menubutton(self.body, text=self.cfg.config["message_settings"]["style"], bootstyle="secondary") - self.message_style_entry.menu = ttk.Menu(self.message_style_entry, tearoff=0) - self.message_style_entry["menu"] = self.message_style_entry.menu + # self.message_style_entry = ttk.Menubutton(self.body, text=self.cfg.config["message_settings"]["style"], bootstyle="secondary") + # self.message_style_entry.menu = ttk.Menu(self.message_style_entry, tearoff=0) + # self.message_style_entry["menu"] = self.message_style_entry.menu - for style in ["codeblock", "image", "embed"]: - self.message_style_entry.menu.add_command(label=style, command=lambda style=style: self._set_message_style(style)) + # for style in ["codeblock", "image", "embed"]: + # self.message_style_entry.menu.add_command(label=style, command=lambda style=style: self._set_message_style(style)) - self.message_style_entry.grid(row=2, column=1, columnspan=2, sticky="we", padx=(10, 10), pady=(10, 0)) + # self.message_style_entry.grid(row=2, column=1, columnspan=2, sticky="we", padx=(10, 10), pady=(10, 0)) #------- From a31f7b90b016d982638cc878d56df1cc10c25d55 Mon Sep 17 00:00:00 2001 From: Benny <83777519+bennyscripts@users.noreply.github.com> Date: Wed, 21 Jan 2026 16:07:44 +0000 Subject: [PATCH 21/37] Add resize grips to tools pages --- gui/main.py | 4 ++-- gui/pages/tools/tools.py | 12 +++++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/gui/main.py b/gui/main.py index c9ef770..87ff4db 100644 --- a/gui/main.py +++ b/gui/main.py @@ -16,7 +16,7 @@ class GhostGUI: def __init__(self, bot_controller): self.resize_grip_size = 5 - self.size = (650, 530) + self.size = (700, 530) self.bot_controller = bot_controller self.resize_grips = {} @@ -77,7 +77,7 @@ def __init__(self, bot_controller): self.home_page = HomePage(self.root, self.bot_controller, self._restart_bot, self.console) self.settings_page = SettingsPage(self.root, self.bot_controller) self.scripts_page = ScriptsPage(self, self.bot_controller, self.images) - self.tools_page = ToolsPage(self.root, self.bot_controller, self.images, self.layout) + self.tools_page = ToolsPage(self.root, self.bot_controller, self.images, self.layout, self._position_resize_grips) logging.set_gui(self) diff --git a/gui/pages/tools/tools.py b/gui/pages/tools/tools.py index 618c4f4..6477f0e 100644 --- a/gui/pages/tools/tools.py +++ b/gui/pages/tools/tools.py @@ -7,11 +7,12 @@ from gui.pages.tools.user_lookup_page import UserLookupPage class ToolsPage: - def __init__(self, root, bot_controller, images, layout): + def __init__(self, root, bot_controller, images, layout, position_resize_grips): self.root = root self.bot_controller = bot_controller self.images = images self.layout = layout + self.position_resize_grips = position_resize_grips self.hover_colour = "#282a2a" self.spypet_page = SpyPetPage(self, root, bot_controller, images, layout) @@ -45,6 +46,7 @@ def draw_spypet(self): main = self.layout.main() self.spypet_page.draw(main) self.layout.sidebar.set_button_command("tools", self.draw_spypet) + self.position_resize_grips() def draw_message_logger(self): self.layout.sidebar.set_current_page("tools") @@ -52,6 +54,7 @@ def draw_message_logger(self): main = self.layout.main() self.message_logger_page.draw(main) self.layout.sidebar.set_button_command("tools", self.draw_message_logger) + self.position_resize_grips() def draw_user_lookup(self): self.layout.sidebar.set_current_page("tools") @@ -59,6 +62,7 @@ def draw_user_lookup(self): main = self.layout.main() self.user_lookup_page.draw(main) self.layout.sidebar.set_button_command("tools", self.draw_user_lookup) + self.position_resize_grips() def _bind_hover_effects(self, widget, targets, hover_bg, normal_bg): def on_enter(_): @@ -86,7 +90,7 @@ def draw(self, parent): page_title = ttk.Label(page_wrapper, text=page["name"], font=("Host Grotesk", 14 if sys.platform != "darwin" else 18, "bold")) page_title.configure(background=self.root.style.colors.get("secondary")) - page_title.grid(row=0, column=0, sticky=ttk.NSEW, padx=15, pady=(15, 15)) + page_title.grid(row=0, column=0, sticky=ttk.NSEW, padx=15, pady=10) page_title.bind("", lambda e, cmd=page["command"]: cmd()) # page_description = ttk.Label(page_wrapper, text=page["description"], font=("Host Grotesk", 12 if sys.platform != "darwin" else 16), wraplength=450) @@ -99,4 +103,6 @@ def draw(self, parent): page_icon.grid(row=0, column=1, sticky=ttk.E, padx=(0, 20), pady=15) page_wrapper.grid_columnconfigure(1, weight=1) - self._bind_hover_effects(page_wrapper, [page_title, page_wrapper, page_icon], self.hover_colour, self.root.style.colors.get("secondary")) \ No newline at end of file + self._bind_hover_effects(page_wrapper, [page_title, page_wrapper, page_icon], self.hover_colour, self.root.style.colors.get("secondary")) + self._bind_hover_effects(page_title, [page_title, page_wrapper, page_icon], self.hover_colour, self.root.style.colors.get("secondary")) + self._bind_hover_effects(page_icon, [page_title, page_wrapper, page_icon], self.hover_colour, self.root.style.colors.get("secondary")) \ No newline at end of file From e69bba9526b70c9484c66b07ac2f1fc67851c72d Mon Sep 17 00:00:00 2001 From: Benny <83777519+bennyscripts@users.noreply.github.com> Date: Wed, 21 Jan 2026 16:08:08 +0000 Subject: [PATCH 22/37] Disable create theme button when name entry is empty --- gui/components/settings/theming.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/gui/components/settings/theming.py b/gui/components/settings/theming.py index 62d1089..a00b343 100644 --- a/gui/components/settings/theming.py +++ b/gui/components/settings/theming.py @@ -97,22 +97,30 @@ def _hover_leave(_): return wrapper + def toggle_create_theme_button(self, state): + if state: + self.create_button.set_state("normal") + else: + self.create_button.set_state("disabled") + def draw(self): self.themes = self.cfg.get_themes() self.theme_dict = self.cfg.theme.to_dict() #------- - create_label = ttk.Label(self.body, text="Create a new theme") + create_label = ttk.Label(self.body, text="New theme name") create_label.configure(background=self.root.style.colors.get("dark")) create_label.grid(row=0, column=0, sticky=ttk.W, padx=(10, 0), pady=(10, 0)) self.create_entry = ttk.Entry(self.body, bootstyle="secondary", font=("Host Grotesk",)) self.create_entry.config(style="secondary.TEntry") self.create_entry.grid(row=0, column=1, sticky="we", padx=(10, 10), pady=(10, 0)) + self.create_entry.bind("", lambda e: self.toggle_create_theme_button(self.create_entry.get().strip() != "")) - create_button = RoundedButton(self.body, text="Create", command=lambda _: self._create_theme(self.create_entry.get()), style="success.TButton") - create_button.grid(row=0, column=2, sticky=ttk.E, padx=(0, 11), pady=(10, 0)) + self.create_button = RoundedButton(self.body, text="Create", command=lambda _: self._create_theme(self.create_entry.get()), style="success.TButton") + self.create_button.grid(row=0, column=2, sticky=ttk.E, padx=(0, 11), pady=(10, 0)) + self.create_button.set_state("disabled") #------- From cf2dcc3c4e646dfb7766c56a1933f9447c375884 Mon Sep 17 00:00:00 2001 From: Benny <83777519+bennyscripts@users.noreply.github.com> Date: Wed, 21 Jan 2026 16:08:42 +0000 Subject: [PATCH 23/37] Add switch for toggling RPC and session spoofing --- gui/components/settings/rich_presence.py | 34 ++++++++++++++------- gui/components/settings/session_spoofing.py | 12 ++++++-- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/gui/components/settings/rich_presence.py b/gui/components/settings/rich_presence.py index 668f7b5..f3e8787 100644 --- a/gui/components/settings/rich_presence.py +++ b/gui/components/settings/rich_presence.py @@ -1,10 +1,11 @@ +import sys import ttkbootstrap as ttk import utils.console as console -from gui.components import SettingsPanel, RoundedButton +from gui.components import SettingsPanel, RoundedButton, RoundedFrame class RichPresencePanel(SettingsPanel): - def __init__(self, root, parent, images, config): - super().__init__(root, parent, "Rich Presence", images.get("rich_presence")) + def __init__(self, root, parent, images, config, width=None): + super().__init__(root, parent, "Rich Presence", images.get("rich_presence"), width=width, collapsed=False) self.cfg = config self.rpc = self.cfg.get_rich_presence() self.rpc_tk_entries = {} @@ -51,16 +52,27 @@ def _reset_rpc(self): self.rpc.reset_defaults() def draw(self): - toggle_checkbox = ttk.Checkbutton(self.body, text="Enable Rich Presence", style="success.TCheckbutton") - toggle_checkbox.grid(row=0, column=0, columnspan=2, sticky=ttk.W, padx=(13, 0), pady=(10, 10)) - toggle_checkbox.configure(command=self._save_rpc) + toggle_wrapper = RoundedFrame(self.wrapper, radius=(10, 10, 10, 10), bootstyle="dark.TFrame") + toggle_wrapper.grid(row=0, column=0, columnspan=4, sticky="we", pady=(0, 10)) + toggle_wrapper.bind("", lambda e: self.toggle_checkbox.invoke()) + + toggle_label = ttk.Label(toggle_wrapper, text="Enable Rich Presence") + toggle_label.configure(background=self.root.style.colors.get("dark")) + toggle_label.grid(row=0, column=0, sticky=ttk.W, padx=(10, 0), pady=10) + toggle_label.bind("", lambda e: self.toggle_checkbox.invoke()) + + self.toggle_checkbox = ttk.Checkbutton(toggle_wrapper, text="", style="success-round-toggle") + self.toggle_checkbox.grid(row=0, column=1, sticky=ttk.E, padx=(0, 10), pady=10) + self.toggle_checkbox.configure(command=self._save_rpc) + + toggle_wrapper.grid_columnconfigure(0, weight=1) if self.rpc.enabled: - toggle_checkbox.state(["!alternate", "selected"]) + self.toggle_checkbox.state(["!alternate", "selected"]) else: - toggle_checkbox.state(["!alternate", "!selected"]) + self.toggle_checkbox.state(["!alternate", "!selected"]) - self.rpc_tk_entries["enabled"] = toggle_checkbox + self.rpc_tk_entries["enabled"] = self.toggle_checkbox padding = (10, 2) for index, (key, value) in enumerate(self.rpc_entries.items()): @@ -76,8 +88,8 @@ def draw(self): label = ttk.Label(self.body, text=value) label.configure(background=self.root.style.colors.get("dark")) - label.grid(row=index + 1, column=0, sticky=ttk.W, padx=padding[0], pady=padding[1]) - entry.grid(row=index + 1, column=1, sticky="we", padx=padding[0], pady=padding[1], columnspan=3) + label.grid(row=index + 1, column=0, sticky=ttk.W, padx=padding[0], pady=(padding[1] + 8 if index == 1 else padding[1], padding[1])) + entry.grid(row=index + 1, column=1, sticky="we", padx=padding[0], pady=(padding[1] + 8 if index == 1 else padding[1], padding[1]), columnspan=3) self.body.grid_columnconfigure(1, weight=1) self.rpc_tk_entries[key] = entry diff --git a/gui/components/settings/session_spoofing.py b/gui/components/settings/session_spoofing.py index d13681a..1b39401 100644 --- a/gui/components/settings/session_spoofing.py +++ b/gui/components/settings/session_spoofing.py @@ -31,10 +31,18 @@ def _select_and_save_device(self, device): self._save_session_spoofing() def draw(self): - self.checkbox = ttk.Checkbutton(self.body, text="Enable session spoofing", style="success.TCheckbutton") - self.checkbox.grid(row=0, column=0, columnspan=2, sticky=ttk.W, padx=(13, 0), pady=(10, 0)) + toggle_label = ttk.Label(self.body, text="Enable Session Spoofing") + toggle_label.configure(background=self.root.style.colors.get("dark")) + toggle_label.grid(row=0, column=0, sticky=ttk.W, padx=(10, 0), pady=(15, 5)) + toggle_label.bind("", lambda e: self.checkbox.invoke()) + + self.checkbox = ttk.Checkbutton(self.body, text="", style="success-round-toggle") + # self.checkbox.grid(row=0, column=0, columnspan=2, sticky=ttk.W, padx=(13, 0), pady=(15, 0)) + self.checkbox.grid(row=0, column=1, sticky=ttk.E, padx=(0, 10), pady=(10, 5)) self.checkbox.configure(command=self._save_session_spoofing) + self.body.grid_columnconfigure(0, weight=1) + if self.cfg.get("session_spoofing.enabled"): self.checkbox.state(["!alternate", "selected"]) else: From 77aff60055f645602afe878dbac764f04b76bbb6 Mon Sep 17 00:00:00 2001 From: Benny <83777519+bennyscripts@users.noreply.github.com> Date: Wed, 21 Jan 2026 16:08:55 +0000 Subject: [PATCH 24/37] Add state to rounded button --- gui/components/rounded_button.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/gui/components/rounded_button.py b/gui/components/rounded_button.py index 08b74ff..51e6539 100644 --- a/gui/components/rounded_button.py +++ b/gui/components/rounded_button.py @@ -17,6 +17,8 @@ def __init__(self, parent, radius=(8, 8, 8, 8), text=None, image=None, command=N self.style = self.root.style self.padx = kwargs.get("padx", 2) self.pady = kwargs.get("pady", 0 if sys.platform != "darwin" else 1) + self.state = "normal" + self.command = command self.configure(background=self._get_parent_background()) @@ -66,11 +68,36 @@ def _darken_color(self, hex_color, factor=0.9): def _hover_enter(self, event=None): """ Apply hover effect """ + if self.state == "disabled": + return hover_color = self._darken_color(self.original_bg, 0.9) self.frame.set_background(hover_color) self.button.configure(background=hover_color) def _hover_leave(self, event=None): """ Reset to original color """ + if self.state == "disabled": + return self.frame.set_background(self.original_bg) - self.button.configure(background=self.original_bg) \ No newline at end of file + self.button.configure(background=self.original_bg) + + def set_state(self, state): + if state == "disabled": + self.state = "disabled" + self.button.state(["disabled"]) + self.frame.set_background(self.style.colors.get("secondary")) + self.button.configure(background=self.style.colors.get("secondary")) + if self.command: + self.button.unbind("") + self.frame.unbind("") + else: + self.state = "normal" + self.button.state(["!disabled"]) + self.frame.set_background(self.original_bg) + self.button.configure(background=self.original_bg) + if self.command: + self.button.bind("", self.command) + self.frame.bind("", self.command) + + def set_text(self, text): + self.button.configure(text=text) \ No newline at end of file From ba4bd52d7d4ca2934d183a1bc4bcc70cc5b58328 Mon Sep 17 00:00:00 2001 From: Benny <83777519+bennyscripts@users.noreply.github.com> Date: Wed, 21 Jan 2026 16:09:29 +0000 Subject: [PATCH 25/37] Completely redesigned settings page --- gui/components/settings/apis.py | 4 +- gui/components/settings/general.py | 6 +- gui/components/settings/snipers.py | 4 +- gui/components/settings_frame.py | 27 ++++--- gui/components/settings_panel.py | 6 +- gui/pages/settings.py | 115 ++++++++++++++++++++++++----- 6 files changed, 124 insertions(+), 38 deletions(-) diff --git a/gui/components/settings/apis.py b/gui/components/settings/apis.py index c845486..7fbf2a1 100644 --- a/gui/components/settings/apis.py +++ b/gui/components/settings/apis.py @@ -3,8 +3,8 @@ from gui.components import SettingsPanel class APIsPanel(SettingsPanel): - def __init__(self, root, parent, images, config): - super().__init__(root, parent, "APIs", images.get("apis")) + def __init__(self, root, parent, images, config, width=None): + super().__init__(root, parent, "APIs", images.get("apis"), width=width, collapsed=False) self.cfg = config self.api_keys_tk_entries = {} self.api_keys_entries = { diff --git a/gui/components/settings/general.py b/gui/components/settings/general.py index 121f51c..76532fd 100644 --- a/gui/components/settings/general.py +++ b/gui/components/settings/general.py @@ -3,8 +3,8 @@ from gui.components import SettingsPanel, DropdownMenu, RoundedButton class GeneralPanel(SettingsPanel): - def __init__(self, root, parent, bot_controller, images, config): - super().__init__(root, parent, "General", images.get("settings"), collapsed=False) + def __init__(self, root, parent, bot_controller, images, config, width=None): + super().__init__(root, parent, "General", images.get("settings"), collapsed=False, width=width) self.bot_controller = bot_controller self.cfg = config self.config_tk_entries = {} @@ -62,8 +62,6 @@ def draw(self): if index == 0: padding = (padding[0], (10, 2)) - elif index == len(self.config_entries) - 1: - padding = (padding[0], (2, 10)) label = ttk.Label(self.body, text=value) label.configure(background=self.root.style.colors.get("dark")) diff --git a/gui/components/settings/snipers.py b/gui/components/settings/snipers.py index a68d1e5..685f893 100644 --- a/gui/components/settings/snipers.py +++ b/gui/components/settings/snipers.py @@ -3,8 +3,8 @@ from gui.components import SettingsPanel, RoundedFrame class SnipersPanel(SettingsPanel): - def __init__(self, root, parent, images, config): - super().__init__(root, parent, "Snipers", images.get("snipers")) + def __init__(self, root, parent, images, config, width=None): + super().__init__(root, parent, "Snipers", images.get("snipers"), width=width, collapsed=False) self.cfg = config self.images = images self.snipers = None diff --git a/gui/components/settings_frame.py b/gui/components/settings_frame.py index 6207e7c..cf2d12e 100644 --- a/gui/components/settings_frame.py +++ b/gui/components/settings_frame.py @@ -4,12 +4,13 @@ from utils.config import Config class SettingsFrame: - def __init__(self, parent, header_text, header_icon, collapsed=False, collapsible=False): + def __init__(self, parent, header_text, header_icon, collapsed=False, collapsible=False, width=None): self.parent = parent self.root = parent.winfo_toplevel() self.hover_colour = "#282a2a" self.header_text = header_text self.header_icon = header_icon + self.width = width self.config = Config() self.collapsible = collapsible @@ -27,7 +28,7 @@ def _toggle_collapsed(self): self.body.pack_forget() else: self.header.set_corner_radius((15, 15, 0, 0)) - self.body.pack(fill=ttk.BOTH, expand=True) + self.body.pack(fill=ttk.BOTH, expand=False) def _hover_enter(self, _): self.header.set_background(background=self.hover_colour) @@ -45,11 +46,11 @@ def _draw_header(self, parent): self.title = ttk.Label(self.header, text=self.header_text, font=("Host Grotesk", 14 if sys.platform != "darwin" else 18, "bold")) self.title.configure(background=self.root.style.colors.get("secondary")) - self.title.grid(row=0, column=0, sticky=ttk.NSEW, padx=15, pady=15) + self.title.grid(row=0, column=0, sticky=ttk.NSEW, padx=15, pady=10) self.icon = ttk.Label(self.header, image=self.header_icon) self.icon.configure(background=self.root.style.colors.get("secondary")) - self.icon.grid(row=0, column=2, sticky=ttk.E, padx=(0, 15), pady=15) + self.icon.grid(row=0, column=2, sticky=ttk.E, padx=(0, 15), pady=10) self.header.grid_columnconfigure(1, weight=1) @@ -60,21 +61,27 @@ def _draw_header(self, parent): component.bind("", lambda e: self._toggle_collapsed()) def _draw_body(self, parent): - frame = RoundedFrame(parent, radius=(0, 0, 15, 15), bootstyle="dark.TFrame") - frame.pack(fill=ttk.BOTH, expand=True) + frame = RoundedFrame(parent, radius=15, bootstyle="dark.TFrame") + # frame.pack(fill=ttk.BOTH, expand=False) + frame.grid(column=0, row=5, sticky="nsew") + parent.grid_columnconfigure(0, weight=1) return frame def draw(self): - wrapper = ttk.Frame(self.parent, takefocus=True) + if self.width: + wrapper = ttk.Frame(self.parent, takefocus=True, width=self.width) + else: + wrapper = ttk.Frame(self.parent, takefocus=True) wrapper.configure(style="default.TLabel") - wrapper.pack(fill=ttk.BOTH, expand=True) + # wrapper.pack(fill=ttk.BOTH, expand=True) + # wrapper.grid(column=0, row=0, sticky="nsew") - self._draw_header(wrapper) + # self._draw_header(wrapper) self.body = self._draw_body(wrapper) if self.is_collapsed: - self.header.set_corner_radius((15, 15, 15, 15)) + # self.header.set_corner_radius((15, 15, 15, 15)) self.body.pack_forget() return self.body, wrapper \ No newline at end of file diff --git a/gui/components/settings_panel.py b/gui/components/settings_panel.py index 0309734..4641031 100644 --- a/gui/components/settings_panel.py +++ b/gui/components/settings_panel.py @@ -2,21 +2,21 @@ from gui.components import SettingsFrame class SettingsPanel: - def __init__(self, root, parent, title, icon, collapsible=True, collapsed=True): + def __init__(self, root, parent, title, icon, collapsible=False, collapsed=False, width=None): self.root = root self.parent = parent self.title = title self.icon = icon self.collapsible = collapsible self.collapsed = collapsed - + self.width = width # Create the body and wrapper upfront self.body, self.wrapper = self._create_body_and_wrapper() self.root.bind("", self._remove_focus) def _create_body_and_wrapper(self): # You can use your existing logic for creating the body and wrapper - body, wrapper = SettingsFrame(self.parent, self.title, self.icon, collapsible=self.collapsible, collapsed=self.collapsed).draw() + body, wrapper = SettingsFrame(self.parent, self.title, self.icon, collapsible=self.collapsible, collapsed=self.collapsed, width=self.width).draw() return body, wrapper def _remove_focus(self, event): diff --git a/gui/pages/settings.py b/gui/pages/settings.py index dbcd7d1..37f5cb2 100644 --- a/gui/pages/settings.py +++ b/gui/pages/settings.py @@ -1,5 +1,7 @@ +import sys import ttkbootstrap as ttk from gui.components.settings import GeneralPanel, ThemingPanel, APIsPanel, SessionSpoofingPanel, RichPresencePanel, SnipersPanel +from gui.components import RoundedFrame, DropdownMenu from gui.helpers import Images from utils.config import Config @@ -13,6 +15,19 @@ def __init__(self, root, bot_controller): self.images = Images() self.cfg = Config() self.cfg.subscribe(self) + + self.current_page = "general" + self.pages = { + "general": None, + "theming": None, + # "apis": None, + # "session_spoofing": None, + "rich_presence": None, + "snipers": None, + } + self.pills = {} + self.selected_colour = "#373737" + self.hover_colour = "#282828" def refresh_config(self): if not self.parent: @@ -24,26 +39,92 @@ def refresh_config(self): if self.parent: self.parent.update_idletasks() self.draw(self.parent) - except: - pass - - def draw(self, parent): - self.parent = parent + if self.parent: + self.root.after_idle(lambda: self.root.focus_force()) + except Exception as e: + print(f"Error refreshing config: {e}") + + def _create_sections(self, wrapper): + general_wrapper = ttk.Frame(wrapper) + + self.general = GeneralPanel(self.root, general_wrapper, self.bot_controller, self.images, self.cfg).draw() + self.session_spoofing = SessionSpoofingPanel(self.root, general_wrapper, self.images, self.cfg).draw() + self.snipers = SnipersPanel(self.root, wrapper, self.images, self.cfg).draw() + self.rpc = RichPresencePanel(self.root, wrapper, self.images, self.cfg).draw() + self.apis = APIsPanel(self.root, general_wrapper, self.images, self.cfg).draw() + self.theming = ThemingPanel(self.root, wrapper, self.images, self.cfg).draw() + + self.general.pack(fill=ttk.BOTH, expand=True, pady=(0, 10)) + self.session_spoofing.pack(fill=ttk.BOTH, expand=True, pady=(0, 10)) + self.apis.pack(fill=ttk.BOTH, expand=True, pady=(0, 10)) + + self.pages["general"] = general_wrapper + self.pages["theming"] = self.theming + # self.pages["apis"] = self.apis + # self.pages["session_spoofing"] = self.session_spoofing + self.pages["rich_presence"] = self.rpc + self.pages["snipers"] = self.snipers + + def _create_pill(self, parent, text, row, command): + def _hover_enter(_, pill, label): + if pill == self.pills[self.current_page]["pill"]: + return + pill.set_background(self.hover_colour) + label.configure(background=self.hover_colour) + def _hover_leave(_, pill, label): + if pill == self.pills[self.current_page]["pill"]: + return + pill.set_background(self.root.style.colors.get("secondary")) + label.configure(background=self.root.style.colors.get("secondary")) - general = GeneralPanel(self.root, parent, self.bot_controller, self.images, self.cfg).draw() - general.pack(fill=ttk.BOTH, expand=True, pady=(0, 15)) + pill = RoundedFrame(parent, radius=10, bootstyle="secondary.TFrame") + pill.grid(row=0, column=row, sticky=ttk.W, padx=(5, 0 if text != "Snipers" else 5), pady=5) + label = ttk.Label(pill, text=text, font=("Host Grotesk", 12 if sys.platform != "darwin" else 14)) + label.configure(background=self.root.style.colors.get("secondary")) + label.grid(row=0, column=0, sticky=ttk.W, padx=5, pady=5) + label.bind("", lambda e: command()) + pill.bind("", lambda e: command()) + pill.bind("", lambda e: _hover_enter(e, pill, label)) + pill.bind("", lambda e: _hover_leave(e, pill, label)) + label.bind("", lambda e: _hover_enter(e, pill, label)) + label.bind("", lambda e: _hover_leave(e, pill, label)) + self.pills[text.lower().replace(" ", "_")] = {"pill": pill, "label": label} - theming = ThemingPanel(self.root, parent, self.images, self.cfg).draw() - theming.pack(fill=ttk.BOTH, expand=True, pady=(0, 15)) + def toggle(self, key): + if key != self.current_page: + for page in self.pages.values(): + page.pack_forget() + self.pages[key].pack(fill=ttk.BOTH, expand=True, pady=(0, 10)) + self.current_page = key - apis = APIsPanel(self.root, parent, self.images, self.cfg).draw() - apis.pack(fill=ttk.BOTH, expand=True, pady=(0, 15)) + for pill_key, pill_components in self.pills.items(): + is_selected = pill_key == key + bg_color = self.selected_colour if is_selected else self.root.style.colors.get("secondary") + pill_components["pill"].set_background(bg_color) + pill_components["label"].configure(background=bg_color, font=("Host Grotesk", 12 if sys.platform != "darwin" else 14, "bold" if is_selected else "normal")) + + def draw(self, parent): + self.parent = parent + + self.settings_wrapper = ttk.Frame(parent) + self._create_sections(self.settings_wrapper) + + self.title = ttk.Label(parent, text="Settings", font=("Host Grotesk", 20 if sys.platform != "darwin" else 24, "bold")) + self.title.configure(background=self.root.style.colors.get("bg")) + self.title.pack(pady=(0, 10), anchor=ttk.W) + + self.pages_wrapper = RoundedFrame(parent, radius=15, bootstyle="dark.TFrame") + self.pages_wrapper.pack(anchor=ttk.W) - session_spoofing = SessionSpoofingPanel(self.root, parent, self.images, self.cfg).draw() - session_spoofing.pack(fill=ttk.BOTH, expand=True, pady=(0, 15)) + self._create_pill(self.pages_wrapper, "General", 0, lambda: self.toggle("general")) + self._create_pill(self.pages_wrapper, "Theming", 1, lambda: self.toggle("theming")) + # self._create_pill(self.pages_wrapper, "APIs", 2, lambda: self.toggle("apis")) + # self._create_pill(self.pages_wrapper, "Session Spoofing", 3, lambda: self.toggle("session_spoofing")) + self._create_pill(self.pages_wrapper, "Rich Presence", 4, lambda: self.toggle("rich_presence")) + self._create_pill(self.pages_wrapper, "Snipers", 5, lambda: self.toggle("snipers")) - rpc = RichPresencePanel(self.root, parent, self.images, self.cfg).draw() - rpc.pack(fill=ttk.BOTH, expand=True, pady=(0, 15)) + # ------- - snipers = SnipersPanel(self.root, parent, self.images, self.cfg).draw() - snipers.pack(fill=ttk.BOTH, expand=True) \ No newline at end of file + self.settings_wrapper.pack(fill=ttk.BOTH, expand=True, pady=(10, 0)) + self.pages[self.current_page].pack(fill=ttk.BOTH, expand=True, pady=(0, 10)) + self.toggle(self.current_page) \ No newline at end of file From 019dd3efffa1ffc31ae6baeff30e2b70d1474c71 Mon Sep 17 00:00:00 2001 From: Benny <83777519+bennyscripts@users.noreply.github.com> Date: Wed, 21 Jan 2026 16:09:50 +0000 Subject: [PATCH 26/37] Change button radius and colour --- gui/components/sidebar.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gui/components/sidebar.py b/gui/components/sidebar.py index e730f4d..1b23988 100644 --- a/gui/components/sidebar.py +++ b/gui/components/sidebar.py @@ -24,12 +24,12 @@ def set_current_page(self, page_name): self.current_page = page_name def _hover_enter(self, button_wrapper, button, page_name): - background = "#242424" if self.current_page != page_name else self.root.style.colors.get("secondary") + background = self.root.style.colors.get("secondary") if self.current_page != page_name else "#1F1F1F" button.configure(background=background) button_wrapper.set_background(background) def _hover_leave(self, button_wrapper, button, page_name): - background = Style.WINDOW_BORDER.value if self.current_page != page_name else self.root.style.colors.get("secondary") + background = Style.WINDOW_BORDER.value if self.current_page != page_name else "#1F1F1F" button.configure(background=background) button_wrapper.set_background(background) @@ -39,7 +39,7 @@ def _create_button(self, image, page_name, command, row): button_wrapper = RoundedFrame( self.sidebar, - radius=15, + radius=20, background=bg_color ) From 41d27c3b9690f0b0116245363ef7a69c64efef32 Mon Sep 17 00:00:00 2001 From: Benny <83777519+bennyscripts@users.noreply.github.com> Date: Wed, 21 Jan 2026 16:10:11 +0000 Subject: [PATCH 27/37] Add right-chevron-tiny --- gui/helpers/images.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/gui/helpers/images.py b/gui/helpers/images.py index 01e1729..19ec0b6 100644 --- a/gui/helpers/images.py +++ b/gui/helpers/images.py @@ -73,9 +73,9 @@ def _load_images(self): ICON_CONFIG = { "bigger": ["scripts"], - "small": ["trash", "github", "restart", "checkmark", "left-chevron", "file-signature", "trash-white", "right-chevron", "titlebar-ico"], - "tiny": ["submit", "max", "min", "search"], - "smaller": ["folder-open", "plus", "reset", "play", "stop"], + "small": ["trash", "github", "restart", "checkmark", "left-chevron", "file-signature", "trash-white", "titlebar-ico"], + "tiny": ["submit", "max", "min", "search", "right-chevron-tiny"], + "smaller": ["folder-open", "plus", "reset", "play", "stop", "right-chevron"], "logo": ["ghost-logo"], } @@ -105,6 +105,7 @@ def _load_images(self): "file-signature": "data/icons/file-signature-solid.png", "left-chevron": "data/icons/chevron-left-solid.png", "right-chevron": "data/icons/chevron-right-solid.png", + "right-chevron-tiny": "data/icons/chevron-right-solid.png", "tools": "data/icons/screwdriver-wrench-solid.png", "reset": "data/icons/rotate-left-solid.png", "play": "data/icons/play-solid.png", From cb7469d3ba315b42baa65eb897127d69ff807cbb Mon Sep 17 00:00:00 2001 From: Benny <83777519+bennyscripts@users.noreply.github.com> Date: Wed, 21 Jan 2026 16:10:52 +0000 Subject: [PATCH 28/37] Change size of inactive window controls on mac os --- gui/components/titlebar.py | 4 ++-- gui/components/tool_page.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gui/components/titlebar.py b/gui/components/titlebar.py index 6bb832c..df404ea 100644 --- a/gui/components/titlebar.py +++ b/gui/components/titlebar.py @@ -109,14 +109,14 @@ def draw(self): close_btn.bind("", lambda e: close_btn.configure(foreground="#CC4940")) close_btn.bind("", lambda e: close_btn.configure(foreground="#FF5F57")) - minimize_btn = ttk.Label(inner_wrapper, text="●", foreground="#4f4c4c", font=("Arial", 25)) + minimize_btn = ttk.Label(inner_wrapper, text="●", foreground="#4f4c4c", font=("Arial", 28)) minimize_btn.configure(background=Style.WINDOW_BORDER.value) minimize_btn.pack(side=ttk.LEFT, padx=(0, 0)) # minimize_btn.bind("", lambda e: self._minimize()) # minimize_btn.bind("", lambda e: minimize_btn.configure(foreground="#CC9A26")) # minimize_btn.bind("", lambda e: minimize_btn.configure(foreground="#FFBD2E")) - maximize_btn = ttk.Label(inner_wrapper, text="●", foreground="#4f4c4c", font=("Arial", 25)) + maximize_btn = ttk.Label(inner_wrapper, text="●", foreground="#4f4c4c", font=("Arial", 28)) maximize_btn.configure(background=Style.WINDOW_BORDER.value) maximize_btn.pack(side=ttk.LEFT, padx=(0, 5)) # maximize_btn.bind("", lambda e: maximize_btn.configure(foreground="#20A833")) diff --git a/gui/components/tool_page.py b/gui/components/tool_page.py index 21f60ac..c6e56a6 100644 --- a/gui/components/tool_page.py +++ b/gui/components/tool_page.py @@ -3,7 +3,7 @@ import ttkbootstrap as ttk from gui.components import RoundedFrame -class ToolPage(abc.ABC): +class ToolPage: def __init__(self, toolspage, root, bot_controller, images, layout, title, frame=True): self.toolspage = toolspage self.root = root From 906c1e82debc61c54bf0165c255ac1f9ef35d05c Mon Sep 17 00:00:00 2001 From: Benny <83777519+bennyscripts@users.noreply.github.com> Date: Wed, 21 Jan 2026 16:11:11 +0000 Subject: [PATCH 29/37] Add lift to rounded frame --- gui/components/rounded_frame.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gui/components/rounded_frame.py b/gui/components/rounded_frame.py index cd3f36a..0d2c902 100644 --- a/gui/components/rounded_frame.py +++ b/gui/components/rounded_frame.py @@ -105,4 +105,7 @@ def set_width(self, width): def bind(self, sequence=None, func=None, add=None): if sequence == "": return super().bind(sequence, self.on_resize, add=add) - return super().bind(sequence, func, add=add) \ No newline at end of file + return super().bind(sequence, func, add=add) + + def lift(self): + self.master.tk.call("raise", self._w) \ No newline at end of file From e1ae99143a62839b0c40ebb737ecc4ab99f403d3 Mon Sep 17 00:00:00 2001 From: Benny <83777519+bennyscripts@users.noreply.github.com> Date: Wed, 21 Jan 2026 16:13:05 +0000 Subject: [PATCH 30/37] Add version to Mac OS Info.plist --- compile.py | 54 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/compile.py b/compile.py index 1ef34d5..9810ed9 100644 --- a/compile.py +++ b/compile.py @@ -1,18 +1,40 @@ import os -import sys import platform import subprocess +import plistlib + +from utils.config import VERSION + + +def patch_macos_plist(app_name): + plist_path = os.path.join("dist", f"{app_name}.app", "Contents", "Info.plist") + + if not os.path.exists(plist_path): + print("Info.plist not found, skipping version patch") + return + + with open(plist_path, "rb") as f: + plist = plistlib.load(f) + + plist["CFBundleShortVersionString"] = VERSION # About menu + plist["CFBundleVersion"] = VERSION # build number + + with open(plist_path, "wb") as f: + plistlib.dump(plist, f) + + print(f"Patched Info.plist with version {VERSION}") + def build(): system = platform.system() - + name = "Ghost" entry_script = "ghost.py" icon = "data/icon-win.png" if system == "Windows" else "data/icon.png" - common_args = [ + args = [ "pyinstaller", - "--name=" + name, + f"--name={name}", "--onefile", "--windowed", "--noconsole", @@ -25,32 +47,28 @@ def build(): entry_script ] - # Add paths to site-packages if needed if system == "Windows": - site_packages = ".venv\\Lib\\site-packages" - add_data = [ + args += [ + "--paths=.venv\\Lib\\site-packages", "--add-data=data\\*;data", "--add-data=data\\fonts\\*;data/fonts", "--add-data=data\\icons\\*;data/icons" ] else: - site_packages = ".venv/lib/python3.10/site-packages" - add_data = [ + args += [ + "--paths=.venv/lib/python3.10/site-packages", "--add-data=data/*:data", "--add-data=data/fonts/*:data/fonts", - "--add-data=data/icons/*:data/icons" + "--add-data=data/icons/*:data/icons", + "--osx-bundle-identifier=fun.benny.ghost" ] - - common_args.append(f"--paths={site_packages}") - common_args += add_data - # macOS-specific option + print(f"🔨 Building Ghost {VERSION} for {system}...") + subprocess.run(args, check=True) + if system == "Darwin": - common_args.append("--osx-bundle-identifier=fun.benny.ghost") + patch_macos_plist(name) - # Run the command - print(f"Building for {system}...") - subprocess.run(common_args) if __name__ == "__main__": build() From 1db6f74e106ca2ff7d50e4494f9d181246613d2d Mon Sep 17 00:00:00 2001 From: Benny <83777519+bennyscripts@users.noreply.github.com> Date: Thu, 22 Jan 2026 12:45:38 +0000 Subject: [PATCH 31/37] Add page title to scripts page --- gui/pages/scripts.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/gui/pages/scripts.py b/gui/pages/scripts.py index 4848ee6..3ad60d5 100644 --- a/gui/pages/scripts.py +++ b/gui/pages/scripts.py @@ -317,6 +317,11 @@ def draw(self, parent): self.restart_warning.pack(fill=ttk.X, side=ttk.TOP, pady=(10, 0)) self.restart_warning.pack_forget() + title = ttk.Label(parent, text="Scripts", font=("Host Grotesk", 20 if sys.platform != "darwin" else 24, "bold")) + title.configure(background=self.root.style.colors.get("bg")) + title.pack(pady=(0, 15), anchor=ttk.W) + # title.grid(row=0, column=0, sticky=ttk.W, pady=(0, 15)) + header = self._draw_header(parent) header.pack(fill=ttk.X) From ee201ffb21d27d40f87dd36c912a52945eb58f31 Mon Sep 17 00:00:00 2001 From: Benny <83777519+bennyscripts@users.noreply.github.com> Date: Thu, 22 Jan 2026 12:45:48 +0000 Subject: [PATCH 32/37] Move scripts page to bottom of sidebar --- gui/components/sidebar.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gui/components/sidebar.py b/gui/components/sidebar.py index 1b23988..a5cc5b2 100644 --- a/gui/components/sidebar.py +++ b/gui/components/sidebar.py @@ -97,8 +97,8 @@ def draw(self): "home": self._create_button(self.images.get("home"), "home", self.button_cmds["home"], 0), # "console": self._create_button(self.images.get("console"), "console", self.button_cmds["console"], 1), "settings": self._create_button(self.images.get("settings"), "settings", self.button_cmds["settings"], 1), - "scripts": self._create_button(self.images.get("scripts"), "scripts", self.button_cmds["scripts"], 2), - "tools": self._create_button(self.images.get("tools"), "tools", self.button_cmds["tools"], 3), + "tools": self._create_button(self.images.get("tools"), "tools", self.button_cmds["tools"], 2), + "scripts": self._create_button(self.images.get("scripts"), "scripts", self.button_cmds["scripts"], 3), } logout_btn = ttk.Label(self.sidebar, image=self.images.get("logout"), background=Style.WINDOW_BORDER.value, anchor="center") From d88e732fbfd483d3337e1d45c6346fc013c1f595 Mon Sep 17 00:00:00 2001 From: Benny <83777519+bennyscripts@users.noreply.github.com> Date: Thu, 22 Jan 2026 12:46:03 +0000 Subject: [PATCH 33/37] Redesign tools to be a grid --- gui/pages/tools/tools.py | 72 ++++++++++++++++++++++++++-------------- 1 file changed, 47 insertions(+), 25 deletions(-) diff --git a/gui/pages/tools/tools.py b/gui/pages/tools/tools.py index 6477f0e..8139882 100644 --- a/gui/pages/tools/tools.py +++ b/gui/pages/tools/tools.py @@ -22,19 +22,19 @@ def __init__(self, root, bot_controller, images, layout, position_resize_grips): self.pages = [ { "name": "ghetto spy.pet", - "description": "A tool to look up every message sent by a user you share mutual servers with.", + "description": "Get messages sent by a user in mutual guilds", "page": self.spypet_page, "command": self.draw_spypet }, { "name": "Message Logger", - "description": "A tool to log deleted messages from every server you're in.", + "description": "Logs every deleted message sent", "page": self.message_logger_page, "command": self.draw_message_logger }, { "name": "User Lookup", - "description": "A tool to look up a user's information.", + "description": "Look up information about a user by their ID", "page": self.user_lookup_page, "command": self.draw_user_lookup } @@ -82,27 +82,49 @@ def on_leave(_): widget.bind("", on_enter) widget.bind("", on_leave) - def draw(self, parent): - for page in self.pages: - page_wrapper = RoundedFrame(parent, radius=15, bootstyle="secondary.TFrame") - page_wrapper.pack(fill="x", expand=True, pady=(0, 10)) - page_wrapper.bind("", lambda e, cmd=page["command"]: cmd()) + def _draw_page_card(self, parent, page): + page_wrapper = RoundedFrame(parent, radius=15, bootstyle="secondary.TFrame") + page_wrapper.bind("", lambda e, cmd=page["command"]: cmd()) - page_title = ttk.Label(page_wrapper, text=page["name"], font=("Host Grotesk", 14 if sys.platform != "darwin" else 18, "bold")) - page_title.configure(background=self.root.style.colors.get("secondary")) - page_title.grid(row=0, column=0, sticky=ttk.NSEW, padx=15, pady=10) - page_title.bind("", lambda e, cmd=page["command"]: cmd()) + page_title = ttk.Label(page_wrapper, text=page["name"], font=("Host Grotesk", 14 if sys.platform != "darwin" else 18, "bold"), justify=ttk.CENTER) + page_title.configure(background=self.root.style.colors.get("secondary")) + page_title.grid(row=0, column=0, pady=(25, 5)) + page_title.bind("", lambda e, cmd=page["command"]: cmd()) - # page_description = ttk.Label(page_wrapper, text=page["description"], font=("Host Grotesk", 12 if sys.platform != "darwin" else 16), wraplength=450) - # page_description.configure(background=self.root.style.colors.get("secondary")) - # page_description.grid(row=1, column=0, sticky=ttk.NSEW, padx=15, pady=(0, 15)) - # page_description.bind("", lambda e, cmd=page["command"]: cmd()) - - page_icon = ttk.Label(page_wrapper, image=self.images.get("right-chevron")) - page_icon.configure(background=self.root.style.colors.get("secondary")) - page_icon.grid(row=0, column=1, sticky=ttk.E, padx=(0, 20), pady=15) - - page_wrapper.grid_columnconfigure(1, weight=1) - self._bind_hover_effects(page_wrapper, [page_title, page_wrapper, page_icon], self.hover_colour, self.root.style.colors.get("secondary")) - self._bind_hover_effects(page_title, [page_title, page_wrapper, page_icon], self.hover_colour, self.root.style.colors.get("secondary")) - self._bind_hover_effects(page_icon, [page_title, page_wrapper, page_icon], self.hover_colour, self.root.style.colors.get("secondary")) \ No newline at end of file + page_description = ttk.Label(page_wrapper, text=page["description"], wraplength=150, justify=ttk.CENTER) + page_description.configure(background=self.root.style.colors.get("secondary")) + page_description.grid(row=1, column=0, pady=(0, 25)) + page_description.bind("", lambda e, cmd=page["command"]: cmd()) + + # page_icon = ttk.Label(page_wrapper, image=self.images.get("right-chevron")) + # page_icon.configure(background=self.root.style.colors.get("secondary")) + # page_icon.grid(row=0, column=1, sticky=ttk.E, padx=(0, 20), pady=15) + + page_wrapper.grid_columnconfigure(0, weight=1) + page_wrapper.grid_rowconfigure(0, weight=1) + page_wrapper.grid_rowconfigure(1, weight=1) + self._bind_hover_effects(page_wrapper, [page_title, page_wrapper, page_description], self.hover_colour, self.root.style.colors.get("secondary")) + self._bind_hover_effects(page_title, [page_title, page_wrapper, page_description], self.hover_colour, self.root.style.colors.get("secondary")) + self._bind_hover_effects(page_description, [page_title, page_wrapper, page_description], self.hover_colour, self.root.style.colors.get("secondary")) + # self._bind_hover_effects(page_icon, [page_title, page_wrapper, page_icon], self.hover_colour, self.root.style.colors.get("secondary")) + + return page_wrapper + + def draw(self, parent): + title = ttk.Label(parent, text="Tools", font=("Host Grotesk", 20 if sys.platform != "darwin" else 24, "bold")) + title.configure(background=self.root.style.colors.get("bg")) + # title.pack(pady=(0, 15), anchor=ttk.W) + title.grid(row=0, column=0, sticky=ttk.W, pady=(0, 15)) + + parent.grid_columnconfigure(0, weight=1) + parent.grid_columnconfigure(1, weight=1) + + # create a grid for the page cards, two columns + row, col = 1, 0 + for page in self.pages: + card = self._draw_page_card(parent, page) + card.grid(row=row, column=col, sticky=ttk.NSEW, padx=5, pady=5) + col += 1 + if col > 1: + col = 0 + row += 1 \ No newline at end of file From 9c6cdb27c9967df3c571399f9e0f612e254f7afa Mon Sep 17 00:00:00 2001 From: Benny <83777519+bennyscripts@users.noreply.github.com> Date: Thu, 22 Jan 2026 15:54:30 +0000 Subject: [PATCH 34/37] Remove .lower from embeds --- bot/helpers/cmdhelper.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/bot/helpers/cmdhelper.py b/bot/helpers/cmdhelper.py index 7b6c218..99c3328 100644 --- a/bot/helpers/cmdhelper.py +++ b/bot/helpers/cmdhelper.py @@ -149,7 +149,7 @@ async def send_message(ctx, embed_obj: dict, extra_title="", extra_message="", d msg_style = "codeblock" if msg_style == "codeblock": - description = re.sub(r"[*_~`]", "", codeblock_desc).lower() + description = re.sub(r"[*_~`]", "", codeblock_desc) if title == theme.title: title = f"{theme.emoji} {title}" @@ -159,17 +159,17 @@ async def send_message(ctx, embed_obj: dict, extra_title="", extra_message="", d msg = await ctx.send( str(codeblock.Codeblock( - title=title.lower(), - description=description.lower(), - extra_title=extra_title.lower(), - # footer=footer.lower() + title=title, + description=description, + extra_title=extra_title, + # footer=footer )), delete_after=delete_after ) elif msg_style == "image": title = remove_emojis(title.replace(theme.emoji, "").lstrip()) - embed2 = imgembed.Embed(title=title.lower(), description=description.lower(), colour=colour) + embed2 = imgembed.Embed(title=title, description=description, colour=colour) embed2.set_footer(text=footer) embed2.set_thumbnail(url=thumbnail) embed_file = embed2.save() @@ -181,8 +181,8 @@ async def send_message(ctx, embed_obj: dict, extra_title="", extra_message="", d if title == theme.title: title = f"{theme.emoji} {title}" embed = discord.Embed( - title=title.lower(), - description=description.lower(), + title=title, + description=description, colour=discord.Color.from_str(colour) ) embed.set_footer(text=footer) From 263320baf66da699bb52a55adb88ab4a3d699b67 Mon Sep 17 00:00:00 2001 From: Benny <83777519+bennyscripts@users.noreply.github.com> Date: Thu, 22 Jan 2026 15:54:56 +0000 Subject: [PATCH 35/37] Add Key to SerpAPI label --- gui/components/settings/apis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/components/settings/apis.py b/gui/components/settings/apis.py index 7fbf2a1..19984dd 100644 --- a/gui/components/settings/apis.py +++ b/gui/components/settings/apis.py @@ -8,7 +8,7 @@ def __init__(self, root, parent, images, config, width=None): self.cfg = config self.api_keys_tk_entries = {} self.api_keys_entries = { - "serpapi": "SerpAPI" + "serpapi": "SerpAPI Key" } def _save_api_keys(self): From 85a31b240d4c6d406ca26e86dbcb6690a3cfbc91 Mon Sep 17 00:00:00 2001 From: Benny <83777519+bennyscripts@users.noreply.github.com> Date: Thu, 22 Jan 2026 15:55:18 +0000 Subject: [PATCH 36/37] Add load_image_from_url method --- gui/helpers/images.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/gui/helpers/images.py b/gui/helpers/images.py index 19ec0b6..3e90dd4 100644 --- a/gui/helpers/images.py +++ b/gui/helpers/images.py @@ -1,10 +1,12 @@ from PIL import Image, ImageTk, ImageFilter, ImageEnhance import os import threading +import requests from utils.files import resource_path import requests from io import BytesIO from collections import Counter +from bot.helpers import imgembed def resize_and_sharpen(image, size): try: @@ -189,4 +191,12 @@ def get_majority_color_from_url(self, image_url: str) -> str: counter = Counter(filtered_pixels) most_common = counter.most_common(1)[0][0] - return '#{:02x}{:02x}{:02x}'.format(*most_common) \ No newline at end of file + return '#{:02x}{:02x}{:02x}'.format(*most_common) + + def load_image_from_url(self, image_url: str, size: tuple, radius=10) -> ImageTk.PhotoImage: + response = requests.get(image_url) + image = Image.open(BytesIO(response.content)).convert("RGBA") + image = resize_and_sharpen(image, size) + if radius > 0: + image = imgembed.add_corners(image, radius) + return ImageTk.PhotoImage(image) \ No newline at end of file From 8568608cfa7af8c585704c8488cb3f0fe119643e Mon Sep 17 00:00:00 2001 From: Benny <83777519+bennyscripts@users.noreply.github.com> Date: Thu, 22 Jan 2026 15:55:35 +0000 Subject: [PATCH 37/37] Add rich presence preview to gui --- gui/components/settings/rich_presence.py | 171 ++++++++++++++++++++++- gui/components/settings_frame.py | 2 +- gui/pages/settings.py | 2 +- 3 files changed, 170 insertions(+), 5 deletions(-) diff --git a/gui/components/settings/rich_presence.py b/gui/components/settings/rich_presence.py index f3e8787..107c989 100644 --- a/gui/components/settings/rich_presence.py +++ b/gui/components/settings/rich_presence.py @@ -4,7 +4,7 @@ from gui.components import SettingsPanel, RoundedButton, RoundedFrame class RichPresencePanel(SettingsPanel): - def __init__(self, root, parent, images, config, width=None): + def __init__(self, root, parent, images, config, width=None, bot_controller=None): super().__init__(root, parent, "Rich Presence", images.get("rich_presence"), width=width, collapsed=False) self.cfg = config self.rpc = self.cfg.get_rich_presence() @@ -37,6 +37,28 @@ def __init__(self, root, parent, images, config, width=None): "large_url": self.rpc.large_url, "small_url": self.rpc.small_url } + self.bot_controller = bot_controller + self.images = images + self.user_banner_colour = None + self.user_avatar = None + self.user = None + self.preview_vars = { + "name": ttk.StringVar(value=self.rpc.name), + "details": ttk.StringVar(value=self.rpc.details), + "state": ttk.StringVar(value=self.rpc.state), + "large_image": ttk.StringVar(value=self.rpc.large_image) + } + + self.large_image_label = None + self.name_label = None + self.details_label = None + self.state_label = None + + self._preview_after_id = None + self.PREVIEW_DEBOUNCE_MS = 500 # adjust if needed + + for var in self.preview_vars.values(): + var.trace_add("write", self._schedule_preview_update) def _save_rpc(self): for index, (key, value) in enumerate(self.rpc_entries.items()): @@ -51,6 +73,128 @@ def _save_rpc(self): def _reset_rpc(self): self.rpc.reset_defaults() + def _schedule_preview_update(self, *_): + if self._preview_after_id is not None: + self.root.after_cancel(self._preview_after_id) + + self._preview_after_id = self.root.after( + self.PREVIEW_DEBOUNCE_MS, + self._update_preview + ) + + def _update_preview(self): + try: + name = self.preview_vars["name"].get() or "Ghost" + details = self.preview_vars["details"].get().strip() + state = self.preview_vars["state"].get().strip() + large_image_url = self.preview_vars["large_image"].get().strip() + if large_image_url == "" or large_image_url.lower() == "none": + large_image_url = "https://www.ghostt.cc/assets/ghost.png" + + self.name_label.configure(text=name) + + row = 1 # start under the name + + if details: + self.details_label.configure(text=details) + self.details_label.grid(row=row, column=0, sticky=ttk.W) + row += 1 + else: + self.details_label.grid_remove() + + if state: + self.state_label.configure(text=state) + self.state_label.grid(row=row, column=0, sticky=ttk.W) + row += 1 + else: + self.state_label.grid_remove() + + if large_image_url: + large_img = self.images.load_image_from_url(large_image_url, (64, 64), 5) + if large_img: + self.large_image_label.configure(image=large_img) + self.large_image_label.image = large_img + + except Exception as e: + print(f"Error updating RPC preview: {e}") + + def _draw_preview(self, parent): + wrapper = RoundedFrame(parent, radius=15, background="#252526") + wrapper.pack(fill=ttk.X, expand=False, pady=(10, 0), padx=10) + + playing_label = ttk.Label(wrapper, text="Playing", font=("Host Grotesk", 10 if sys.platform != "darwin" else 12)) + playing_label.configure(background="#252526") + playing_label.grid(row=0, column=0, sticky=ttk.W, padx=(5, 5), pady=(5, 0)) + + large_image = self.images.load_image_from_url(self.rpc.large_image if self.rpc.large_image else "https://www.ghostt.cc/assets/ghost.png", (64, 64), 5) + self.large_image_label = ttk.Label(wrapper, image=large_image, background="#252526") + self.large_image_label.image = large_image + self.large_image_label.grid(row=1, column=0, padx=(5, 5), pady=5) + + # small_image = self.images.load_image_from_url(self.rpc.small_image if self.rpc.small_image else "https://www.ghostt.cc/assets/ghost.png", (28, 28), 12) + # self.small_image_label = ttk.Label(wrapper, image=small_image, background="#252526") + # self.small_image_label.image = small_image + # self.small_image_label.place(x=50, y=70, width=28, height=28) + # self.small_image_label.lift() + + details_wrapper = RoundedFrame(wrapper, radius=0, background="#252526") + details_wrapper.grid(row=1, column=1, sticky=ttk.W, padx=(0, 5), pady=5) + + self.name_label = ttk.Label(details_wrapper, text=self.rpc.name or "Ghost", font=("Host Grotesk", 12 if sys.platform != "darwin" else 14, "bold")) + self.name_label.configure(background="#252526") + self.name_label.grid(row=0, column=0, sticky=ttk.W) + + self.details_label = ttk.Label(details_wrapper, text=self.rpc.details or "", font=("Host Grotesk", 10 if sys.platform != "darwin" else 12)) + self.details_label.configure(background="#252526") + self.details_label.grid(row=1, column=0, sticky=ttk.W) + + self.state_label = ttk.Label(details_wrapper, text=self.rpc.state or "", font=("Host Grotesk", 10 if sys.platform != "darwin" else 12)) + self.state_label.configure(background="#252526") + self.state_label.grid(row=2, column=0, sticky=ttk.W) + + time_elapsed_label = ttk.Label(details_wrapper, text="00:15", font=("Host Grotesk", 10 if sys.platform != "darwin" else 12)) + time_elapsed_label.configure(foreground="#68ae7c", background="#252526") + time_elapsed_label.grid(row=3, column=0, sticky=ttk.W) + + def _draw_user_wrapper(self, parent): + wrapper = RoundedFrame(parent, radius=(15, 15, 15, 15), bootstyle="dark.TFrame", custom_size=True) + wrapper.set_width(225) + wrapper.grid(row=1, column=1, sticky=ttk.NSEW, padx=(10, 0)) + + if self.user_avatar: + accent_colour_banner = RoundedFrame(wrapper, radius=(15, 15, 0, 0), background=self.user_banner_colour, parent_background=self.root.style.colors.get("bg")) + accent_colour_banner.set_height(85) + accent_colour_banner.pack(side=ttk.TOP, fill=ttk.X) + accent_colour_banner.columnconfigure(0, weight=1) + + avatar_label = ttk.Canvas(wrapper, width=100, height=200, background=self.user_banner_colour, highlightthickness=0) + + avatar_label.create_rectangle(0, 0, 100, 50, fill=self.user_banner_colour, outline="") + avatar_label.create_rectangle(0, 50, 100, 200, fill=self.root.style.colors.get("dark"), outline="") + + # create an ovel the same size of the avatar but an extra 5px on each side and use the dark background color, this is to create a border + avatar_label.create_oval(9, 8, 85, 85, fill=self.root.style.colors.get("dark"), outline="") + avatar_label.create_image(65//2 + 15, 65//2 + 15, image=self.user_avatar, anchor="center") + + avatar_label.place(x=0, y=85-50, width=100, height=200) + + if self.user: + user_info_wrapper = ttk.Frame(wrapper, style="dark.TFrame") + user_info_wrapper.pack(side=ttk.TOP, fill=ttk.X, pady=(35, 0), padx=(10, 10)) + user_info_wrapper.configure(height=50) + + display_name = ttk.Label(user_info_wrapper, text=self.user.display_name, font=("Host Grotesk", 16 if sys.platform != "darwin" else 18, "bold")) + display_name.configure(background=self.root.style.colors.get("dark")) + display_name.place(relx=0, rely=0) + + username = ttk.Label(user_info_wrapper, text=f"{self.user.name}", font=("Host Grotesk", 12 if sys.platform != "darwin" else 14)) + username.configure(background=self.root.style.colors.get("dark"), foreground="lightgrey") + username.place(relx=0, rely=0.42) + + self._draw_preview(wrapper) + + return wrapper + def draw(self): toggle_wrapper = RoundedFrame(self.wrapper, radius=(10, 10, 10, 10), bootstyle="dark.TFrame") toggle_wrapper.grid(row=0, column=0, columnspan=4, sticky="we", pady=(0, 10)) @@ -67,6 +211,13 @@ def draw(self): toggle_wrapper.grid_columnconfigure(0, weight=1) + self.user = self.bot_controller.get_user() + if self.user: + avatar_url = self.user.avatar.url if self.user and self.user.avatar else "https://ia600305.us.archive.org/31/items/discordprofilepictures/discordblue.png" + self.user_avatar = self.bot_controller.get_avatar_from_url(avatar_url, size=65, radius=65//2) + self.user_banner_colour = self.images.get_majority_color_from_url(avatar_url) + self._draw_user_wrapper(self.wrapper) + if self.rpc.enabled: self.toggle_checkbox.state(["!alternate", "selected"]) else: @@ -80,8 +231,21 @@ def draw(self): continue rpc_value = self.rpc.get(key) - entry = ttk.Entry(self.body, bootstyle="secondary", font=("Host Grotesk",)) - entry.insert(0, rpc_value) + if key in self.preview_vars: + entry = ttk.Entry( + self.body, + bootstyle="secondary", + textvariable=self.preview_vars[key], + font=("Host Grotesk",) + ) + else: + entry = ttk.Entry( + self.body, + bootstyle="secondary", + font=("Host Grotesk",) + ) + entry.insert(0, rpc_value) + entry.bind("", lambda event: self._save_rpc()) entry.bind("", lambda event: self._save_rpc()) @@ -105,4 +269,5 @@ def draw(self): reset_rpc_button = RoundedButton(self.body, text="Reset", style="danger.TButton", command=self._reset_rpc) reset_rpc_button.grid(row=len(self.rpc_entries) + 1, column=3, sticky=ttk.E, padx=(5, 11), pady=10) + self._update_preview() return self.wrapper \ No newline at end of file diff --git a/gui/components/settings_frame.py b/gui/components/settings_frame.py index cf2d12e..5aaf136 100644 --- a/gui/components/settings_frame.py +++ b/gui/components/settings_frame.py @@ -63,7 +63,7 @@ def _draw_header(self, parent): def _draw_body(self, parent): frame = RoundedFrame(parent, radius=15, bootstyle="dark.TFrame") # frame.pack(fill=ttk.BOTH, expand=False) - frame.grid(column=0, row=5, sticky="nsew") + frame.grid(column=0, row=1, sticky="nsew") parent.grid_columnconfigure(0, weight=1) return frame diff --git a/gui/pages/settings.py b/gui/pages/settings.py index 37f5cb2..7012940 100644 --- a/gui/pages/settings.py +++ b/gui/pages/settings.py @@ -50,7 +50,7 @@ def _create_sections(self, wrapper): self.general = GeneralPanel(self.root, general_wrapper, self.bot_controller, self.images, self.cfg).draw() self.session_spoofing = SessionSpoofingPanel(self.root, general_wrapper, self.images, self.cfg).draw() self.snipers = SnipersPanel(self.root, wrapper, self.images, self.cfg).draw() - self.rpc = RichPresencePanel(self.root, wrapper, self.images, self.cfg).draw() + self.rpc = RichPresencePanel(self.root, wrapper, self.images, self.cfg, bot_controller=self.bot_controller).draw() self.apis = APIsPanel(self.root, general_wrapper, self.images, self.cfg).draw() self.theming = ThemingPanel(self.root, wrapper, self.images, self.cfg).draw()