Removal OF files

This commit is contained in:
kiyreload27
2025-12-28 17:10:23 +00:00
parent d03f317d3e
commit 72dfc34893
1047 changed files with 0 additions and 4793 deletions

View File

@@ -1,559 +0,0 @@
"""
Card List View - Browse and search support cards with ownership management
"""
import tkinter as tk
from tkinter import ttk
import sys
import os
from PIL import Image, ImageTk
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from db.db_queries import get_all_cards, get_card_by_id, get_effects_at_level, set_card_owned, is_card_owned, update_owned_card_level
from utils import resolve_image_path
from gui.theme import (
BG_DARK, BG_MEDIUM, BG_LIGHT, BG_HIGHLIGHT,
ACCENT_PRIMARY, ACCENT_SECONDARY, ACCENT_SUCCESS,
TEXT_PRIMARY, TEXT_SECONDARY, TEXT_MUTED,
FONT_HEADER, FONT_SUBHEADER, FONT_BODY, FONT_BODY_BOLD, FONT_SMALL, FONT_MONO,
RARITY_COLORS, TYPE_COLORS, TYPE_ICONS,
create_styled_button, create_styled_text, create_card_frame,
get_rarity_color, get_type_color, get_type_icon,
EFFECT_DESCRIPTIONS, Tooltip
)
class CardListFrame(ttk.Frame):
"""Frame containing card list with search/filter, ownership, and details panel"""
def __init__(self, parent, on_card_selected_callback=None):
super().__init__(parent)
self.on_card_selected = on_card_selected_callback
self.cards = []
self.current_card_id = None
self.card_image = None # Keep reference to prevent garbage collection
self.icon_cache = {} # Cache for list icons
# Create main layout
self.create_widgets()
self.load_cards()
def create_widgets(self):
"""Create the card list interface"""
# Main horizontal layout
main_pane = ttk.PanedWindow(self, orient=tk.HORIZONTAL)
main_pane.pack(fill=tk.BOTH, expand=True)
# Left panel - Card list with filters
left_frame = ttk.Frame(main_pane, width=420)
main_pane.add(left_frame, weight=1)
# Right panel - Card details
self.details_frame = ttk.Frame(main_pane)
main_pane.add(self.details_frame, weight=2)
# === Left Panel Contents ===
# Initialize filter variables FIRST (before search trace can trigger filter_cards)
self.rarity_var = tk.StringVar(value="All")
self.type_var = tk.StringVar(value="All")
self.owned_only_var = tk.BooleanVar(value=False)
# Search bar with modern styling
search_frame = tk.Frame(left_frame, bg=BG_DARK)
search_frame.pack(fill=tk.X, padx=10, pady=10)
search_icon = tk.Label(search_frame, text="🔍", font=FONT_BODY, bg=BG_DARK, fg=TEXT_MUTED)
search_icon.pack(side=tk.LEFT, padx=(0, 5))
self.search_var = tk.StringVar()
self.search_entry = ttk.Entry(search_frame, textvariable=self.search_var, width=35)
self.search_entry.pack(side=tk.LEFT, fill=tk.X, expand=True)
# Set placeholder BEFORE adding the trace (so it doesn't trigger filter)
self.search_entry.insert(0, "Search cards...")
self.search_entry.config(foreground=TEXT_MUTED)
self.search_entry.bind('<FocusIn>', self._on_search_focus_in)
self.search_entry.bind('<FocusOut>', self._on_search_focus_out)
# NOW add the trace (after placeholder is set)
self.search_var.trace('w', lambda *args: self.filter_cards())
# Filter dropdowns
filter_frame = tk.Frame(left_frame, bg=BG_DARK)
filter_frame.pack(fill=tk.X, padx=10, pady=(0, 10))
# Rarity filter
tk.Label(filter_frame, text="Rarity:", font=FONT_SMALL, bg=BG_DARK, fg=TEXT_MUTED).pack(side=tk.LEFT)
rarity_combo = ttk.Combobox(filter_frame, textvariable=self.rarity_var,
values=["All", "SSR", "SR", "R"], width=7, state='readonly')
rarity_combo.pack(side=tk.LEFT, padx=(5, 15))
rarity_combo.bind('<<ComboboxSelected>>', lambda e: self.filter_cards())
# Type filter
tk.Label(filter_frame, text="Type:", font=FONT_SMALL, bg=BG_DARK, fg=TEXT_MUTED).pack(side=tk.LEFT)
type_combo = ttk.Combobox(filter_frame, textvariable=self.type_var,
values=["All", "Speed", "Stamina", "Power", "Guts", "Wisdom", "Friend", "Group"],
width=10, state='readonly')
type_combo.pack(side=tk.LEFT, padx=5)
type_combo.bind('<<ComboboxSelected>>', lambda e: self.filter_cards())
# Owned only filter
owned_check = ttk.Checkbutton(filter_frame, text="Owned Only",
variable=self.owned_only_var, command=self.filter_cards)
owned_check.pack(side=tk.LEFT, padx=15)
# Reset Button
ttk.Button(filter_frame, text="Reset", command=self.reset_filters,
style='Small.TButton', width=7).pack(side=tk.LEFT, padx=5)
# Shortcuts
self.bind_all('<Control-f>', lambda e: self.search_entry.focus_set())
# Card count label
self.count_label = tk.Label(left_frame, text="0 cards", font=FONT_SMALL,
bg=BG_DARK, fg=ACCENT_PRIMARY)
self.count_label.pack(pady=5)
# Card list (Treeview)
list_frame = tk.Frame(left_frame, bg=BG_DARK)
list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10))
self.tree = ttk.Treeview(list_frame, columns=('owned', 'name', 'rarity', 'type'),
show='tree headings', selectmode='browse',
style="CardList.Treeview")
self.tree.heading('#0', text='')
self.tree.column('#0', width=45, anchor='center')
self.tree.heading('owned', text='', command=lambda: self.sort_column('owned', False))
self.tree.heading('name', text='Name', anchor='w', command=lambda: self.sort_column('name', False))
self.tree.heading('rarity', text='Rarity', command=lambda: self.sort_column('rarity', False))
self.tree.heading('type', text='Type', command=lambda: self.sort_column('type', False))
self.tree.column('owned', width=30, anchor='center')
self.tree.column('name', width=180, minwidth=150)
self.tree.column('rarity', width=55, anchor='center')
self.tree.column('type', width=90, anchor='center')
scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.tree.yview)
self.tree.configure(yscrollcommand=scrollbar.set)
self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.tree.bind('<<TreeviewSelect>>', self.on_select)
# Tag for owned cards
self.tree.tag_configure('owned', background='#1a3a2e')
# === Right Panel Contents (Details) ===
self.create_details_panel()
def create_details_panel(self):
"""Create the card details panel"""
# Container with card-like appearance
details_container = tk.Frame(self.details_frame, bg=BG_DARK)
details_container.pack(fill=tk.BOTH, expand=True, padx=15, pady=10)
# Image area with card frame
image_frame = create_card_frame(details_container, padx=10, pady=10)
image_frame.pack(pady=10)
self.image_label = tk.Label(image_frame, text="", bg=BG_MEDIUM)
self.image_label.pack(padx=5, pady=5)
# Header with card name
self.detail_name = tk.Label(details_container, text="Select a card",
font=FONT_HEADER, bg=BG_DARK, fg=ACCENT_PRIMARY)
self.detail_name.pack(pady=(10, 5))
self.detail_info = tk.Label(details_container, text="",
font=FONT_SMALL, bg=BG_DARK, fg=TEXT_MUTED)
self.detail_info.pack()
# Owned checkbox with emphasis
owned_frame = tk.Frame(details_container, bg=BG_DARK)
owned_frame.pack(pady=15)
self.owned_var = tk.BooleanVar(value=False)
self.owned_checkbox = ttk.Checkbutton(owned_frame, text="✨ I Own This Card",
variable=self.owned_var,
command=self.toggle_owned,
style='Large.TCheckbutton')
self.owned_checkbox.pack(side=tk.LEFT)
# Level selector with button-based control (no slider)
level_frame = tk.Frame(details_container, bg=BG_DARK)
level_frame.pack(fill=tk.X, padx=30, pady=10)
tk.Label(level_frame, text="Card Level:", font=FONT_BODY,
bg=BG_DARK, fg=TEXT_SECONDARY).pack(side=tk.LEFT)
# Level display with increment/decrement
level_ctrl = tk.Frame(level_frame, bg=BG_DARK)
level_ctrl.pack(side=tk.LEFT, padx=15)
self.level_var = tk.IntVar(value=50)
self.max_level = 50
self.valid_levels = [30, 35, 40, 45, 50] # Default SSR
# Decrement button
dec_btn = tk.Button(level_ctrl, text="", font=FONT_HEADER,
bg=BG_LIGHT, fg=TEXT_PRIMARY, bd=0, width=2,
activebackground=BG_HIGHLIGHT, cursor='hand2',
command=self.decrement_level)
dec_btn.pack(side=tk.LEFT)
self.level_label = tk.Label(level_ctrl, text="50", width=4, font=FONT_HEADER,
bg=BG_MEDIUM, fg=ACCENT_PRIMARY, padx=10)
self.level_label.pack(side=tk.LEFT, padx=2)
# Increment button
inc_btn = tk.Button(level_ctrl, text="+", font=FONT_HEADER,
bg=BG_LIGHT, fg=TEXT_PRIMARY, bd=0, width=2,
activebackground=BG_HIGHLIGHT, cursor='hand2',
command=self.increment_level)
inc_btn.pack(side=tk.LEFT)
# Quick level buttons container
self.level_btn_frame = tk.Frame(level_frame, bg=BG_DARK)
self.level_btn_frame.pack(side=tk.LEFT, padx=20)
self.level_buttons = {}
# Initial population
self.update_level_buttons('SSR', 50)
# Effects display header
effects_header = tk.Frame(details_container, bg=BG_DARK)
effects_header.pack(fill=tk.X, padx=20, pady=(20, 10))
tk.Label(effects_header, text="📊 Effects at Current Level",
font=FONT_SUBHEADER, bg=BG_DARK, fg=TEXT_PRIMARY).pack(side=tk.LEFT)
# Effects text area with modern styling
effects_frame = create_card_frame(details_container)
effects_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=(0, 15))
self.effects_text = create_styled_text(effects_frame, height=10)
self.effects_text.pack(fill=tk.BOTH, expand=True, padx=2, pady=2)
self.effects_text.config(state=tk.DISABLED)
def load_cards(self):
"""Load all cards from database"""
self.cards = get_all_cards()
self.populate_tree(self.cards)
def reset_filters(self):
"""Reset all filters to default"""
self.search_var.set("")
self.rarity_var.set("All")
self.type_var.set("All")
self.owned_only_var.set(False)
# Reset placeholder
self.search_entry.delete(0, tk.END)
self.search_entry.insert(0, "Search cards...")
self.search_entry.config(foreground=TEXT_MUTED)
self.filter_cards()
def _on_search_focus_in(self, event):
"""Clear placeholder on focus"""
if self.search_entry.get() == "Search cards...":
self.search_entry.delete(0, tk.END)
self.search_entry.config(foreground=TEXT_PRIMARY)
def _on_search_focus_out(self, event):
"""Show placeholder if empty"""
if not self.search_entry.get():
self.search_entry.insert(0, "Search cards...")
self.search_entry.config(foreground=TEXT_MUTED)
def filter_cards(self):
"""Filter cards based on search and dropdown values"""
rarity = self.rarity_var.get() if self.rarity_var.get() != "All" else None
card_type = self.type_var.get() if self.type_var.get() != "All" else None
# Ignore placeholder text
search_text = self.search_var.get().strip()
search = search_text if search_text and search_text != "Search cards..." else None
owned_only = self.owned_only_var.get()
self.cards = get_all_cards(rarity_filter=rarity, type_filter=card_type,
search_term=search, owned_only=owned_only)
self.populate_tree(self.cards)
def sort_column(self, col, reverse):
"""Sort treeview by column"""
l = [(self.tree.set(k, col), k) for k in self.tree.get_children('')]
# Custom sort logic
if col == 'owned':
# Sort by star/empty
l.sort(key=lambda t: t[0] if t[0] else "", reverse=reverse)
elif col == 'rarity':
# Sort by rarity rank (SSR > SR > R)
rarity_map = {'SSR': 3, 'SR': 2, 'R': 1}
l.sort(key=lambda t: rarity_map.get(t[0], 0), reverse=reverse)
else:
# Default string sort
l.sort(reverse=reverse)
# Rearrange items
for index, (val, k) in enumerate(l):
self.tree.move(k, '', index)
# Reverse sort next time
self.tree.heading(col, command=lambda: self.sort_column(col, not reverse))
def populate_tree(self, cards):
"""Populate treeview with cards"""
self.tree.delete(*self.tree.get_children())
for card in cards:
card_id, name, rarity, card_type, max_level, image_path, is_owned, owned_level = card
type_icon = get_type_icon(card_type)
owned_mark = "" if is_owned else ""
tag = 'owned' if is_owned else ''
# Show level for owned cards
display_name = name
if is_owned and owned_level:
display_name = f"{name} (Lv{owned_level})"
# Load Icon
img = self.icon_cache.get(card_id)
if not img:
resolved_path = resolve_image_path(image_path)
if resolved_path and os.path.exists(resolved_path):
try:
pil_img = Image.open(resolved_path)
pil_img.thumbnail((32, 32), Image.Resampling.LANCZOS)
img = ImageTk.PhotoImage(pil_img)
self.icon_cache[card_id] = img
except:
pass
if img:
self.tree.insert('', tk.END, iid=card_id, text='', image=img,
values=(owned_mark, display_name, rarity, f"{type_icon} {card_type}"),
tags=(tag,))
else:
self.tree.insert('', tk.END, iid=card_id, text='',
values=(owned_mark, display_name, rarity, f"{type_icon} {card_type}"),
tags=(tag,))
self.count_label.config(text=f"{len(cards)} cards")
def on_select(self, event):
"""Handle card selection"""
selection = self.tree.selection()
if not selection:
return
card_id = int(selection[0])
card = get_card_by_id(card_id)
if card:
card_id, name, rarity, card_type, max_level, url, image_path, is_owned, owned_level = card
# Update owned checkbox
self.owned_var.set(bool(is_owned))
# Load card image if available
self.load_card_image(image_path)
# Use owned level if owned, otherwise max level or default 50
initial_level = owned_level if is_owned and owned_level else max_level
# Update level controls
self.max_level = max_level
self.update_level_buttons(rarity, max_level)
# Snap initial level to valid levels
if initial_level not in self.valid_levels:
# Find closest or default to max
initial_level = max_level
self.level_var.set(initial_level)
self.level_label.config(text=str(initial_level))
# Update details display with colors
type_icon = get_type_icon(card_type)
type_color = get_type_color(card_type)
rarity_color = get_rarity_color(rarity)
self.detail_name.config(text=f"{type_icon} {name}", fg=ACCENT_PRIMARY)
self.detail_info.config(text=f"{rarity}{card_type} │ Max Level: {max_level}")
# Load effects
self.current_card_id = card_id
self.update_effects_display()
# Notify parent window
if self.on_card_selected:
self.on_card_selected(card_id, name)
def load_card_image(self, image_path):
"""Load and display card image"""
resolved_path = resolve_image_path(image_path)
if resolved_path and os.path.exists(resolved_path):
try:
img = Image.open(resolved_path)
img.thumbnail((130, 130)) # Slightly larger
self.card_image = ImageTk.PhotoImage(img)
self.image_label.config(image=self.card_image)
except Exception as e:
self.image_label.config(image='', text="[Image not found]")
else:
self.image_label.config(image='', text="")
def toggle_owned(self):
"""Toggle owned status for current card"""
if self.current_card_id:
owned = self.owned_var.get()
level = int(self.level_var.get())
set_card_owned(self.current_card_id, owned, level)
self.filter_cards() # Refresh list to update owned markers
def update_level_buttons(self, rarity, max_level):
"""Update quick level buttons based on rarity/max level"""
# Determine valid levels
if max_level == 50: # SSR
self.valid_levels = [30, 35, 40, 45, 50]
elif max_level == 45: # SR
self.valid_levels = [25, 30, 35, 40, 45]
else: # R (max 40)
self.valid_levels = [20, 25, 30, 35, 40]
# Clear existing buttons
for widget in self.level_btn_frame.winfo_children():
widget.destroy()
self.level_buttons = {}
# Create new buttons
for lvl in self.valid_levels:
btn = create_styled_button(self.level_btn_frame, text=f"Lv{lvl}",
command=lambda l=lvl: self.set_level(l),
style_type='default')
btn.config(width=5, padx=6, pady=3, font=FONT_SMALL)
btn.pack(side=tk.LEFT, padx=2)
self.level_buttons[lvl] = btn
def set_level(self, level):
"""Set level from quick button"""
self.level_var.set(level)
self.level_label.config(text=str(level))
self.update_effects_display()
# Save level if owned
if self.current_card_id and self.owned_var.get():
update_owned_card_level(self.current_card_id, level)
self.update_tree_item_level(self.current_card_id, level)
def increment_level(self):
"""Increase level to next valid step"""
current = self.level_var.get()
# Find next level in valid_levels
for lvl in self.valid_levels:
if lvl > current:
self.set_level(lvl)
return
def decrement_level(self):
"""Decrease level to previous valid step"""
current = self.level_var.get()
# Find previous level in valid_levels
for lvl in reversed(self.valid_levels):
if lvl < current:
self.set_level(lvl)
return
def update_tree_item_level(self, card_id, level):
"""Update visible name in tree without full reload"""
if self.tree.exists(card_id):
current_values = self.tree.item(card_id, 'values')
if current_values:
# current_values is a tuple: (owned_mark, name, rarity, type)
# We need to strip existing " (LvXX)" from name if present
name = current_values[1]
base_name = name.split(" (Lv")[0]
new_name = f"{base_name} (Lv{level})"
# Make new values tuple preserving other columns
new_values = (current_values[0], new_name, current_values[2], current_values[3])
self.tree.item(card_id, values=new_values)
def update_effects_display(self):
"""Update the effects display for current card and level"""
if not self.current_card_id:
return
level = int(self.level_var.get())
effects = get_effects_at_level(self.current_card_id, level)
self.effects_text.config(state=tk.NORMAL)
self.effects_text.delete('1.0', tk.END)
# Configure tags for styling
self.effects_text.tag_configure('header', font=FONT_SUBHEADER, foreground=ACCENT_PRIMARY)
self.effects_text.tag_configure('highlight', foreground=ACCENT_SUCCESS)
self.effects_text.tag_configure('effect_name', foreground=TEXT_SECONDARY)
self.effects_text.tag_configure('effect_value', foreground=TEXT_PRIMARY)
self.effects_text.tag_configure('effect_tooltip', underline=False)
if effects:
self.effects_text.insert(tk.END, f"━━━ Level {level} ━━━\n\n", 'header')
for name, value in effects:
# Highlight high values
prefix = ""
if '%' in str(value):
try:
num = int(str(value).replace('%', '').replace('+', ''))
if num >= 20:
prefix = ""
except:
pass
if prefix:
self.effects_text.insert(tk.END, prefix, 'highlight')
# Insert effect name with tooltip tag
tag_name = f"tooltip_{name.replace(' ', '_')}"
self.effects_text.insert(tk.END, f"{name}: ", ('effect_name', tag_name))
# Bind tooltip events
self.effects_text.tag_bind(tag_name, "<Enter>", lambda e, n=name: self.show_effect_tooltip(e, n))
self.effects_text.tag_bind(tag_name, "<Leave>", self.hide_effect_tooltip)
self.effects_text.insert(tk.END, f"{value}\n", 'effect_value')
else:
self.effects_text.insert(tk.END, f"No effects data for Level {level}\n\n")
self.effects_text.insert(tk.END, "Available levels: 1, 25, 40, 50\n", 'effect_name')
self.effects_text.config(state=tk.DISABLED)
def show_effect_tooltip(self, event, effect_name):
"""Show tooltip for effect"""
if effect_name in EFFECT_DESCRIPTIONS:
text = EFFECT_DESCRIPTIONS[effect_name]
x = event.x_root + 15
y = event.y_root + 10
# Close existing if any
self.hide_effect_tooltip(None)
self.tooltip_window = tk.Toplevel(self)
self.tooltip_window.wm_overrideredirect(True)
self.tooltip_window.wm_geometry(f"+{x}+{y}")
label = tk.Label(self.tooltip_window, text=text, justify=tk.LEFT,
background=BG_LIGHT, foreground=TEXT_PRIMARY,
relief=tk.SOLID, borderwidth=1, font=FONT_SMALL,
padx=10, pady=5, wraplength=250)
label.pack()
def hide_effect_tooltip(self, event):
"""Hide tooltip"""
if hasattr(self, 'tooltip_window') and self.tooltip_window:
self.tooltip_window.destroy()
self.tooltip_window = None

