diff --git a/app/tools/cs2_patterns.py b/app/tools/cs2_patterns.py index de91936..1d7a869 100644 --- a/app/tools/cs2_patterns.py +++ b/app/tools/cs2_patterns.py @@ -9,6 +9,7 @@ from pathlib import Path from typing import Any, Iterable, Optional import tkinter as tk +import tkinter.font as tkfont from tkinter import filedialog, messagebox, ttk import requests @@ -177,14 +178,16 @@ class CS2PatternTool(tk.Toplevel): super().__init__(app.root) self.app = app self.fetcher = CS2PatternFetcher() + self.theme = getattr(self.app, "theme", "light") self.title(self._t("cs2.title")) - self.geometry("540x360") + self.geometry("560x380") self.minsize(520, 320) self.resizable(True, True) self._drag_offset: tuple[int, int] | None = None self._setup_window() self.body = ttk.Frame(self) self.body.pack(fill=tk.BOTH, expand=True, padx=16, pady=(12, 16)) + self._toolbar_buttons: list[dict[str, Any]] = [] self.weapons_var = tk.StringVar() self.patterns_var = tk.StringVar() @@ -206,6 +209,11 @@ class CS2PatternTool(tk.Toplevel): def _init_widgets(self) -> None: frame = self.body + toolbar = ttk.Frame(frame) + toolbar.pack(fill=tk.X, pady=(0, 16)) + for icon, label, command in self._toolbar_button_defs(): + self._add_toolbar_button(toolbar, icon, label, command) + top = ttk.Frame(frame) top.pack(fill=tk.X, pady=(0, 12)) ttk.Label(top, text=self._t("cs2.weapon_label")).grid(row=0, column=0, sticky="w") @@ -233,28 +241,13 @@ class CS2PatternTool(tk.Toplevel): ) entry = ttk.Entry(dir_frame, textvariable=self.directory_var) entry.grid(row=1, column=0, sticky="we", padx=(0, 8)) - ttk.Button( - dir_frame, text=self._t("cs2.browse_button"), command=self._browse_directory - ).grid(row=1, column=1, sticky="e") dir_frame.columnconfigure(0, weight=1) - buttons = ttk.Frame(frame) - buttons.pack(fill=tk.X, pady=(0, 12)) - ttk.Button( - buttons, text=self._t("cs2.refresh_button"), command=self._refresh_data - ).pack(side=tk.LEFT) - self.download_btn = ttk.Button( - buttons, - text=self._t("cs2.download_button"), - command=self._download_selected, - state="disabled", - ) - self.download_btn.pack(side=tk.RIGHT) - status_label = ttk.Label( frame, textvariable=self.status_var, anchor="w", justify="left" ) status_label.pack(fill=tk.X) + self._refresh_toolbar_buttons_theme() def _setup_window(self) -> None: self.overrideredirect(True) @@ -388,10 +381,8 @@ class CS2PatternTool(tk.Toplevel): self.pattern_combo.configure(state="readonly", values=patterns) if patterns: self.pattern_combo.set(patterns[0]) - self.download_btn.configure(state="normal") else: self.pattern_combo.set("") - self.download_btn.configure(state="disabled") # Event handlers --------------------------------------------------- @@ -401,8 +392,7 @@ class CS2PatternTool(tk.Toplevel): self._populate_patterns(weapon) def _on_pattern_selected(self, event=None) -> None: # noqa: ANN001 - if self.patterns_var.get(): - self.download_btn.configure(state="normal") + return def _browse_directory(self) -> None: directory = filedialog.askdirectory(parent=self, mustexist=True) @@ -467,8 +457,187 @@ class CS2PatternTool(tk.Toplevel): y = event.y_root - offset[1] self.geometry(f"+{x}+{y}") + def _toolbar_button_defs(self) -> list[tuple[str, str, Any]]: + return [ + ("🔄", self._t("cs2.refresh_button"), self._refresh_data), + ("⬇", self._t("cs2.download_button"), self._download_selected), + ("📁", self._t("cs2.browse_button"), self._browse_directory), + ] + + def _toolbar_palette(self) -> dict[str, str]: + if getattr(self, "theme", "light") == "dark": + return { + "normal": "#2f2f35", + "hover": "#3a3a40", + "active": "#1f1f25", + "outline": "#4d4d50", + "outline_focus": "#7c7c88", + "text": "#f1f1f5", + } + return { + "normal": "#ffffff", + "hover": "#ededf4", + "active": "#dcdce6", + "outline": "#d0d0d8", + "outline_focus": "#a9a9b2", + "text": "#1f1f1f", + } + + def _add_toolbar_button(self, parent, icon: str, label: str, command) -> None: + font = tkfont.Font(root=self, family="Segoe UI", size=9) + padding_x = 12 + gap = font.measure(" ") + icon_width = font.measure(icon) or font.measure(" ") + label_width = font.measure(label) + width = padding_x * 2 + icon_width + gap + label_width + height = 28 + radius = 9 + bg = self._background_colour() + canvas = tk.Canvas( + parent, + width=width, + height=height, + bd=0, + highlightthickness=0, + bg=bg, + relief="flat", + cursor="hand2", + takefocus=1, + ) + canvas.pack(side=tk.LEFT, padx=4, pady=1) + + palette = self._toolbar_palette() + rect_id = self._create_round_rect( + canvas, + 1, + 1, + width - 1, + height - 1, + radius, + fill=palette["normal"], + outline=palette["outline"], + width=1, + ) + icon_id = canvas.create_text( + padding_x, + height / 2, + text=icon, + font=font, + fill=palette["text"], + anchor="w", + ) + label_id = canvas.create_text( + padding_x + icon_width + gap, + height / 2, + text=label, + font=font, + fill=palette["text"], + anchor="w", + ) + + button_data = { + "canvas": canvas, + "rect": rect_id, + "text_ids": (icon_id, label_id), + "command": command, + "palette": palette.copy(), + } + self._toolbar_buttons.append(button_data) + + def set_fill(state: str) -> None: + pal = button_data["palette"] + canvas.itemconfigure(rect_id, fill=pal[state]) + + def execute(): + command() + + def on_press(_event=None): + set_fill("active") + + def on_release(event=None): + if event is not None and ( + event.x < 0 or event.y < 0 or event.x > width or event.y > height + ): + set_fill("normal") + return + set_fill("hover") + canvas.after_idle(execute) + + def on_enter(_event): + set_fill("hover") + + def on_leave(_event): + set_fill("normal") + + def on_focus_in(_event): + pal = button_data["palette"] + canvas.itemconfigure(rect_id, outline=pal["outline_focus"]) + + def on_focus_out(_event): + pal = button_data["palette"] + canvas.itemconfigure(rect_id, outline=pal["outline"]) + + def invoke_keyboard(_event=None): + set_fill("active") + canvas.after(120, lambda: set_fill("hover")) + canvas.after_idle(execute) + + canvas.bind("", on_press) + canvas.bind("", on_release) + canvas.bind("", on_enter) + canvas.bind("", on_leave) + canvas.bind("", on_focus_in) + canvas.bind("", on_focus_out) + canvas.bind("", invoke_keyboard) + canvas.bind("", invoke_keyboard) + + def _refresh_toolbar_buttons_theme(self) -> None: + if not self._toolbar_buttons: + return + palette = self._toolbar_palette() + bg = self._background_colour() + for data in self._toolbar_buttons: + canvas = data["canvas"] + rect = data["rect"] + text_ids = data["text_ids"] + data["palette"] = palette.copy() + canvas.configure(bg=bg) + canvas.itemconfigure(rect, fill=palette["normal"], outline=palette["outline"]) + for text_id in text_ids: + canvas.itemconfigure(text_id, fill=palette["text"]) + + @staticmethod + def _create_round_rect(canvas: tk.Canvas, x1, y1, x2, y2, radius, **kwargs): + points = [ + x1 + radius, + y1, + x2 - radius, + y1, + x2, + y1, + x2, + y1 + radius, + x2, + y2 - radius, + x2, + y2, + x2 - radius, + y2, + x1 + radius, + y2, + x1, + y2, + x1, + y2 - radius, + x1, + y1 + radius, + x1, + y1, + ] + return canvas.create_polygon(points, smooth=True, splinesteps=24, **kwargs) + def _background_colour(self) -> str: - return "#0f0f10" if getattr(self.app, "theme", "light") == "dark" else "#ffffff" + return "#0f0f10" if getattr(self, "theme", "light") == "dark" else "#ffffff" def _t(self, key: str) -> str: translator = getattr(self.app, "translator", None)