# tracker_v1_2.py # Motivation Tracker, non-threatening UI für gute Laune # Eingebaute intermittierende Verstärkung (Zufallsgewinnausschüttung) für mehr Motivation # Scrollable, Matplotlib stats, JSON persistence # Python 3.8+ import json import os import random from datetime import datetime, timedelta import tkinter as tk from tkinter import ttk, messagebox, simpledialog # Matplotlib (TkAgg backend) import matplotlib matplotlib.use("TkAgg") from matplotlib.figure import Figure from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg import matplotlib.dates as mdates import matplotlib.font_manager as fm from matplotlib import rcParams # ------------------------- # Config # ------------------------- DATA_FILE = "data_v1_1.json" PREFERRED_FONT = "Segoe UI" # falls nicht vorhanden, Tk ersetzt automatisch # default tasks and kaomoji DEFAULT_TASKS = ["CTF", "HTB", "PYTHON", "DIGITAL GARDENING"] KAOMOJI = { "CTF": " ✧(๑ↀᆺↀ๑)✧", "HTB": " 🌙(˘͈ᵕ ˘͈♡)", "PYTHON": " 🜁(Φ ω Φ)", "DIGITAL GARDENING": " 🌿(╹‿╹✿)" } DEFAULT_KAO = " ✨(˘͈ᵕ˘͈)✨" # color theme: COLORS = { "bg": "#FBF7FF", "header": "#EADCF6", "card": "#FFF4F9", "muted": "#7B6699", "accent": "#6B3FA0", "dark_accent": "#34224A", "text": "#2E2440", "positive": "#2E7D32", "bar1": "#A56CC1", "bar2": "#4B2C5E" } rcParams["font.family"] = [ "Segoe UI Symbol", "DejaVu Sans" ] rcParams["figure.dpi"] = 120 rcParams["savefig.dpi"] = 120 # ------------------------- # Data helpers # ------------------------- def load_data(): if os.path.exists(DATA_FILE): try: with open(DATA_FILE, "r", encoding="utf-8") as f: data = json.load(f) except Exception: data = {} else: data = {} data.setdefault("tasks", DEFAULT_TASKS.copy()) data.setdefault("history", {}) # {"YYYY-MM-DD": {task: bool, ...}} data.setdefault("current_week_points", 0) data.setdefault("total_earned", 0.0) data.setdefault("last_payout_date", None) data.setdefault("payout_history", []) # Listen wieder in Sets umwandeln (für rewarded_today) if "rewarded_today" in data: data["rewarded_today"] = {k: set(v) for k, v in data["rewarded_today"].items()} return data def save_data(data): try: # Sets in Listen umwandeln für JSON data_copy = data.copy() if "rewarded_today" in data_copy: data_copy["rewarded_today"] = {k: list(v) for k, v in data_copy["rewarded_today"].items()} with open(DATA_FILE, "w", encoding="utf-8") as f: json.dump(data_copy, f, indent=2, ensure_ascii=False) except Exception as e: print("Fehler beim Speichern:", e) # ------------------------- # Utility: OS-independent mousewheel binding for canvas # ------------------------- def bind_canvas_mousewheel(canvas): def _on_mousewheel(event): # event.delta on Windows with Tk returns multiples of 120 if event.num == 5 or event.delta < 0: canvas.yview_scroll(1, "units") elif event.num == 4 or event.delta > 0: canvas.yview_scroll(-1, "units") # Bind/Unbind nur wenn Maus über Canvas ist def _bound_to_mousewheel(event): canvas.bind_all("", _on_mousewheel) canvas.bind_all("", _on_mousewheel) canvas.bind_all("", _on_mousewheel) def _unbound_to_mousewheel(event): canvas.unbind_all("") canvas.unbind_all("") canvas.unbind_all("") canvas.bind('', _bound_to_mousewheel) canvas.bind('', _unbound_to_mousewheel) # ------------------------- # Main App # ------------------------- class TrackerApp: def __init__(self, root): self.root = root self.root.title("🔮 Tracker v1.2") self.root.geometry("900x500") self.root.minsize(820, 600) self.data = load_data() self._ensure_today() self._build_ui() # optional: auto check weekly payout on startup if saturday and not paid self._check_auto_payout_on_startup() def _ensure_today(self): today = datetime.now().strftime("%Y-%m-%d") tasks = self.data.get("tasks", DEFAULT_TASKS.copy()) if today not in self.data["history"]: self.data["history"][today] = {t: False for t in tasks} save_data(self.data) # sync for changed tasks for _, rec in self.data["history"].items(): for t in self.data["tasks"]: if t not in rec: rec[t] = False # ------------------------- # Build UI (with Scrollbar) # ------------------------- def _build_ui(self): # Header header = tk.Frame(self.root, bg=COLORS["header"], pady=10) header.pack(fill=tk.X) tk.Label(header, text="(っ◉ω◉)っ~~☆'。゚.✧.・。゚★'.・.・。゚", bg=COLORS["header"], fg=COLORS["dark_accent"], font=(PREFERRED_FONT, 18, "bold")).pack() tk.Label(header, text="Kleine Rituale, viel Magie ✨", bg=COLORS["header"], fg=COLORS["muted"], font=(PREFERRED_FONT, 10)).pack() # Scrollable main area: canvas + vertical scrollbar container = tk.Frame(self.root, bg=COLORS["bg"]) container.pack(fill=tk.BOTH, expand=True, padx=10, pady=8) self.canvas = tk.Canvas(container, bg=COLORS["bg"], highlightthickness=0) self.v_scroll = ttk.Scrollbar(container, orient="vertical", command=self.canvas.yview) self.canvas.configure(yscrollcommand=self.v_scroll.set) self.v_scroll.pack(side=tk.RIGHT, fill=tk.Y) self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) # inner frame (where content goes) self.main_frame = tk.Frame(self.canvas, bg=COLORS["bg"]) self.canvas.create_window((0, 0), window=self.main_frame, anchor="nw") # bind configure to update scrollregion self.main_frame.bind("", lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all"))) # bind mousewheel / trackpad scrolling bind_canvas_mousewheel(self.canvas) # --- add content to main_frame --- self._build_content_cards() def _build_content_cards(self): # week summary card week_card = tk.Frame(self.main_frame, bg=COLORS["card"], bd=1, relief=tk.RIDGE) week_card.pack(fill=tk.X, pady=(0, 10)) tk.Label(week_card, text="🌙 Diese Woche gesammelt:", bg=COLORS["card"], fg=COLORS["muted"], font=(PREFERRED_FONT, 12, "bold")).pack(anchor="w", padx=12, pady=(8, 0)) self.week_label = tk.Label(week_card, text=self._week_text(), bg=COLORS["card"], fg=COLORS["accent"], font=(PREFERRED_FONT, 16, "bold")) self.week_label.pack(anchor="w", padx=12, pady=(0, 12)) # tasks section tasks_card = tk.LabelFrame(self.main_frame, text=f"📅 Heute: {datetime.now().strftime('%d.%m.%Y')}", bg=COLORS["bg"], fg=COLORS["text"], font=(PREFERRED_FONT, 12, "bold")) tasks_card.pack(fill=tk.BOTH, pady=(0, 10)) self.task_vars = {} color_seq = [COLORS["card"], "#FFF6EA", "#E9F7F2", "#F7EAF4"] today_key = datetime.now().strftime("%Y-%m-%d") for i, t in enumerate(self.data.get("tasks", DEFAULT_TASKS)): bg = color_seq[i % len(color_seq)] row = tk.Frame(tasks_card, bg=bg, bd=1, relief=tk.GROOVE) row.pack(fill=tk.X, padx=8, pady=6) var = tk.BooleanVar(value=self.data["history"].get(today_key, {}).get(t, False)) self.task_vars[t] = var ka = KAOMOJI.get(t, DEFAULT_KAO) cb_text = f" ✦ {t} ✦{ka}" cb = tk.Checkbutton(row, text=cb_text, variable=var, bg=bg, fg=COLORS["text"], font=(PREFERRED_FONT, 12), anchor="w", command=lambda task=t: self._toggle_task(task)) cb.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=6, pady=8) points = tk.Label(row, text="→ 20 Cent ✨", bg=bg, fg=COLORS["bar2"], font=(PREFERRED_FONT, 10, "bold")) points.pack(side=tk.RIGHT, padx=8) # progress card prog_card = tk.Frame(self.main_frame, bg=COLORS["card"], bd=1, relief=tk.RIDGE) prog_card.pack(fill=tk.X, pady=(0, 10)) tk.Label(prog_card, text="🔮 Heutiger Fortschritt:", bg=COLORS["card"], fg=COLORS["muted"], font=(PREFERRED_FONT, 12, "bold")).pack(anchor="w", padx=12, pady=(8, 6)) style = ttk.Style() style.configure("Horizontal.TProgressbar", troughcolor=COLORS["bg"], background=COLORS["accent"]) self.progress = ttk.Progressbar(prog_card, style="Horizontal.TProgressbar", mode="determinate") self.progress.pack(fill=tk.X, padx=12, pady=(0, 8)) self.progress_label = tk.Label(prog_card, text="", bg=COLORS["card"], fg=COLORS["text"], font=(PREFERRED_FONT, 11)) self.progress_label.pack(anchor="w", padx=12, pady=(0,8)) # bottom controls area bottom = tk.Frame(self.main_frame, bg=COLORS["bg"]) bottom.pack(fill=tk.X, pady=(0,12)) total_card = tk.Frame(bottom, bg=COLORS["card"], bd=1, relief=tk.RIDGE) total_card.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0,8)) tk.Label(total_card, text="🌿 Gesamt verdient:", bg=COLORS["card"], fg=COLORS["muted"], font=(PREFERRED_FONT, 12, "bold")).pack(anchor="w", padx=10, pady=(8,2)) self.total_label = tk.Label(total_card, text=f"{self.data.get('total_earned',0.0):.2f} €", bg=COLORS["card"], fg=COLORS["positive"], font=(PREFERRED_FONT, 14, "bold")) self.total_label.pack(anchor="w", padx=10, pady=(0,12)) controls = tk.Frame(bottom, bg=COLORS["bg"]) controls.pack(side=tk.RIGHT) tk.Button(controls, text="📜 Verlauf", command=self.show_history, font=(PREFERRED_FONT, 11), bg=COLORS["card"]).pack(side=tk.LEFT, padx=6) tk.Button(controls, text="💶 Auszahlungen", command=self.show_payout_history, font=(PREFERRED_FONT, 11), bg=COLORS["card"]).pack(side=tk.LEFT, padx=6) tk.Button(controls, text="📈 Statistiken", command=self.show_stats, font=(PREFERRED_FONT, 11), bg=COLORS["card"]).pack(side=tk.LEFT, padx=6) # task management (add/remove/reset) manage = tk.Frame(self.main_frame, bg=COLORS["bg"]) manage.pack(fill=tk.X, pady=(6,12)) self.new_task_entry = tk.Entry(manage, font=(PREFERRED_FONT, 12)) self.new_task_entry.pack(side=tk.LEFT, padx=(6,6)) tk.Button(manage, text="+ Aufgabe hinzufügen", command=self.add_task, font=(PREFERRED_FONT, 11)).pack(side=tk.LEFT, padx=6) tk.Button(manage, text="− Aufgabe entfernen", command=self.remove_task, font=(PREFERRED_FONT, 11)).pack(side=tk.LEFT, padx=6) tk.Button(manage, text="♻️ Tag zurücksetzen", command=self.reset_today, font=(PREFERRED_FONT, 11)).pack(side=tk.LEFT, padx=6) # final update of displays self.update_display() # ------------------------- # Toggle logic for tasks # FIXED: Verhindert Punkt-Exploit durch rewarded_today Tracking # ------------------------- def _toggle_task(self, task): today = datetime.now().strftime("%Y-%m-%d") self.data.setdefault("history", {}) self.data["history"].setdefault(today, {t: False for t in self.data.get("tasks", DEFAULT_TASKS)}) # Neu: Tracking für bereits belohnte Tasks self.data.setdefault("rewarded_today", {}) if today not in self.data["rewarded_today"]: self.data["rewarded_today"][today] = set() was = self.data["history"][today].get(task, False) nowv = bool(self.task_vars[task].get()) self.data["history"][today][task] = nowv # Nur Punkte vergeben, wenn Task erstmalig heute abgehakt wird if nowv and not was and task not in self.data["rewarded_today"][today]: self.data["current_week_points"] = self.data.get("current_week_points",0) + 1 self.data["rewarded_today"][today].add(task) # Als belohnt markieren # Tagesbonus nur wenn alle tasks abgehakt und noch nicht alle belohnt wurden if all(self.data["history"][today].values()): # Prüfen ob Tagesbonus heute schon vergeben wurde if not self.data.get("daily_bonus_given", {}).get(today, False): self.data["current_week_points"] += 2 self.data.setdefault("daily_bonus_given", {})[today] = True messagebox.showinfo("🌟 Tagesbonus", "Alle Aufgaben erledigt! +2 Bonuspunkte ✨") # Random surprise nur beim ersten Abhaken if random.random() < 0.12: add = random.randint(1, 3) self.data["current_week_points"] += add messagebox.showinfo("🔮 Überraschung", f"Du hast {add} Bonuspunkte gewonnen! {DEFAULT_KAO}") elif not nowv and was: # Punkte werden nicht abgezogen, da die Belohnung schon vergeben wurde pass save_data(self.data) self.update_display() # ------------------------- # Add / Remove Tasks # ------------------------- def add_task(self): t = self.new_task_entry.get().strip() if not t: return if t in self.data.get("tasks", []): messagebox.showwarning("Schon vorhanden", "Diese Aufgabe existiert bereits.") return self.data["tasks"].append(t) if t not in KAOMOJI: KAOMOJI[t] = DEFAULT_KAO # add to all existing days for _, rec in self.data["history"].items(): rec[t] = False save_data(self.data) messagebox.showinfo("Hinzugefügt", f"Aufgabe '{t}' wurde hinzugefügt. {KAOMOJI[t]}") self._rebuild_ui() # rebuild so new checkbox appears def remove_task(self): t = simpledialog.askstring("Aufgabe entfernen", "Gib den genauen Namen der Aufgabe ein:") if not t: return if t not in self.data.get("tasks", []): messagebox.showwarning("Nicht gefunden", "Diese Aufgabe existiert nicht.") return if not messagebox.askyesno("Bestätigen", f"Soll '{t}' wirklich entfernt werden?"): return self.data["tasks"].remove(t) for day_key in list(self.data["history"].keys()): if t in self.data["history"][day_key]: del self.data["history"][day_key][t] if t in KAOMOJI: del KAOMOJI[t] save_data(self.data) messagebox.showinfo("Entfernt", f"'{t}' wurde entfernt.") self._rebuild_ui() def reset_today(self): today = datetime.now().strftime("%Y-%m-%d") if today in self.data["history"]: for k in self.data["history"][today]: self.data["history"][today][k] = False # Reset auch rewarded_today und daily_bonus if today in self.data.get("rewarded_today", {}): self.data["rewarded_today"][today] = set() if today in self.data.get("daily_bonus_given", {}): self.data["daily_bonus_given"][today] = False save_data(self.data) self._rebuild_ui() self.update_display() messagebox.showinfo("Zurückgesetzt", "Heute wurden alle Aufgaben zurückgesetzt.") else: messagebox.showinfo("Kein Eintrag", "Heute gibt es keine Einträge zum Zurücksetzen.") # ------------------------- # Rebuild UI (simpler approach) # ------------------------- def _rebuild_ui(self): # destroy and rebuild to refresh tasks list for w in self.root.winfo_children(): w.destroy() # reload data and rebuild self.data = load_data() self._ensure_today() self._build_ui() # ------------------------- # Display updates # ------------------------- def _week_text(self): pts = self.data.get("current_week_points", 0) euros = pts * 0.20 return f"{pts} Punkte = {euros:.2f} €" def update_display(self): # update summary labels and progress self.week_label.config(text=self._week_text()) self.total_label.config(text=f"{self.data.get('total_earned', 0.0):.2f} €") today = datetime.now().strftime("%Y-%m-%d") total = len(self.data.get("tasks", [])) done = 0 if today in self.data["history"]: done = sum(1 for v in self.data["history"][today].values() if v) percent = int(done / total * 100) if total > 0 else 0 self.progress['value'] = percent self.progress_label.config(text=f"{done}/{total} Tasks erledigt") # sync checkbox variables for task, var in self.task_vars.items(): cur = self.data["history"].get(today, {}).get(task, False) var.set(bool(cur)) # ------------------------- # History & Payout # ------------------------- def show_history(self): win = tk.Toplevel(self.root) win.title("📜 Verlauf") win.geometry("640x560") win.configure(bg=COLORS["bg"]) tk.Label(win, text="📚 Verlauf (letzte 30 Tage)", bg=COLORS["bg"], fg=COLORS["dark_accent"], font=(PREFERRED_FONT, 14, "bold")).pack(pady=8) cont = tk.Frame(win, bg=COLORS["bg"]) cont.pack(fill=tk.BOTH, expand=True, padx=8, pady=6) canvas = tk.Canvas(cont, bg=COLORS["bg"], highlightthickness=0) vs = ttk.Scrollbar(cont, orient="vertical", command=canvas.yview) canvas.configure(yscrollcommand=vs.set) vs.pack(side=tk.RIGHT, fill=tk.Y) canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) frame = tk.Frame(canvas, bg=COLORS["bg"]) canvas.create_window((0,0), window=frame, anchor="nw") frame.bind("", lambda e: canvas.configure(scrollregion=canvas.bbox("all"))) bind_canvas_mousewheel(canvas) days = sorted(self.data.get("history", {}).keys(), reverse=True)[:30] for d in days: rec = self.data["history"].get(d, {}) total = len(rec) done = sum(1 for v in rec.values() if v) card = tk.Frame(frame, bg=COLORS["card"], bd=1, relief=tk.RIDGE) card.pack(fill=tk.X, pady=6, padx=6) tk.Label(card, text=f"{d} — {done}/{total} erledigt", bg=COLORS["card"], fg=COLORS["accent"], font=(PREFERRED_FONT, 12, "bold")).pack(anchor="w", padx=8, pady=(6,2)) comp = [t for t, v in rec.items() if v] if comp: tk.Label(card, text="Erledigt: " + ", ".join(comp), bg=COLORS["card"], fg=COLORS["text"], font=(PREFERRED_FONT, 10)).pack(anchor="w", padx=8, pady=(0,8)) else: tk.Label(card, text="Keine Aufgaben erledigt an diesem Tag.", bg=COLORS["card"], fg=COLORS["muted"], font=(PREFERRED_FONT, 10, "italic")).pack(anchor="w", padx=8, pady=(0,8)) def show_payout_history(self): win = tk.Toplevel(self.root) win.title("💶 Auszahlungshistorie") win.geometry("520x420") win.configure(bg=COLORS["bg"]) tk.Label(win, text="💰 Auszahlungshistorie", bg=COLORS["bg"], fg=COLORS["accent"], font=(PREFERRED_FONT, 14, "bold")).pack(pady=8) ph = list(reversed(self.data.get("payout_history", []))) if not ph: tk.Label(win, text="Noch keine Auszahlungen.", bg=COLORS["bg"], fg=COLORS["text"], font=(PREFERRED_FONT, 11)).pack(pady=18) return frame = tk.Frame(win, bg=COLORS["bg"]) frame.pack(fill=tk.BOTH, expand=True, padx=8, pady=6) for p in ph: tk.Label(frame, text=f"{p['date']}: {p['amount']:.2f} € ({p['points']} pts)", bg=COLORS["card"], fg=COLORS["text"], font=(PREFERRED_FONT, 11)).pack(fill=tk.X, padx=8, pady=6) # manual trigger for weekly payout def trigger_weekly_payout(self): pts = self.data.get("current_week_points", 0) if pts <= 0: messagebox.showinfo("Keine Punkte", "Keine Punkte diese Woche.") return amount = round(pts * 0.20, 2) if messagebox.askyesno("Auszahlung", f"{amount:.2f} € auszahlen?"): date = datetime.now().strftime("%Y-%m-%d") self.data.setdefault("payout_history", []).append({"date": date, "points": pts, "amount": amount}) self.data["total_earned"] = round(self.data.get("total_earned", 0.0) + amount, 2) self.data["current_week_points"] = 0 self.data["last_payout_date"] = date save_data(self.data) self.update_display() messagebox.showinfo("Ausgezahlt", f"{amount:.2f} € wurden ausgezahlt. Viel Freude!") def _check_auto_payout_on_startup(self): # if it's Saturday and not paid today, optionally show a non-blocking prompt today = datetime.now() if today.weekday() == 5: # Saturday last = self.data.get("last_payout_date") today_str = today.strftime("%Y-%m-%d") if last != today_str and self.data.get("current_week_points", 0) > 0: # show a small popup to allow payout if messagebox.askyesno("Samstag", "Heute ist Samstag — Auszahlung der Woche durchführen?"): self.trigger_weekly_payout() # ------------------------- # Stats (Matplotlib embedded), well padded so labels not cut # ------------------------- def show_stats(self): win = tk.Toplevel(self.root) win.title("📈 Statistiken") win.geometry("940x500") win.configure(bg=COLORS["bg"]) tk.Label(win, text="📊 Fortschrittsübersicht", bg=COLORS["bg"], fg=COLORS["accent"], font=(PREFERRED_FONT, 16, "bold")).pack(pady=10) cont = tk.Frame(win, bg=COLORS["bg"]) cont.pack(fill=tk.BOTH, expand=True, padx=8, pady=8) canvas = tk.Canvas(cont, bg=COLORS["bg"], highlightthickness=0) vscroll = ttk.Scrollbar(cont, orient="vertical", command=canvas.yview) canvas.configure(yscrollcommand=vscroll.set) vscroll.pack(side=tk.RIGHT, fill=tk.Y) canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) inner = tk.Frame(canvas, bg=COLORS["bg"]) canvas.create_window((0,0), window=inner, anchor="nw") inner.bind("", lambda e: canvas.configure(scrollregion=canvas.bbox("all"))) bind_canvas_mousewheel(canvas) # --- last 7 days line chart days = [] counts = [] for i in range(6, -1, -1): d = (datetime.now() - timedelta(days=i)).date() days.append(d) key = d.strftime("%Y-%m-%d") counts.append(sum(1 for v in self.data["history"].get(key, {}).values() if v)) fig1 = Figure(figsize=(8.6, 3.0), dpi=100) ax1 = fig1.add_subplot(111) ax1.plot(days, counts, marker="o", linewidth=2, color=COLORS["bar1"]) ax1.set_title("Letzte 7 Tage — erledigte Tasks", color=COLORS["accent"], fontsize=12) ax1.set_ylabel("Tasks") ax1.set_ylim(0, max(4, max(counts) + 1)) ax1.grid(axis="y", linestyle="--", alpha=0.25) ax1.xaxis.set_major_formatter(mdates.DateFormatter("%a\n%d.%m")) fig1.tight_layout(pad=1.2) canvas1 = FigureCanvasTkAgg(fig1, master=inner) canvas1.draw() canvas1.get_tk_widget().pack(pady=12, fill=tk.BOTH, expand=True) # --- last 6 months bar chart months = [] mcounts = [] today = datetime.now().date() for off in range(5, -1, -1): y = today.year m = today.month - off while m <= 0: m += 12 y -= 1 months.append((y, m)) # count tasks c = 0 for date_str, rec in self.data["history"].items(): try: d = datetime.strptime(date_str, "%Y-%m-%d").date() except Exception: continue if d.year == y and d.month == m: c += sum(1 for v in rec.values() if v) mcounts.append(c) month_labels = [datetime(y, m, 1).strftime("%b %Y") for (y, m) in months] fig2 = Figure(figsize=(8.6, 3.4), dpi=100) ax2 = fig2.add_subplot(111) bars = ax2.bar(month_labels, mcounts, color=COLORS["bar2"], alpha=0.95) ax2.set_title("Letzte 6 Monate — erledigte Tasks (gesamt)", color=COLORS["accent"], fontsize=12) ax2.set_ylabel("Tasks gesamt") ax2.grid(axis="y", linestyle="--", alpha=0.25) # kaomoji above bars for rect, val in zip(bars, mcounts): xpos = rect.get_x() + rect.get_width() / 2 ypos = rect.get_height() + 0.4 if val == 0: k = "🌙(˘˘)" elif val < 6: k = "🔮(˘͈ᵕ˘͈)" elif val < 15: k = "✨(๑˃ᴗ˂)ﻭ" else: k = "🌟\(^▽^)/" ax2.text(xpos, ypos, k, ha="center", va="bottom", fontsize=10) fig2.tight_layout(pad=1.3) canvas2 = FigureCanvasTkAgg(fig2, master=inner) canvas2.draw() canvas2.get_tk_widget().pack(pady=12, fill=tk.BOTH, expand=True) # ------------------------- # Run # ------------------------- if __name__ == "__main__": main_root = tk.Tk() # --- HiDPI / schärfere Schrift (Windows) --- try: import ctypes ctypes.windll.shcore.SetProcessDpiAwareness(1) # SYSTEM_AWARE except Exception: pass # --- Tk-Skalierung an System-DPI anpassen --- dpi = main_root.winfo_fpixels('1i') main_root.tk.call("tk", "scaling", dpi / 72) app = TrackerApp(main_root) main_root.mainloop()