View File

@@ -1,547 +0,0 @@
"""
Deck Builder Frame
Build decks with 6 cards and view combined effects with breakdown
"""
import tkinter as tk
from tkinter import ttk, messagebox
import sys
import os
from PIL import Image, ImageTk
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from db.db_queries import (
get_all_cards, get_all_decks, create_deck, delete_deck,
add_card_to_deck, remove_card_from_deck, get_deck_cards,
get_effects_at_level
)
from utils import resolve_image_path
from gui.theme import (
BG_DARK, BG_DARKEST, BG_MEDIUM, BG_LIGHT, BG_HIGHLIGHT,
ACCENT_PRIMARY, ACCENT_SECONDARY, ACCENT_SUCCESS, ACCENT_ERROR,
TEXT_PRIMARY, TEXT_SECONDARY, TEXT_MUTED,
FONT_HEADER, FONT_SUBHEADER, FONT_BODY, FONT_BODY_BOLD, FONT_SMALL, FONT_TINY,
TYPE_COLORS, get_type_color, get_type_icon,
create_styled_button, create_styled_text, create_card_frame
)
class CardSlot(tk.Frame):
"""Visual component for a single card slot"""
def __init__(self, parent, index, remove_callback, level_callback):
super().__init__(parent, bg=BG_MEDIUM, highlightthickness=2, highlightbackground=BG_LIGHT)
self.index = index
self.remove_callback = remove_callback
self.level_callback = level_callback
self.image_ref = None # Keep reference to prevent GC
self.setup_ui()
def setup_ui(self):
# Configure grid weight
self.columnconfigure(1, weight=1)
# Slot number indicator
slot_label = tk.Label(self, text=f"#{self.index + 1}", font=FONT_TINY,
bg=BG_LIGHT, fg=TEXT_MUTED, padx=4, pady=2)
slot_label.place(x=2, y=2)
# Image Area (Left)
self.image_label = tk.Label(self, bg=BG_MEDIUM, text="📭", fg=TEXT_MUTED,
font=('Segoe UI', 20))
self.image_label.grid(row=0, column=0, rowspan=3, padx=8, pady=8)
# Details Area (Right)
self.name_label = tk.Label(self, text="Empty Slot", bg=BG_MEDIUM, fg=TEXT_MUTED,
font=FONT_BODY_BOLD, anchor='w', wraplength=130)
self.name_label.grid(row=0, column=1, sticky='w', padx=8, pady=(10, 0))
self.meta_label = tk.Label(self, text="", bg=BG_MEDIUM, fg=TEXT_MUTED,
font=FONT_SMALL, anchor='w')
self.meta_label.grid(row=1, column=1, sticky='w', padx=8)
# Controls (Bottom Right)
ctrl_frame = tk.Frame(self, bg=BG_MEDIUM)
ctrl_frame.grid(row=2, column=1, sticky='ew', padx=8, pady=8)
# Level Selector
tk.Label(ctrl_frame, text="Lv:", bg=BG_MEDIUM, fg=TEXT_MUTED,
font=FONT_SMALL).pack(side=tk.LEFT)
self.level_var = tk.StringVar(value="50")
self.level_combo = ttk.Combobox(ctrl_frame, textvariable=self.level_var,
values=[], width=4, state='readonly')
self.level_combo.pack(side=tk.LEFT, padx=4)
self.level_combo.bind('<<ComboboxSelected>>', self._on_level_change)
# Remove Button
self.remove_btn = tk.Button(ctrl_frame, text="", bg=BG_LIGHT, fg=ACCENT_ERROR,
bd=0, font=FONT_BODY_BOLD, width=2,
activebackground=ACCENT_ERROR, activeforeground=TEXT_PRIMARY,
cursor='hand2',
command=lambda: self.remove_callback(self.index))
self.remove_btn.pack(side=tk.RIGHT)
# Hide controls initially
self.toggle_controls(False)
def toggle_controls(self, visible):
state = 'normal' if visible else 'disabled'
self.level_combo.config(state='readonly' if visible else 'disabled')
if not visible:
self.remove_btn.pack_forget()
else:
self.remove_btn.pack(side=tk.RIGHT)
def set_card(self, card_data):
"""Set card data: (id, name, rarity, type, image_path, level)"""
if not card_data:
self.reset()
return
card_id, name, rarity, card_type, image_path, level = card_data
# Calculate valid levels based on rarity
if rarity == 'SSR':
valid_levels = [50, 45, 40, 35, 30]
max_lvl = 50
elif rarity == 'SR':
valid_levels = [45, 40, 35, 30, 25]
max_lvl = 45
else: # R
valid_levels = [40, 35, 30, 25, 20]
max_lvl = 40
self.level_combo['values'] = [str(l) for l in valid_levels]
# Snap level to valid value if not present (e.g. old data)
if level not in valid_levels:
level = max_lvl
# Update styling based on type
color = get_type_color(card_type)
type_icon = get_type_icon(card_type)
self.name_label.config(text=name, fg=TEXT_PRIMARY)
self.meta_label.config(text=f"{type_icon} {rarity}{card_type}", fg=color)
self.level_var.set(str(level))
# Update border color based on rarity
rarity_borders = {'SSR': '#ffd700', 'SR': '#c0c0c0', 'R': '#cd853f'}
self.config(highlightbackground=rarity_borders.get(rarity, BG_LIGHT))
# Load Image
self._load_image(image_path)
self.toggle_controls(True)
def reset(self):
self.name_label.config(text="Empty Slot", fg=TEXT_MUTED)
self.meta_label.config(text="Click a card to add")
self.image_label.config(image='', text="📭", font=('Segoe UI', 20))
self.config(highlightbackground=BG_LIGHT)
self.image_ref = None
self.toggle_controls(False)
def _load_image(self, path):
resolved_path = resolve_image_path(path)
if resolved_path and os.path.exists(resolved_path):
try:
pil_img = Image.open(resolved_path)
pil_img.thumbnail((65, 65), Image.Resampling.LANCZOS)
self.image_ref = ImageTk.PhotoImage(pil_img)
self.image_label.config(image=self.image_ref, text='')
except Exception as e:
print(f"Failed to load image: {e}")
self.image_label.config(image='', text="⚠️")
else:
self.image_label.config(image='', text="🖼️")
def _on_level_change(self, event):
self.level_callback(self.index, int(self.level_var.get()))
class DeckBuilderFrame(ttk.Frame):
"""Deck builder with combined effects breakdown"""
def __init__(self, parent):
super().__init__(parent)
self.current_deck_id = None
self.deck_slots = [None] * 6 # 6 card slots
self.setup_ui()
self.refresh_decks()
def setup_ui(self):
# Main container with split view
main_split = ttk.PanedWindow(self, orient=tk.HORIZONTAL)
main_split.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# === Left Panel: Card Browser ===
left_panel = ttk.Frame(main_split)
main_split.add(left_panel, weight=1)
# Header
header = tk.Frame(left_panel, bg=BG_DARK)
header.pack(fill=tk.X, pady=(0, 10))
tk.Label(header, text="📋 Available Cards", font=FONT_SUBHEADER,
bg=BG_DARK, fg=TEXT_PRIMARY).pack(side=tk.LEFT)
# Filters
filter_frame = tk.Frame(left_panel, bg=BG_DARK)
filter_frame.pack(fill=tk.X, pady=(0, 8))
# Filters - Initialize vars FIRST
self.type_var = tk.StringVar(value="All")
self.owned_only_var = tk.BooleanVar(value=False)
self.search_var = tk.StringVar()
# Search Entry
self.search_entry = ttk.Entry(filter_frame, textvariable=self.search_var, width=18)
self.search_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 8))
# Placeholder behavior (before trace)
self.search_entry.insert(0, "Search...")
self.search_entry.config(foreground=TEXT_MUTED)
self.search_entry.bind('<FocusIn>', self._on_search_focus_in)
self.search_entry.bind('<FocusOut>', self._on_search_focus_out)
# Add trace AFTER placeholder is set
self.search_var.trace('w', lambda *args: self.filter_cards())
types = ["All", "Speed", "Stamina", "Power", "Guts", "Wisdom", "Friend", "Group"]
type_combo = ttk.Combobox(filter_frame, textvariable=self.type_var,
values=types, width=9, state='readonly')
type_combo.pack(side=tk.LEFT)
type_combo.bind('<<ComboboxSelected>>', lambda e: self.filter_cards())
ttk.Checkbutton(filter_frame, text="Owned", variable=self.owned_only_var,
command=self.filter_cards).pack(side=tk.LEFT, padx=8)
# Card List
list_frame = tk.Frame(left_panel, bg=BG_DARK)
list_frame.pack(fill=tk.BOTH, expand=True)
self.card_tree = ttk.Treeview(list_frame, columns=('name', 'rarity', 'type'),
show='tree headings', style="DeckList.Treeview")
self.card_tree.heading('#0', text='')
self.card_tree.column('#0', width=45, anchor='center')
self.card_tree.heading('name', text='Name')
self.card_tree.heading('rarity', text='Rarity')
self.card_tree.heading('type', text='Type')
self.card_tree.column('name', width=130)
self.card_tree.column('rarity', width=45, anchor='center')
self.card_tree.column('type', width=65, anchor='center')
scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.card_tree.yview)
self.card_tree.configure(yscrollcommand=scrollbar.set)
self.card_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# Double-click to add
self.card_tree.bind('<Double-1>', lambda e: self.add_selected_to_deck())
# Add Button
add_btn = create_styled_button(left_panel, text=" Add to Deck",
command=self.add_selected_to_deck,
style_type='accent')
add_btn.pack(fill=tk.X, pady=10)
# === Right Panel: Deck & Stats ===
right_panel = ttk.Frame(main_split)
main_split.add(right_panel, weight=2)
# Deck Controls
deck_ctrl = tk.Frame(right_panel, bg=BG_DARK)
deck_ctrl.pack(fill=tk.X, pady=(0, 15))
tk.Label(deck_ctrl, text="🎴 Current Deck:", font=FONT_BODY,
bg=BG_DARK, fg=TEXT_SECONDARY).pack(side=tk.LEFT)
self.deck_combo = ttk.Combobox(deck_ctrl, width=25, state='readonly')
self.deck_combo.pack(side=tk.LEFT, padx=10)
self.deck_combo.bind('<<ComboboxSelected>>', self.on_deck_selected)
ttk.Button(deck_ctrl, text="+ New", command=self.create_new_deck,
style='Small.TButton').pack(side=tk.LEFT, padx=5)
ttk.Button(deck_ctrl, text="🗑️ Delete", command=self.delete_current_deck,
style='Small.TButton').pack(side=tk.LEFT)
# Card count indicator
self.deck_count_label = tk.Label(deck_ctrl, text="0/6 cards",
font=FONT_SMALL, bg=BG_DARK, fg=ACCENT_PRIMARY)
self.deck_count_label.pack(side=tk.LEFT, padx=15)
# Deck Grid (3x2)
self.slots_frame = tk.Frame(right_panel, bg=BG_DARK)
self.slots_frame.pack(fill=tk.X)
self.card_slots = []
for i in range(6):
slot = CardSlot(self.slots_frame, i, self.remove_from_slot, self.on_slot_level_changed)
r, c = divmod(i, 3)
slot.grid(row=r, column=c, padx=6, pady=6, sticky='nsew')
self.slots_frame.columnconfigure(c, weight=1)
self.card_slots.append(slot)
# Stats / Effects Area
effects_header = tk.Frame(right_panel, bg=BG_DARK)
effects_header.pack(fill=tk.X, pady=(20, 10))
tk.Label(effects_header, text="📊 Combined Effects Breakdown",
font=FONT_SUBHEADER, bg=BG_DARK, fg=TEXT_PRIMARY).pack(side=tk.LEFT)
effects_frame = create_card_frame(right_panel)
effects_frame.pack(fill=tk.BOTH, expand=True)
self.effects_tree = ttk.Treeview(effects_frame,
columns=('effect', 'total', 'c1', 'c2', 'c3', 'c4', 'c5', 'c6'),
show='headings', height=8)
self.effects_tree.heading('effect', text='Effect')
self.effects_tree.heading('total', text='TOTAL')
self.effects_tree.column('effect', width=140)
self.effects_tree.column('total', width=60, anchor='center')
for i in range(1, 7):
self.effects_tree.heading(f'c{i}', text=f'#{i}')
self.effects_tree.column(f'c{i}', width=45, anchor='center')
vsb = ttk.Scrollbar(effects_frame, orient=tk.VERTICAL, command=self.effects_tree.yview)
self.effects_tree.configure(yscrollcommand=vsb.set)
self.effects_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=2, pady=2)
vsb.pack(side=tk.RIGHT, fill=tk.Y, pady=2)
# Unique Effects Area
unique_header = tk.Frame(right_panel, bg=BG_DARK)
unique_header.pack(fill=tk.X, pady=(15, 8))
tk.Label(unique_header, text="✨ Unique Effects", font=FONT_BODY_BOLD,
bg=BG_DARK, fg=ACCENT_SECONDARY).pack(side=tk.LEFT)
unique_frame = create_card_frame(right_panel)
unique_frame.pack(fill=tk.X)
self.unique_text = create_styled_text(unique_frame, height=5)
self.unique_text.pack(fill=tk.BOTH, expand=True, padx=2, pady=2)
self.unique_text.config(state=tk.DISABLED)
self.icon_cache = {}
self.filter_cards()
# Helper methods for placeholder
def _on_search_focus_in(self, event):
"""Clear placeholder on focus"""
if self.search_entry.get() == "Search...":
self.search_entry.delete(0, tk.END)
self.search_entry.config(foreground=TEXT_PRIMARY)
def _on_search_focus_out(self, event):
"""Show placeholder if empty"""
if not self.search_entry.get():
self.search_entry.insert(0, "Search...")
self.search_entry.config(foreground=TEXT_MUTED)
# --- Logic Methods ---
def filter_cards(self):
for item in self.card_tree.get_children():
self.card_tree.delete(item)
type_filter = self.type_var.get() if self.type_var.get() != "All" else None
# Ignore placeholder
search_text = self.search_var.get()
search = search_text if search_text and search_text != "Search..." else None
owned_only = self.owned_only_var.get()
cards = get_all_cards(type_filter=type_filter, search_term=search, owned_only=owned_only)
for card in cards:
card_id, name, rarity, card_type, max_level, image_path, is_owned, owned_level = card
# Load Icon
img = self.icon_cache.get(card_id)
resolved_path = resolve_image_path(image_path)
if not img and resolved_path and os.path.exists(resolved_path):
try:
pil_img = Image.open(resolved_path)
pil_img.thumbnail((32, 32), Image.Resampling.LANCZOS)
img = ImageTk.PhotoImage(pil_img)
self.icon_cache[card_id] = img
except:
pass
type_icon = get_type_icon(card_type)
if img:
self.card_tree.insert('', tk.END, text='', image=img,
values=(name, rarity, f"{type_icon}"), iid=str(card_id))
else:
self.card_tree.insert('', tk.END, text='?',
values=(name, rarity, f"{type_icon}"), iid=str(card_id))
def refresh_decks(self):
decks = get_all_decks()
self.deck_combo['values'] = [f"{d[0]}: {d[1]}" for d in decks]
if decks and not self.current_deck_id:
self.deck_combo.current(0)
self.on_deck_selected(None)
def on_deck_selected(self, event):
selection = self.deck_combo.get()
if selection:
self.current_deck_id = int(selection.split(':')[0])
self.load_deck()
def load_deck(self):
if not self.current_deck_id:
return
# Reset visual slots
for s in self.card_slots:
s.reset()
self.deck_slots = [None] * 6
# Load from DB
deck_cards = get_deck_cards(self.current_deck_id)
for card in deck_cards:
slot_pos, level, card_id, name, rarity, card_type, image_path = card
if 0 <= slot_pos < 6:
self.deck_slots[slot_pos] = card_id
self.card_slots[slot_pos].set_card((card_id, name, rarity, card_type, image_path, level))
self.update_deck_count()
self.update_effects_breakdown()
def create_new_deck(self):
name = tk.simpledialog.askstring("New Deck", "Enter deck name:")
if name:
deck_id = create_deck(name)
self.current_deck_id = deck_id
self.refresh_decks()
self.deck_combo.set(f"{deck_id}: {name}")
self.load_deck()
def delete_current_deck(self):
if self.current_deck_id:
if messagebox.askyesno("Delete Deck", "Are you sure you want to delete this deck?"):
delete_deck(self.current_deck_id)
self.current_deck_id = None
self.deck_combo.set('')
self.refresh_decks()
self.load_deck()
def add_selected_to_deck(self):
if not self.current_deck_id:
messagebox.showwarning("No Deck", "Select or create a deck first.")
return
selection = self.card_tree.selection()
if not selection:
return
card_id = int(selection[0])
# Check for duplicates
if card_id in self.deck_slots:
messagebox.showinfo("Duplicate Card", "This card is already in the deck.")
return
# Find empty slot
for i in range(6):
if self.deck_slots[i] is None:
add_card_to_deck(self.current_deck_id, card_id, i, 50)
self.load_deck()
return
messagebox.showinfo("Deck Full", "Remove a card first to add a new one.")
def remove_from_slot(self, index):
if self.current_deck_id and self.deck_slots[index]:
remove_card_from_deck(self.current_deck_id, index)
self.deck_slots[index] = None
self.card_slots[index].reset()
self.update_deck_count()
self.update_effects_breakdown()
def update_deck_count(self):
"""Update the X/6 cards display"""
count = sum(1 for slot in self.deck_slots if slot is not None)
self.deck_count_label.config(text=f"{count}/6 cards")
def on_slot_level_changed(self, index, new_level):
if self.current_deck_id and self.deck_slots[index]:
card_id = self.deck_slots[index]
add_card_to_deck(self.current_deck_id, card_id, index, new_level)
self.update_effects_breakdown()
def update_effects_breakdown(self):
for item in self.effects_tree.get_children():
self.effects_tree.delete(item)
# Clear Unique Text
self.unique_text.config(state=tk.NORMAL)
self.unique_text.delete('1.0', tk.END)
if not self.current_deck_id:
self.unique_text.insert(tk.END, "No deck selected")
self.unique_text.config(state=tk.DISABLED)
return
# Prepare data for calculation
card_info = []
for i in range(6):
if self.deck_slots[i]:
level = int(self.card_slots[i].level_var.get())
card_info.append((self.deck_slots[i], level))
else:
card_info.append(None)
# Gather effects
all_effects = {}
unique_effects_list = []
for i, info in enumerate(card_info):
if info:
card_id, level = info
card_name = self.card_slots[i].name_label.cget("text")
effects = get_effects_at_level(card_id, level)
for name, value in effects:
if name == "Unique Effect":
unique_effects_list.append(f"{card_name}: {value}")
continue
if name not in all_effects:
all_effects[name] = [''] * 6
all_effects[name][i] = value
# Configure tags
self.unique_text.tag_configure('card_name', foreground=ACCENT_PRIMARY)
# Fill Unique Effects
if unique_effects_list:
self.unique_text.insert(tk.END, "\n".join(unique_effects_list))
else:
self.unique_text.insert(tk.END, "No unique effects in this deck", 'card_name')
self.unique_text.config(state=tk.DISABLED)
# Sum totals
for effect_name, values in sorted(all_effects.items()):
total = 0
is_percent = False
for v in values:
if v:
if '%' in str(v): is_percent = True
try:
total += float(str(v).replace('%','').replace('+',''))
except: pass
total_str = f"{total:.0f}%" if is_percent else (f"+{total:.0f}" if total > 0 else str(int(total)))
row_vals = [effect_name, total_str] + values
self.effects_tree.insert('', tk.END, values=row_vals)
import tkinter.simpledialog

View File

@@ -1,23 +0,0 @@
import tkinter as tk
from db.db_queries import get_deck_bonus
class DeckView(tk.Toplevel):
def __init__(self, parent):
super().__init__(parent)
self.title("Deck Builder")
self.geometry("500x400")
self.deck_id = 1 # Default deck
tk.Button(self, text="Calculate Deck Bonuses", command=self.calculate).pack(pady=10)
self.output = tk.Text(self, height=20)
self.output.pack(fill=tk.BOTH, expand=True)
def calculate(self):
self.output.delete("1.0", tk.END)
bonuses = get_deck_bonus(self.deck_id)
if not bonuses:
self.output.insert(tk.END, "No bonuses found for this deck.\n")
return
for bonus, total in bonuses:
self.output.insert(tk.END, f"{bonus}: +{total}\n")

View File

@@ -1,298 +0,0 @@
"""
Effects View - Display support effects at all levels with interactive slider
"""
import tkinter as tk
from tkinter import ttk, messagebox
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from db.db_queries import get_all_effects, get_effects_at_level, get_unique_effect_names, get_card_by_id
from gui.theme import (
BG_DARK, BG_MEDIUM, BG_LIGHT, BG_HIGHLIGHT,
ACCENT_PRIMARY, ACCENT_SECONDARY, ACCENT_SUCCESS, ACCENT_TERTIARY,
TEXT_PRIMARY, TEXT_SECONDARY, TEXT_MUTED,
FONT_HEADER, FONT_SUBHEADER, FONT_BODY, FONT_BODY_BOLD, FONT_SMALL, FONT_MONO,
create_styled_button, create_styled_text, create_card_frame,
EFFECT_DESCRIPTIONS
)
class EffectsFrame(ttk.Frame):
"""Frame for viewing support effects at different levels"""
def __init__(self, parent):
super().__init__(parent)
self.current_card_id = None
self.current_card_name = None
self.max_level = 50
self.create_widgets()
def create_widgets(self):
"""Create the effects view interface"""
# Header
header_frame = tk.Frame(self, bg=BG_DARK)
header_frame.pack(fill=tk.X, padx=20, pady=15)
self.card_label = tk.Label(header_frame, text="📊 Select a card from the Card List tab",
font=FONT_HEADER, bg=BG_DARK, fg=ACCENT_PRIMARY)
self.card_label.pack(side=tk.LEFT)
# Legend Button
legend_btn = create_styled_button(header_frame, text="❓ Legend",
command=self.show_legend, style_type='default')
legend_btn.config(font=FONT_SMALL, padx=10, pady=4)
legend_btn.pack(side=tk.RIGHT)
# Level control frame
control_frame = tk.Frame(self, bg=BG_MEDIUM, padx=15, pady=12)
control_frame.pack(fill=tk.X, padx=20)
# Level label
tk.Label(control_frame, text="Level:", font=FONT_BODY,
bg=BG_MEDIUM, fg=TEXT_SECONDARY).pack(side=tk.LEFT)
# Level display with increment/decrement buttons
level_ctrl = tk.Frame(control_frame, bg=BG_MEDIUM)
level_ctrl.pack(side=tk.LEFT, padx=15)
# Decrement button
dec_btn = tk.Button(level_ctrl, text="", font=FONT_HEADER,
bg=BG_LIGHT, fg=TEXT_PRIMARY, bd=0, width=2,
activebackground=BG_HIGHLIGHT, cursor='hand2',
command=self.decrement_level)
dec_btn.pack(side=tk.LEFT)
self.level_var = tk.IntVar(value=50)
self.level_display = tk.Label(level_ctrl, text="50", width=4, font=FONT_HEADER,
bg=BG_LIGHT, fg=ACCENT_PRIMARY, padx=10)
self.level_display.pack(side=tk.LEFT, padx=2)
# Increment button
inc_btn = tk.Button(level_ctrl, text="+", font=FONT_HEADER,
bg=BG_LIGHT, fg=TEXT_PRIMARY, bd=0, width=2,
activebackground=BG_HIGHLIGHT, cursor='hand2',
command=self.increment_level)
inc_btn.pack(side=tk.LEFT)
# Quick level buttons
button_frame = tk.Frame(control_frame, bg=BG_MEDIUM)
button_frame.pack(side=tk.LEFT, padx=25)
quick_levels = [1, 25, 40, 50]
for lvl in quick_levels:
btn = create_styled_button(button_frame, text=f"Lv{lvl}",
command=lambda l=lvl: self.set_level(l),
style_type='default')
btn.config(width=5, font=FONT_SMALL, padx=6, pady=3)
btn.pack(side=tk.LEFT, padx=3)
# Main content area
content_frame = tk.Frame(self, bg=BG_DARK)
content_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=15)
# Left: Current level effects
left_frame = ttk.LabelFrame(content_frame, text=" Current Level Effects ", padding=12)
left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0, 10))
self.current_effects = create_styled_text(left_frame, height=15)
self.current_effects.pack(fill=tk.BOTH, expand=True)
self.current_effects.config(state=tk.DISABLED)
# Right: Effect progression table
right_frame = ttk.LabelFrame(content_frame, text=" Effect Progression ", padding=12)
right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
# Treeview for effect table
columns = ('effect', 'lv1', 'lv25', 'lv40', 'lv50')
self.effects_tree = ttk.Treeview(right_frame, columns=columns, show='headings', height=12)
self.effects_tree.heading('effect', text='Effect', anchor='w')
self.effects_tree.column('effect', width=140, minwidth=120)
for col in columns[1:]:
level = col.replace('lv', 'Lv ')
self.effects_tree.heading(col, text=level)
self.effects_tree.column(col, width=65, anchor='center')
scrollbar = ttk.Scrollbar(right_frame, orient=tk.VERTICAL, command=self.effects_tree.yview)
self.effects_tree.configure(yscrollcommand=scrollbar.set)
self.effects_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
def show_legend(self):
"""Show effect explanations"""
legend = {
"Friendship Bonus": "Increases stats gained when training with this support card during Friendship Training (orange aura).",
"Motivation Bonus": "Increases stats gained based on your Uma's motivation level.",
"Specialty Rate": "Increases the chance of this card appearing in its specialty training.",
"Training Bonus": "Flat percentage increase to stats gained in training where this card is present.",
"Initial Bond": "Starting gauge value for this card.",
"Race Bonus": "Increases stats gained from racing.",
"Fan Count Bonus": "Increases fans gained from racing.",
"Skill Pt Bonus": "Bonus skill points gained when training with this card.",
"Hint Lv": "Starting level of skills taught by this card's hints.",
"Hint Rate": "Increases chance of getting a hint event."
}
text = "📖 Effect Explanations:\n\n"
for name, desc in legend.items():
text += f"{name}:\n {desc}\n\n"
messagebox.showinfo("Effect Legend", text)
def set_card(self, card_id):
"""Load a card's effects"""
self.current_card_id = card_id
# Get card info for max level
card = get_card_by_id(card_id)
if card:
self.current_card_name = card[1]
self.max_level = card[4]
if self.level_var.get() > self.max_level:
self.level_var.set(self.max_level)
self.level_display.config(text=str(self.max_level))
self.card_label.config(text=f"📊 {self.current_card_name}")
# Update displays
self.update_current_effects()
self.update_progression_table()
def set_level(self, level):
"""Set level from quick button"""
if level <= self.max_level:
self.level_var.set(level)
self.level_display.config(text=str(level))
self.update_current_effects()
def increment_level(self):
"""Increase level by 1"""
current = self.level_var.get()
if current < self.max_level:
self.set_level(current + 1)
def decrement_level(self):
"""Decrease level by 1"""
current = self.level_var.get()
if current > 1:
self.set_level(current - 1)
def update_current_effects(self):
"""Update the current level effects display"""
self.current_effects.config(state=tk.NORMAL)
self.current_effects.delete('1.0', tk.END)
# Configure tags
self.current_effects.tag_configure('header', font=FONT_SUBHEADER, foreground=ACCENT_PRIMARY)
self.current_effects.tag_configure('highlight', foreground=ACCENT_SUCCESS)
self.current_effects.tag_configure('effect_name', foreground=TEXT_SECONDARY)
self.current_effects.tag_configure('effect_value', foreground=TEXT_PRIMARY, font=FONT_BODY_BOLD)
self.current_effects.tag_configure('effect_tooltip', underline=False)
if not self.current_card_id:
self.current_effects.insert(tk.END, "No card selected\n\n", 'effect_name')
self.current_effects.insert(tk.END, "Select a card from the Card List tab to view its effects.", 'effect_name')
self.current_effects.config(state=tk.DISABLED)
return
level = self.level_var.get()
effects = get_effects_at_level(self.current_card_id, level)
self.current_effects.insert(tk.END, f"━━━ Level {level} ━━━\n\n", 'header')
if effects:
for name, value in effects:
# Highlight high values
prefix = ""
if '%' in str(value):
try:
num = int(str(value).replace('%', '').replace('+', ''))
if num >= 20:
prefix = ""
except:
pass
if prefix:
self.current_effects.insert(tk.END, prefix, 'highlight')
# Insert effect name with tooltip tag
tag_name = f"tooltip_{name.replace(' ', '_')}"
self.current_effects.insert(tk.END, f"{name}: ", ('effect_name', tag_name))
# Bind tooltip events
self.current_effects.tag_bind(tag_name, "<Enter>", lambda e, n=name: self.show_effect_tooltip(e, n))
self.current_effects.tag_bind(tag_name, "<Leave>", self.hide_effect_tooltip)
self.current_effects.insert(tk.END, f"{value}\n", 'effect_value')
else:
self.current_effects.insert(tk.END, "No effect data available for this level.\n\n", 'effect_name')
self.current_effects.insert(tk.END, "Try selecting: Lv 1, 25, 40, or 50", 'effect_name')
self.current_effects.config(state=tk.DISABLED)
def show_effect_tooltip(self, event, effect_name):
"""Show tooltip for effect"""
if effect_name in EFFECT_DESCRIPTIONS:
text = EFFECT_DESCRIPTIONS[effect_name]
x = event.x_root + 15
y = event.y_root + 10
# Close existing if any
self.hide_effect_tooltip(None)
self.tooltip_window = tk.Toplevel(self)
self.tooltip_window.wm_overrideredirect(True)
self.tooltip_window.wm_geometry(f"+{x}+{y}")
label = tk.Label(self.tooltip_window, text=text, justify=tk.LEFT,
background=BG_LIGHT, foreground=TEXT_PRIMARY,
relief=tk.SOLID, borderwidth=1, font=FONT_SMALL,
padx=10, pady=5, wraplength=250)
label.pack()
def hide_effect_tooltip(self, event):
"""Hide tooltip"""
if hasattr(self, 'tooltip_window') and self.tooltip_window:
self.tooltip_window.destroy()
self.tooltip_window = None
def update_progression_table(self):
"""Update the effect progression table"""
self.effects_tree.delete(*self.effects_tree.get_children())
if not self.current_card_id:
return
# Get all effects
all_effects = get_all_effects(self.current_card_id)
# Organize by effect name
effect_by_level = {}
for level, effect_name, effect_value in all_effects:
if effect_name not in effect_by_level:
effect_by_level[effect_name] = {}
effect_by_level[effect_name][level] = effect_value
# Key levels for the table
key_levels = [1, 25, 40, 50]
# Add rows
for effect_name, levels in sorted(effect_by_level.items()):
row = [effect_name]
for lvl in key_levels:
# Find closest level we have data for
value = levels.get(lvl, '')
if not value:
# Try to find nearest
for l in sorted(levels.keys()):
if l <= lvl:
value = levels[l]
row.append(value)
self.effects_tree.insert('', tk.END, values=row)

View File

@@ -1,180 +0,0 @@
"""
Hints and Skills View - Display support hints and event skills
"""
import tkinter as tk
from tkinter import ttk
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from db.db_queries import get_hints, get_events, get_all_event_skills, get_card_by_id
from gui.theme import (
BG_DARK, BG_MEDIUM, BG_LIGHT,
ACCENT_PRIMARY, ACCENT_SECONDARY, ACCENT_TERTIARY, ACCENT_SUCCESS,
TEXT_PRIMARY, TEXT_SECONDARY, TEXT_MUTED,
FONT_HEADER, FONT_SUBHEADER, FONT_BODY, FONT_BODY_BOLD, FONT_SMALL,
create_styled_text, create_card_frame
)
class HintsSkillsFrame(ttk.Frame):
"""Frame for viewing support hints and event skills"""
def __init__(self, parent):
super().__init__(parent)
self.current_card_id = None
self.current_card_name = None
self.create_widgets()
def create_widgets(self):
"""Create the hints and skills interface"""
# Header
header_frame = tk.Frame(self, bg=BG_DARK)
header_frame.pack(fill=tk.X, padx=20, pady=15)
self.card_label = tk.Label(header_frame,
text="💡 Select a card from the Card List tab",
font=FONT_HEADER, bg=BG_DARK, fg=ACCENT_PRIMARY)
self.card_label.pack(side=tk.LEFT)
# Main content with two columns
content_frame = tk.Frame(self, bg=BG_DARK)
content_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=(0, 15))
# Left column: Hints
left_container = tk.Frame(content_frame, bg=BG_DARK)
left_container.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0, 10))
hints_header = tk.Frame(left_container, bg=BG_DARK)
hints_header.pack(fill=tk.X, pady=(0, 8))
tk.Label(hints_header, text="🎯 Training Hints", font=FONT_SUBHEADER,
bg=BG_DARK, fg=TEXT_PRIMARY).pack(side=tk.LEFT)
hints_frame = create_card_frame(left_container)
hints_frame.pack(fill=tk.BOTH, expand=True)
self.hints_text = create_styled_text(hints_frame, height=18)
self.hints_text.pack(fill=tk.BOTH, expand=True, padx=2, pady=2)
self.hints_text.config(state=tk.DISABLED)
# Configure tags for hints
self.hints_text.tag_configure('header', font=FONT_SUBHEADER, foreground=ACCENT_PRIMARY)
self.hints_text.tag_configure('skill', foreground=ACCENT_TERTIARY, font=FONT_BODY_BOLD)
self.hints_text.tag_configure('desc', foreground=TEXT_MUTED)
self.hints_text.tag_configure('number', foreground=ACCENT_SECONDARY)
# Right column: Events and Skills
right_container = tk.Frame(content_frame, bg=BG_DARK)
right_container.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
events_header = tk.Frame(right_container, bg=BG_DARK)
events_header.pack(fill=tk.X, pady=(0, 8))
tk.Label(events_header, text="📅 Training Events & Skills", font=FONT_SUBHEADER,
bg=BG_DARK, fg=TEXT_PRIMARY).pack(side=tk.LEFT)
events_frame = create_card_frame(right_container)
events_frame.pack(fill=tk.BOTH, expand=True)
tree_container = tk.Frame(events_frame, bg=BG_MEDIUM)
tree_container.pack(fill=tk.BOTH, expand=True, padx=2, pady=2)
# Treeview for events
self.events_tree = ttk.Treeview(tree_container, columns=('event', 'skills'), show='tree headings')
self.events_tree.heading('#0', text='')
self.events_tree.heading('event', text='Event/Skill')
self.events_tree.heading('skills', text='Details')
self.events_tree.column('#0', width=35)
self.events_tree.column('event', width=240)
self.events_tree.column('skills', width=180)
scrollbar = ttk.Scrollbar(tree_container, orient=tk.VERTICAL, command=self.events_tree.yview)
self.events_tree.configure(yscrollcommand=scrollbar.set)
self.events_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# Summary section at bottom
summary_frame = tk.Frame(self, bg=BG_MEDIUM, padx=15, pady=10)
summary_frame.pack(fill=tk.X, padx=20, pady=(0, 10))
self.summary_label = tk.Label(summary_frame, text="", font=FONT_SMALL,
bg=BG_MEDIUM, fg=TEXT_SECONDARY)
self.summary_label.pack()
def set_card(self, card_id):
"""Load a card's hints and skills"""
self.current_card_id = card_id
# Get card info
card = get_card_by_id(card_id)
if card:
self.current_card_name = card[1]
self.card_label.config(text=f"💡 {self.current_card_name}")
self.update_hints_display()
self.update_events_display()
def update_hints_display(self):
"""Update the hints display"""
self.hints_text.config(state=tk.NORMAL)
self.hints_text.delete('1.0', tk.END)
if not self.current_card_id:
self.hints_text.insert(tk.END, "No card selected\n\n", 'desc')
self.hints_text.insert(tk.END, "Select a card from the Card List tab to view its hints.", 'desc')
self.hints_text.config(state=tk.DISABLED)
return
hints = get_hints(self.current_card_id)
self.hints_text.insert(tk.END, "Training Skills this card can teach:\n\n", 'header')
if hints:
for i, (hint_name, hint_desc) in enumerate(hints, 1):
self.hints_text.insert(tk.END, f" {i}. ", 'number')
self.hints_text.insert(tk.END, f"{hint_name}\n", 'skill')
if hint_desc:
self.hints_text.insert(tk.END, f" {hint_desc}\n", 'desc')
self.hints_text.insert(tk.END, "\n")
else:
self.hints_text.insert(tk.END, " No hints/skills data available.\n\n", 'desc')
self.hints_text.insert(tk.END, " This may mean:\n", 'desc')
self.hints_text.insert(tk.END, " • Card hasn't been scraped yet\n", 'desc')
self.hints_text.insert(tk.END, " • Card has no trainable skills\n", 'desc')
self.hints_text.config(state=tk.DISABLED)
def update_events_display(self):
"""Update the events tree display"""
self.events_tree.delete(*self.events_tree.get_children())
if not self.current_card_id:
return
events = get_events(self.current_card_id)
events_with_skills = get_all_event_skills(self.current_card_id)
# Add events as parent nodes
for event_id, event_name, event_type in events:
skills = events_with_skills.get(event_name, [])
skill_count = f"{len(skills)} skills" if skills else "No skills"
event_node = self.events_tree.insert('', tk.END, text='📅',
values=(event_name, skill_count))
# Add skills as children
for skill in skills:
self.events_tree.insert(event_node, tk.END, text='',
values=(skill, ''))
# Update summary
hint_count = len(get_hints(self.current_card_id))
event_count = len(events)
self.summary_label.config(
text=f"📊 Summary: {hint_count} hints │ {event_count} events"
)

View File

@@ -1,239 +0,0 @@
"""
Main Window for Umamusume Support Card Manager
Tabbed interface for card browsing, effects, deck builder, and hints
"""
import tkinter as tk
from tkinter import ttk
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from db.db_queries import get_database_stats, get_owned_count
from gui.card_view import CardListFrame
from gui.effects_view import EffectsFrame
from gui.hints_skills_view import HintsSkillsFrame
from gui.deck_builder import DeckBuilderFrame
from gui.update_dialog import show_update_dialog
from gui.theme import (
configure_styles, create_styled_button,
BG_DARK, BG_MEDIUM, BG_LIGHT, BG_HIGHLIGHT,
ACCENT_PRIMARY, ACCENT_SECONDARY, ACCENT_TERTIARY,
TEXT_PRIMARY, TEXT_SECONDARY, TEXT_MUTED,
FONT_TITLE, FONT_HEADER, FONT_BODY, FONT_SMALL
)
from utils import resolve_image_path
from version import VERSION
class MainWindow:
"""Main application window with tabbed interface"""
def __init__(self):
self.root = tk.Tk()
self.root.title("Umamusume Support Card Manager")
self.root.geometry("1350x800")
self.root.minsize(1350, 800)
# Set icon
try:
icon_path = resolve_image_path("1_Special Week.png")
if icon_path and os.path.exists(icon_path):
icon_img = tk.PhotoImage(file=icon_path)
self.root.iconphoto(True, icon_img)
except Exception as e:
print(f"Failed to set icon: {e}")
# Configure all styles using centralized theme
configure_styles(self.root)
# Create main container
main_container = ttk.Frame(self.root)
main_container.pack(fill=tk.BOTH, expand=True)
# Header with stats
self.create_header(main_container)
# Status bar - Create BEFORE notebook to anchor it to bottom
self.create_status_bar(main_container)
# Tabbed notebook
self.notebook = ttk.Notebook(main_container)
self.notebook.pack(fill=tk.BOTH, expand=True, padx=15, pady=8)
# Create tabs
self.create_tabs()
def create_header(self, parent):
"""Create header with database statistics and update button"""
# Header container with subtle bottom border effect
header_outer = tk.Frame(parent, bg=BG_DARK)
header_outer.pack(fill=tk.X)
header_frame = tk.Frame(header_outer, bg=BG_DARK)
header_frame.pack(fill=tk.X, padx=20, pady=15)
# Left side: Title and version
title_frame = tk.Frame(header_frame, bg=BG_DARK)
title_frame.pack(side=tk.LEFT)
# App icon and title
title_label = tk.Label(
title_frame,
text="🏇 Umamusume Support Card Manager",
font=FONT_TITLE,
bg=BG_DARK,
fg=ACCENT_PRIMARY
)
title_label.pack(side=tk.LEFT)
# Version badge
version_frame = tk.Frame(title_frame, bg=ACCENT_SECONDARY, padx=8, pady=2)
version_frame.pack(side=tk.LEFT, padx=12)
version_label = tk.Label(
version_frame,
text=f"v{VERSION}",
font=FONT_SMALL,
bg=ACCENT_SECONDARY,
fg=TEXT_PRIMARY
)
version_label.pack()
# Right side: Update button and stats
right_frame = tk.Frame(header_frame, bg=BG_DARK)
right_frame.pack(side=tk.RIGHT)
# Update button with modern styling
self.update_button = create_styled_button(
right_frame,
text="🔄 Check for Updates",
command=self.show_update_dialog,
style_type='default'
)
self.update_button.pack(side=tk.RIGHT, padx=(15, 0))
# Stats panel with card-like appearance
stats_frame = tk.Frame(right_frame, bg=BG_MEDIUM, padx=15, pady=8)
stats_frame.pack(side=tk.RIGHT)
stats = get_database_stats()
owned = get_owned_count()
# Build stats text with better formatting
stats_parts = [
f"📊 {stats.get('total_cards', 0)} Cards",
f"{owned} Owned",
f"🏆 {stats.get('by_rarity', {}).get('SSR', 0)} SSR",
f"{stats.get('by_rarity', {}).get('SR', 0)} SR",
f"{stats.get('by_rarity', {}).get('R', 0)} R"
]
stats_text = "".join(stats_parts)
self.stats_label = tk.Label(
stats_frame,
text=stats_text,
font=FONT_SMALL,
bg=BG_MEDIUM,
fg=TEXT_SECONDARY
)
self.stats_label.pack()
# Subtle separator line
separator = tk.Frame(header_outer, bg=BG_LIGHT, height=1)
separator.pack(fill=tk.X, padx=15)
def create_tabs(self):
"""Create all tab frames"""
# Card List Tab
self.card_frame = CardListFrame(self.notebook, on_card_selected_callback=self.on_card_selected)
self.notebook.add(self.card_frame, text=" 📋 Card List ")
# Effects Tab
self.effects_frame = EffectsFrame(self.notebook)
self.notebook.add(self.effects_frame, text=" 📊 Effects ")
# Deck Builder Tab
self.deck_frame = DeckBuilderFrame(self.notebook)
self.notebook.add(self.deck_frame, text=" 🎴 Deck Builder ")
# Hints & Skills Tab
self.hints_frame = HintsSkillsFrame(self.notebook)
self.notebook.add(self.hints_frame, text=" 💡 Hints & Skills ")
def create_status_bar(self, parent):
"""Create status bar at bottom"""
status_outer = tk.Frame(parent, bg=BG_MEDIUM)
status_outer.pack(fill=tk.X, side=tk.BOTTOM)
status_frame = tk.Frame(status_outer, bg=BG_MEDIUM)
status_frame.pack(fill=tk.X, padx=15, pady=8)
self.status_label = tk.Label(
status_frame,
text="✓ Ready",
font=FONT_SMALL,
bg=BG_MEDIUM,
fg=TEXT_MUTED
)
self.status_label.pack(side=tk.LEFT)
tk.Label(
status_frame,
text="Data from gametora.com",
font=FONT_SMALL,
bg=BG_MEDIUM,
fg=TEXT_MUTED
).pack(side=tk.RIGHT)
tk.Label(
status_frame,
text="Made by Kiyreload │ ",
font=FONT_SMALL,
bg=BG_MEDIUM,
fg=ACCENT_TERTIARY
).pack(side=tk.RIGHT)
def on_card_selected(self, card_id, card_name):
"""Handle card selection from card list"""
# Update other tabs with selected card
if hasattr(self, 'effects_frame'):
self.effects_frame.set_card(card_id)
if hasattr(self, 'hints_frame'):
self.hints_frame.set_card(card_id)
self.status_label.config(text=f"📌 Selected: {card_name}")
def refresh_stats(self):
"""Refresh the statistics display"""
stats = get_database_stats()
owned = get_owned_count()
stats_parts = [
f"📊 {stats.get('total_cards', 0)} Cards",
f"{owned} Owned",
f"🏆 {stats.get('by_rarity', {}).get('SSR', 0)} SSR",
f"{stats.get('by_rarity', {}).get('SR', 0)} SR",
f"{stats.get('by_rarity', {}).get('R', 0)} R"
]
stats_text = "".join(stats_parts)
self.stats_label.config(text=stats_text)
def show_update_dialog(self):
"""Show the update dialog"""
show_update_dialog(self.root)
def run(self):
"""Start the application"""
self.root.mainloop()
def main():
"""Entry point for GUI"""
app = MainWindow()
app.run()
if __name__ == "__main__":
main()

View File

@@ -1,461 +0,0 @@
"""
Centralized Theme Module for Umamusume Support Card Manager
Modern glassmorphism-inspired dark theme with consistent styling
"""
import tkinter as tk
from tkinter import ttk
# ═══════════════════════════════════════════════════════════════════════════════
# COLOR PALETTE
# ═══════════════════════════════════════════════════════════════════════════════
# Primary backgrounds (rich purplish-blues with depth)
BG_DARKEST = '#0d0d1a' # Deepest background
BG_DARK = '#151528' # Main application background
BG_MEDIUM = '#1e1e3f' # Card/panel backgrounds
BG_LIGHT = '#2a2a5a' # Elevated elements, hover states
BG_HIGHLIGHT = '#3d3d7a' # Active/selected backgrounds
# Accents (vibrant but refined)
ACCENT_PRIMARY = '#ff6b9d' # Pink accent (main action color)
ACCENT_SECONDARY = '#7c5cff' # Purple accent (secondary actions)
ACCENT_TERTIARY = '#5ce1e6' # Cyan accent (info/highlights)
ACCENT_SUCCESS = '#4ade80' # Green for success states
ACCENT_WARNING = '#fbbf24' # Amber for warnings
ACCENT_ERROR = '#ff6b6b' # Red for errors
# Text colors
TEXT_PRIMARY = '#ffffff' # Primary text (headings, important)
TEXT_SECONDARY = '#e0e0f0' # Secondary text (body text)
TEXT_MUTED = '#9090b0' # Muted text (labels, hints)
TEXT_DISABLED = '#606080' # Disabled text
# Rarity colors (enhanced with glow effect potential)
RARITY_SSR = '#ffd700' # Gold
RARITY_SR = '#c0c0c0' # Silver
RARITY_R = '#cd853f' # Bronze (warmer)
RARITY_COLORS = {
'SSR': RARITY_SSR,
'SR': RARITY_SR,
'R': RARITY_R
}
# Type colors (for card types)
TYPE_COLORS = {
'Speed': '#3b82f6', # Blue
'Stamina': '#f97316', # Orange
'Power': '#eab308', # Yellow
'Guts': '#ef4444', # Red
'Wisdom': '#22c55e', # Green
'Friend': '#a855f7', # Purple
'Group': '#f59e0b' # Amber
}
# Type icons
TYPE_ICONS = {
'Speed': '🏃',
'Stamina': '💚',
'Power': '💪',
'Guts': '🔥',
'Wisdom': '🧠',
'Friend': '💜',
'Group': '👥'
}
# ═══════════════════════════════════════════════════════════════════════════════
# FONTS
# ═══════════════════════════════════════════════════════════════════════════════
FONT_FAMILY = 'Segoe UI'
FONT_FAMILY_MONO = 'Consolas'
FONT_TITLE = (FONT_FAMILY, 18, 'bold')
FONT_HEADER = (FONT_FAMILY, 14, 'bold')
FONT_SUBHEADER = (FONT_FAMILY, 12, 'bold')
FONT_BODY = (FONT_FAMILY, 11)
FONT_BODY_BOLD = (FONT_FAMILY, 11, 'bold')
FONT_SMALL = (FONT_FAMILY, 10)
FONT_TINY = (FONT_FAMILY, 9)
FONT_MONO = (FONT_FAMILY_MONO, 11)
FONT_MONO_SMALL = (FONT_FAMILY_MONO, 10)
# ═══════════════════════════════════════════════════════════════════════════════
# STYLE CONFIGURATION
# ═══════════════════════════════════════════════════════════════════════════════
def configure_styles(root: tk.Tk):
"""Configure all ttk styles for the application"""
style = ttk.Style()
# Use clam theme as base for better customization
style.theme_use('clam')
# ─────────────────────────────────────────────────────────────────────────
# General Frame and Label styles
# ─────────────────────────────────────────────────────────────────────────
style.configure('TFrame', background=BG_DARK)
style.configure('TLabel', background=BG_DARK, foreground=TEXT_SECONDARY, font=FONT_BODY)
style.configure('TLabelframe', background=BG_DARK, foreground=TEXT_SECONDARY)
style.configure('TLabelframe.Label', background=BG_DARK, foreground=ACCENT_PRIMARY, font=FONT_SUBHEADER)
# Header styles
style.configure('Title.TLabel', font=FONT_TITLE, foreground=TEXT_PRIMARY, background=BG_DARK)
style.configure('Header.TLabel', font=FONT_HEADER, foreground=ACCENT_PRIMARY, background=BG_DARK)
style.configure('Subheader.TLabel', font=FONT_SUBHEADER, foreground=TEXT_PRIMARY, background=BG_DARK)
style.configure('Subtitle.TLabel', font=FONT_SMALL, foreground=TEXT_MUTED, background=BG_DARK)
style.configure('Stats.TLabel', font=FONT_SMALL, foreground=TEXT_SECONDARY, background=BG_MEDIUM, padding=8)
style.configure('Accent.TLabel', font=FONT_BODY, foreground=ACCENT_PRIMARY, background=BG_DARK)
# ─────────────────────────────────────────────────────────────────────────
# Button styles
# ─────────────────────────────────────────────────────────────────────────
style.configure('TButton',
padding=(12, 6),
font=FONT_BODY,
background=BG_LIGHT,
foreground=TEXT_PRIMARY)
style.map('TButton',
background=[('active', BG_HIGHLIGHT), ('pressed', ACCENT_PRIMARY)],
foreground=[('active', TEXT_PRIMARY), ('pressed', TEXT_PRIMARY)])
style.configure('Accent.TButton',
padding=(12, 6),
font=FONT_BODY_BOLD,
background=ACCENT_PRIMARY,
foreground=TEXT_PRIMARY)
style.map('Accent.TButton',
background=[('active', '#ff8ab5'), ('pressed', '#e55a88')])
style.configure('Small.TButton',
padding=(8, 4),
font=FONT_SMALL)
# ─────────────────────────────────────────────────────────────────────────
# Checkbutton styles
# ─────────────────────────────────────────────────────────────────────────
style.configure('TCheckbutton',
background=BG_DARK,
foreground=TEXT_SECONDARY,
font=FONT_BODY)
style.map('TCheckbutton',
background=[('active', BG_DARK)],
foreground=[('active', TEXT_PRIMARY)])
style.configure('Large.TCheckbutton',
font=FONT_BODY_BOLD,
background=BG_DARK,
foreground=TEXT_PRIMARY)
# ─────────────────────────────────────────────────────────────────────────
# Entry and Combobox styles
# ─────────────────────────────────────────────────────────────────────────
style.configure('TEntry',
fieldbackground=BG_MEDIUM,
foreground=TEXT_PRIMARY,
insertcolor=TEXT_PRIMARY,
padding=6)
style.configure('TCombobox',
fieldbackground=BG_MEDIUM,
background=BG_LIGHT,
foreground=TEXT_PRIMARY,
arrowcolor=TEXT_MUTED,
padding=4)
style.map('TCombobox',
fieldbackground=[('readonly', BG_MEDIUM)],
selectbackground=[('readonly', BG_HIGHLIGHT)])
# ─────────────────────────────────────────────────────────────────────────
# Notebook (Tab) styles
# ─────────────────────────────────────────────────────────────────────────
style.configure('TNotebook',
background=BG_DARK,
borderwidth=0)
style.configure('TNotebook.Tab',
padding=(20, 10),
font=FONT_BODY_BOLD,
background=BG_MEDIUM,
foreground=TEXT_MUTED)
style.map('TNotebook.Tab',
background=[('selected', BG_LIGHT), ('active', BG_HIGHLIGHT)],
foreground=[('selected', ACCENT_PRIMARY), ('active', TEXT_PRIMARY)],
expand=[('selected', (0, 0, 0, 2))])
# ─────────────────────────────────────────────────────────────────────────
# Treeview styles
# ─────────────────────────────────────────────────────────────────────────
style.configure('Treeview',
background=BG_MEDIUM,
foreground=TEXT_SECONDARY,
fieldbackground=BG_MEDIUM,
font=FONT_BODY,
rowheight=28)
style.configure('Treeview.Heading',
font=FONT_BODY_BOLD,
background=BG_LIGHT,
foreground=TEXT_PRIMARY,
padding=6)
style.map('Treeview',
background=[('selected', ACCENT_PRIMARY)],
foreground=[('selected', TEXT_PRIMARY)])
style.map('Treeview.Heading',
background=[('active', BG_HIGHLIGHT)])
# Card list with larger rows for thumbnails
style.configure('CardList.Treeview',
background=BG_MEDIUM,
foreground=TEXT_SECONDARY,
fieldbackground=BG_MEDIUM,
font=FONT_BODY,
rowheight=40)
# Deck list style
style.configure('DeckList.Treeview',
background=BG_MEDIUM,
foreground=TEXT_SECONDARY,
fieldbackground=BG_MEDIUM,
font=FONT_BODY,
rowheight=40)
style.map('DeckList.Treeview',
background=[('selected', ACCENT_PRIMARY)])
# ─────────────────────────────────────────────────────────────────────────
# Scale (Slider) styles
# ─────────────────────────────────────────────────────────────────────────
style.configure('TScale',
background=BG_DARK,
troughcolor=BG_MEDIUM,
sliderthickness=18)
style.configure('Horizontal.TScale',
background=BG_DARK)
# ─────────────────────────────────────────────────────────────────────────
# Progressbar styles
# ─────────────────────────────────────────────────────────────────────────
style.configure('TProgressbar',
background=ACCENT_PRIMARY,
troughcolor=BG_MEDIUM,
borderwidth=0,
thickness=8)
# ─────────────────────────────────────────────────────────────────────────
# Scrollbar styles
# ─────────────────────────────────────────────────────────────────────────
style.configure('TScrollbar',
background=BG_LIGHT,
troughcolor=BG_MEDIUM,
borderwidth=0,
arrowsize=14)
style.map('TScrollbar',
background=[('active', BG_HIGHLIGHT), ('pressed', ACCENT_PRIMARY)])
# ─────────────────────────────────────────────────────────────────────────
# PanedWindow styles
# ─────────────────────────────────────────────────────────────────────────
style.configure('TPanedwindow', background=BG_DARK)
# Set root background
root.configure(bg=BG_DARK)
# ═══════════════════════════════════════════════════════════════════════════════
# WIDGET HELPER FUNCTIONS
# ═══════════════════════════════════════════════════════════════════════════════
def create_styled_button(parent, text, command=None, style_type='default', **kwargs):
"""Create a styled tk.Button with modern appearance"""
bg_colors = {
'default': BG_LIGHT,
'accent': ACCENT_PRIMARY,
'secondary': ACCENT_SECONDARY,
'success': ACCENT_SUCCESS,
'warning': ACCENT_WARNING,
'danger': ACCENT_ERROR
}
hover_colors = {
'default': BG_HIGHLIGHT,
'accent': '#ff8ab5',
'secondary': '#9580ff',
'success': '#6ee7a0',
'warning': '#fcd34d',
'danger': '#ff8a8a'
}
bg = bg_colors.get(style_type, BG_LIGHT)
hover_bg = hover_colors.get(style_type, BG_HIGHLIGHT)
btn = tk.Button(
parent,
text=text,
command=command,
bg=bg,
fg=TEXT_PRIMARY,
font=FONT_BODY_BOLD if style_type == 'accent' else FONT_BODY,
activebackground=hover_bg,
activeforeground=TEXT_PRIMARY,
bd=0,
padx=16,
pady=8,
cursor='hand2',
relief=tk.FLAT,
**kwargs
)
# Add hover effect
def on_enter(e):
btn.configure(bg=hover_bg)
def on_leave(e):
btn.configure(bg=bg)
btn.bind('<Enter>', on_enter)
btn.bind('<Leave>', on_leave)
return btn
def create_styled_text(parent, height=10, **kwargs):
"""Create a styled tk.Text widget with modern appearance"""
text = tk.Text(
parent,
bg=BG_MEDIUM,
fg=TEXT_SECONDARY,
font=FONT_MONO,
insertbackground=TEXT_PRIMARY,
selectbackground=ACCENT_PRIMARY,
selectforeground=TEXT_PRIMARY,
relief=tk.FLAT,
padx=12,
pady=12,
height=height,
wrap=tk.WORD,
**kwargs
)
return text
def create_card_frame(parent, **kwargs):
"""Create a styled frame that looks like a card"""
frame = tk.Frame(
parent,
bg=BG_MEDIUM,
highlightthickness=1,
highlightbackground=BG_LIGHT,
**kwargs
)
return frame
def get_rarity_color(rarity):
"""Get the color for a card rarity"""
return RARITY_COLORS.get(rarity, TEXT_SECONDARY)
def get_type_color(card_type):
"""Get the color for a card type"""
return TYPE_COLORS.get(card_type, TEXT_SECONDARY)
def get_type_icon(card_type):
"""Get the emoji icon for a card type"""
return TYPE_ICONS.get(card_type, '')
# ═══════════════════════════════════════════════════════════════════════════════
# TOOLTIPS & HELPERS
# ═══════════════════════════════════════════════════════════════════════════════
EFFECT_DESCRIPTIONS = {
"Friendship Bonus": "Increases stats gained when training with this support card during Friendship Training (orange aura).",
"Motivation Bonus": "Increases stats gained based on your Uma's motivation level.",
"Specialty Rate": "Increases the chance of this card appearing in its specialty training.",
"Training Bonus": "Flat percentage increase to stats gained in training where this card is present.",
"Initial Bond": "Starting gauge value for this card.",
"Race Bonus": "Increases stats gained from racing.",
"Fan Count Bonus": "Increases fans gained from racing.",
"Skill Pt Bonus": "Bonus skill points gained when training with this card.",
"Hint Lv": "Starting level of skills taught by this card's hints.",
"Hint Rate": "Increases chance of getting a hint event.",
"Minigame Fail Rate": "Reduces chance of failing training.",
"Energy Usage": "Reduces energy consumed during training.",
"Current Energy": "Increases starting energy in scenario.",
"Vitality": "Increases vitality gain from events.",
"Stamina": "Increases stamina gain from training.",
"Speed": "Increases speed gain from training.",
"Power": "Increases power gain from training.",
"Guts": "Increases guts gain from training.",
"Wisdom": "Increases wisdom gain from training.",
"Logic": "Custom logic effect.",
"Starting Stats": "Increases initial stats at start of scenario."
}
class Tooltip:
"""
Creates a tooltip for a given widget as the mouse hovers above it.
"""
def __init__(self, widget, text):
self.widget = widget
self.text = text
self.tip_window = None
self.id = None
self.x = self.y = 0
self._id1 = self.widget.bind("<Enter>", self.enter)
self._id2 = self.widget.bind("<Leave>", self.leave)
self._id3 = self.widget.bind("<ButtonPress>", self.leave)
def enter(self, event=None):
self.schedule()
def leave(self, event=None):
self.unschedule()
self.hidetip()
def schedule(self):
self.unschedule()
self.id = self.widget.after(500, self.showtip)
def unschedule(self):
id = self.id
self.id = None
if id:
self.widget.after_cancel(id)
def showtip(self, event=None):
x = y = 0
try:
x, y, cx, cy = self.widget.bbox("insert")
except:
pass
x += self.widget.winfo_rootx() + 25
y += self.widget.winfo_rooty() + 20
# Creates a toplevel window
self.tip_window = tk.Toplevel(self.widget)
# Leaves only the label and removes the app window
self.tip_window.wm_overrideredirect(True)
self.tip_window.wm_geometry(f"+{x}+{y}")
label = tk.Label(
self.tip_window,
text=self.text,
justify=tk.LEFT,
background=BG_LIGHT,
foreground=TEXT_PRIMARY,
relief=tk.SOLID,
borderwidth=1,
font=FONT_SMALL,
padx=10,
pady=5
)
label.pack(ipadx=1)
def hidetip(self):
tw = self.tip_window
self.tip_window = None
if tw:
tw.destroy()
def create_tooltip(widget, text):
"""Create a tooltip for a widget"""
return Tooltip(widget, text)

View File

@@ -1,344 +0,0 @@
"""
Update Dialog for UmamusumeCardManager
Provides a modal dialog for the update process.
"""
import tkinter as tk
from tkinter import ttk, messagebox, scrolledtext
import threading
import webbrowser
from typing import Optional, Callable
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from updater.update_checker import check_for_updates, download_update, apply_update, get_current_version
from gui.theme import (
BG_DARK, BG_DARKEST, BG_MEDIUM, BG_LIGHT, BG_HIGHLIGHT,
ACCENT_PRIMARY, ACCENT_SECONDARY, ACCENT_SUCCESS, ACCENT_ERROR,
TEXT_PRIMARY, TEXT_SECONDARY, TEXT_MUTED,
FONT_HEADER, FONT_SUBHEADER, FONT_BODY, FONT_BODY_BOLD, FONT_SMALL,
create_styled_button
)
class UpdateDialog:
"""Modal dialog for checking and applying updates."""
def __init__(self, parent: tk.Tk, on_close_callback: Optional[Callable] = None):
self.parent = parent
self.on_close_callback = on_close_callback
self.update_info = None
self.download_thread = None
self.is_downloading = False
# Create the dialog window
self.dialog = tk.Toplevel(parent)
self.dialog.title("Check for Updates")
self.dialog.geometry("520x600")
self.dialog.resizable(True, True)
self.dialog.minsize(480, 500)
self.dialog.transient(parent)
self.dialog.grab_set()
# Center on parent
self.center_on_parent()
self.dialog.configure(bg=BG_DARK)
# Set up the UI
self.setup_ui()
# Start checking for updates
self.check_for_updates()
def center_on_parent(self):
"""Center the dialog on the parent window."""
self.dialog.update_idletasks()
parent_x = self.parent.winfo_x()
parent_y = self.parent.winfo_y()
parent_w = self.parent.winfo_width()
parent_h = self.parent.winfo_height()
dialog_w = 520
dialog_h = 600
x = parent_x + (parent_w - dialog_w) // 2
y = parent_y + (parent_h - dialog_h) // 2
self.dialog.geometry(f"{dialog_w}x{dialog_h}+{x}+{y}")
def setup_ui(self):
"""Set up the dialog UI."""
# Button frame (Create first to pack at bottom)
self.button_frame = tk.Frame(self.dialog, bg=BG_DARK, pady=20, padx=20)
self.button_frame.pack(side=tk.BOTTOM, fill=tk.X)
# Close button
self.close_button = create_styled_button(
self.button_frame,
text="Close",
command=self.close,
style_type='default'
)
self.close_button.pack(side=tk.RIGHT)
# Update button (hidden initially)
self.update_button = create_styled_button(
self.button_frame,
text="⬇️ Download & Install",
command=self.start_download,
style_type='accent'
)
# We don't pack it yet
# Main container
main_frame = tk.Frame(self.dialog, bg=BG_DARK, padx=25, pady=20)
main_frame.pack(fill=tk.BOTH, expand=True)
# Title
self.title_label = tk.Label(
main_frame,
text="🔄 Checking for Updates...",
font=FONT_HEADER,
bg=BG_DARK,
fg=ACCENT_PRIMARY
)
self.title_label.pack(pady=(0, 10))
# Status message
self.status_label = tk.Label(
main_frame,
text="Connecting to GitHub...",
font=FONT_BODY,
bg=BG_DARK,
fg=TEXT_MUTED,
wraplength=460
)
self.status_label.pack(pady=(0, 10))
# Version info frame
self.version_frame = tk.Frame(main_frame, bg=BG_MEDIUM, padx=15, pady=10)
self.version_frame.pack(fill=tk.X, pady=(0, 15))
self.current_version_label = tk.Label(
self.version_frame,
text=f"Current Version: v{get_current_version()}",
font=FONT_BODY,
bg=BG_MEDIUM,
fg=TEXT_SECONDARY
)
self.current_version_label.pack(anchor='w')
self.new_version_label = tk.Label(
self.version_frame,
text="Latest Version: Checking...",
font=FONT_BODY,
bg=BG_MEDIUM,
fg=TEXT_SECONDARY
)
self.new_version_label.pack(anchor='w')
# Release Notes Area
self.notes_label = tk.Label(
main_frame,
text="What's New:",
font=FONT_BODY_BOLD,
bg=BG_DARK,
fg=TEXT_PRIMARY
)
self.notes_label.pack(anchor='w', pady=(0, 5))
# Text box for release notes
self.notes_text = scrolledtext.ScrolledText(
main_frame,
height=10,
bg=BG_MEDIUM,
fg=TEXT_SECONDARY,
font=FONT_SMALL,
borderwidth=0,
highlightthickness=0,
padx=10,
pady=10
)
self.notes_text.pack(fill=tk.BOTH, expand=True, pady=(0, 15))
self.notes_text.insert(tk.END, "Checking for release notes...")
self.notes_text.config(state=tk.DISABLED)
# Progress bar (hidden initially)
self.progress_frame = tk.Frame(main_frame, bg=BG_DARK)
self.progress_frame.pack(fill=tk.X, pady=(0, 10))
self.progress_label = tk.Label(
self.progress_frame,
text="",
font=FONT_SMALL,
bg=BG_DARK,
fg=TEXT_MUTED
)
self.progress_label.pack(anchor='w', pady=(0, 5))
self.progress_bar = ttk.Progressbar(
self.progress_frame,
mode='indeterminate',
length=460
)
self.progress_bar.pack(fill=tk.X)
self.progress_bar.start(10)
def check_for_updates(self):
"""Check for updates in a background thread."""
def check():
self.update_info = check_for_updates()
self.dialog.after(0, self.update_check_complete)
thread = threading.Thread(target=check, daemon=True)
thread.start()
def update_check_complete(self):
"""Called when the update check is complete."""
self.progress_bar.stop()
self.progress_frame.pack_forget() # Hide progress bar when check is done
# Enable text box to update it
self.notes_text.config(state=tk.NORMAL)
self.notes_text.delete(1.0, tk.END)
if self.update_info:
# Update available!
self.title_label.config(text="🎉 Update Available!")
self.status_label.config(
text="A new version is available.",
fg=ACCENT_SUCCESS
)
self.new_version_label.config(
text=f"Latest Version: {self.update_info['new_version']}",
fg=ACCENT_SUCCESS
)
# Show Release Notes
notes = self.update_info.get('release_notes', 'No release notes available.')
self.notes_text.insert(tk.END, notes)
# Show update button
self.update_button.pack(side=tk.RIGHT, padx=(0, 10))
else:
# Up to date or error
self.title_label.config(text="✅ You're Up to Date!")
self.status_label.config(
text=f"You are running the latest version.",
fg=TEXT_SECONDARY
)
self.new_version_label.config(
text=f"Latest Version: v{get_current_version()}",
fg=ACCENT_SUCCESS
)
self.notes_text.insert(tk.END, "You are using the latest version of Umamusume Support Card Manager.\n\nEnjoy!")
self.notes_text.config(state=tk.DISABLED)
def start_download(self):
"""Start downloading the update."""
if self.is_downloading or not self.update_info:
return
self.is_downloading = True
self.update_button.config(state=tk.DISABLED, text="Downloading...")
self.close_button.config(state=tk.DISABLED)
self.title_label.config(text="⬇️ Downloading Update...")
self.status_label.config(text="Please wait...", fg=TEXT_MUTED)
# Configure progress bar for determinate mode
self.progress_frame.pack(fill=tk.X, pady=(0, 10)) # Show progress frame again
self.progress_bar.config(mode='determinate', maximum=100)
self.progress_bar.pack(fill=tk.X)
self.progress_bar['value'] = 0
def download():
def progress_callback(downloaded, total):
if total > 0:
percent = int((downloaded / total) * 100)
mb_downloaded = downloaded / (1024 * 1024)
mb_total = total / (1024 * 1024)
self.dialog.after(0, lambda: self.update_progress(percent, mb_downloaded, mb_total))
download_path = download_update(self.update_info['download_url'], progress_callback)
self.dialog.after(0, lambda: self.download_complete(download_path))
self.download_thread = threading.Thread(target=download, daemon=True)
self.download_thread.start()
def update_progress(self, percent: int, downloaded_mb: float, total_mb: float):
"""Update the progress bar."""
self.progress_bar['value'] = percent
self.progress_label.config(text=f"Downloaded: {downloaded_mb:.1f} MB / {total_mb:.1f} MB ({percent}%)")
def download_complete(self, download_path: Optional[str]):
"""Called when the download is complete."""
self.is_downloading = False
if download_path:
self.title_label.config(text="✅ Download Complete!")
self.status_label.config(
text="Update ready to install.",
fg=ACCENT_SUCCESS
)
# Change button to install
self.update_button.config(
state=tk.NORMAL,
text="🔄 Install & Restart",
command=lambda: self.install_update(download_path)
)
self.close_button.config(state=tk.NORMAL)
else:
self.title_label.config(text="❌ Download Failed")
self.status_label.config(
text="Failed not download update.",
fg=ACCENT_ERROR
)
self.update_button.config(state=tk.NORMAL, text="⬇️ Retry Download")
self.close_button.config(state=tk.NORMAL)
def install_update(self, download_path: str):
"""Install the downloaded update."""
self.title_label.config(text="🔄 Installing Update...")
self.status_label.config(text="Applying update...", fg=TEXT_MUTED)
self.update_button.config(state=tk.DISABLED)
self.close_button.config(state=tk.DISABLED)
if apply_update(download_path):
# Exit the application - the updater script will restart it
self.dialog.after(1000, lambda: self.parent.quit())
else:
messagebox.showinfo(
"Manual Update Required",
f"The update was downloaded but cannot be applied automatically.\n\n"
f"Downloaded file location:\n{download_path}\n\n"
f"Please replace the current executable manually.",
parent=self.dialog
)
self.close()
def close(self):
"""Close the dialog."""
if self.on_close_callback:
self.on_close_callback()
self.dialog.destroy()
def show_update_dialog(parent: tk.Tk, on_close_callback: Optional[Callable] = None) -> UpdateDialog:
"""
Show the update dialog.
Args:
parent: The parent Tk window
on_close_callback: Optional callback when dialog is closed
Returns:
The UpdateDialog instance
"""
return UpdateDialog(parent, on_close_callback)