feat: Implement initial GUI for deck, card, and skill management with CustomTkinter components.

This commit is contained in:
kiyreload27
2026-01-04 19:31:01 +00:00
parent d8e4dd909d
commit 8749e9a1d8
9 changed files with 1130 additions and 901 deletions

View File

@@ -4,7 +4,7 @@ Tabbed interface for card browsing, effects, deck builder, and hints
"""
import tkinter as tk
from tkinter import ttk
import customtkinter as ctk
import sys
import os
@@ -32,27 +32,29 @@ class MainWindow:
"""Main application window with tabbed interface"""
def __init__(self):
self.root = tk.Tk()
# Initialize CTk root
self.root = ctk.CTk()
self.root.title("Umamusume Support Card Manager")
self.root.geometry("1400x850")
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):
# ctk uses iconbitmap for windows usually, but iconphoto works too
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 for legacy widgets
configure_styles(self.root)
# Create main container
main_container = ttk.Frame(self.root)
main_container.pack(fill=tk.BOTH, expand=True)
# Note: CTk already has a main frame in a way, but we'll use a container for padding
main_container = ctk.CTkFrame(self.root, fg_color="transparent")
main_container.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# State
self.last_selected_levels = {} # card_id -> level
@@ -60,56 +62,53 @@ class MainWindow:
# Header with stats
self.create_header(main_container)
# Status bar - Create BEFORE notebook to anchor it to bottom
# Status bar
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)
# Tabbed notebook -> CTkTabview
self.tabview = ctk.CTkTabview(main_container)
self.tabview.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 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)
# Header container
header_frame = ctk.CTkFrame(parent, fg_color="transparent")
header_frame.pack(fill=tk.X, padx=10, pady=(0, 10))
# Left side: Title and version
title_frame = tk.Frame(header_frame, bg=BG_DARK)
title_frame = ctk.CTkFrame(header_frame, fg_color="transparent")
title_frame.pack(side=tk.LEFT)
# App icon and title
title_label = tk.Label(
title_label = ctk.CTkLabel(
title_frame,
text="🏇 Umamusume Support Card Manager",
font=FONT_TITLE,
bg=BG_DARK,
fg=ACCENT_PRIMARY
text_color=ACCENT_PRIMARY
)
title_label.pack(side=tk.LEFT)
title_label.pack(side=tk.LEFT, padx=(0, 10))
# 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,
version_label = ctk.CTkLabel(
title_frame,
text=f"v{VERSION}",
font=FONT_SMALL,
bg=ACCENT_SECONDARY,
fg=TEXT_PRIMARY
fg_color=ACCENT_SECONDARY,
text_color=TEXT_PRIMARY,
corner_radius=6,
height=24,
width=60
)
version_label.pack()
version_label.pack(side=tk.LEFT)
# Right side: Update button and stats
right_frame = tk.Frame(header_frame, bg=BG_DARK)
right_frame = ctk.CTkFrame(header_frame, fg_color="transparent")
right_frame.pack(side=tk.RIGHT)
# Update button with modern styling
# Update button
self.update_button = create_styled_button(
right_frame,
text="🔄 Check for Updates",
@@ -118,122 +117,96 @@ class MainWindow:
)
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,
# Stats panel
self.stats_label = ctk.CTkLabel(
right_frame,
text="Loading stats...",
font=FONT_SMALL,
bg=BG_MEDIUM,
fg=TEXT_SECONDARY
fg_color=BG_MEDIUM,
text_color=TEXT_SECONDARY,
corner_radius=8,
padx=15,
pady=5
)
self.stats_label.pack()
self.stats_label.pack(side=tk.RIGHT)
# Subtle separator line
separator = tk.Frame(header_outer, bg=BG_LIGHT, height=1)
separator.pack(fill=tk.X, padx=15)
# Initial stats load
self.refresh_stats()
def create_tabs(self):
"""Create all tab frames"""
# Add tabs
tab_cards = self.tabview.add(" 📋 Card List ")
tab_effects = self.tabview.add(" 📊 Search Effects ")
tab_deck = self.tabview.add(" 🎴 Deck Builder ")
tab_search = self.tabview.add(" 🔍 Skill Search ")
tab_skills = self.tabview.add(" 📜 Deck Skills ")
# Card List Tab
self.card_frame = CardListFrame(self.notebook,
# Note: CardListFrame and others inherit from ttk.Frame/tk.Frame.
# We need to make sure they can be packed into a CTkFrame (the tab).
self.card_frame = CardListFrame(tab_cards,
on_card_selected_callback=self.on_card_selected,
on_stats_updated_callback=self.refresh_stats)
self.notebook.add(self.card_frame, text=" 📋 Card List ")
self.card_frame.pack(fill=tk.BOTH, expand=True)
# Effects Tab
self.effects_frame = EffectsFrame(self.notebook)
self.notebook.add(self.effects_frame, text=" 📊 Effects ")
self.effects_frame = EffectsFrame(tab_effects)
self.effects_frame.pack(fill=tk.BOTH, expand=True)
# Deck Builder Tab
self.deck_frame = DeckBuilderFrame(self.notebook)
self.notebook.add(self.deck_frame, text=" 🎴 Deck Builder ")
self.deck_frame = DeckBuilderFrame(tab_deck)
self.deck_frame.pack(fill=tk.BOTH, expand=True)
# Skill Search Tab
self.hints_frame = SkillSearchFrame(self.notebook)
self.notebook.add(self.hints_frame, text=" 🔍 Skill Search ")
self.hints_frame = SkillSearchFrame(tab_search)
self.hints_frame.pack(fill=tk.BOTH, expand=True)
# Deck Skills Tab
self.deck_skills_frame = DeckSkillsFrame(self.notebook)
self.notebook.add(self.deck_skills_frame, text=" 📜 Deck Skills ")
self.deck_skills_frame = DeckSkillsFrame(tab_skills)
self.deck_skills_frame.pack(fill=tk.BOTH, expand=True)
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)
# Using pack side=BOTTOM relative to the main container
status_frame = ctk.CTkFrame(parent, height=30, fg_color="transparent")
status_frame.pack(fill=tk.X, side=tk.BOTTOM, pady=(10, 0))
status_frame = tk.Frame(status_outer, bg=BG_MEDIUM)
status_frame.pack(fill=tk.X, padx=15, pady=8)
self.status_label = tk.Label(
self.status_label = ctk.CTkLabel(
status_frame,
text="✓ Ready",
font=FONT_SMALL,
bg=BG_MEDIUM,
fg=TEXT_MUTED
text_color=TEXT_MUTED
)
self.status_label.pack(side=tk.LEFT)
self.status_label.pack(side=tk.LEFT, padx=10)
tk.Label(
ctk.CTkLabel(
status_frame,
text="Data from gametora.com",
font=FONT_SMALL,
bg=BG_MEDIUM,
fg=TEXT_MUTED
text_color=TEXT_MUTED
).pack(side=tk.RIGHT)
# Diagnostics Button
diag_btn = tk.Button(
status_frame,
text="🔍 Diagnostics",
font=FONT_SMALL,
bg=BG_MEDIUM,
fg=ACCENT_TERTIARY,
bd=0,
activebackground=BG_LIGHT,
activeforeground=TEXT_PRIMARY,
cursor='hand2',
command=self.show_diagnostics
)
diag_btn.pack(side=tk.RIGHT, padx=15)
tk.Label(
ctk.CTkLabel(
status_frame,
text="VibeCoded by Kiyreload │ ",
font=FONT_SMALL,
bg=BG_MEDIUM,
fg=ACCENT_TERTIARY
text_color=ACCENT_TERTIARY
).pack(side=tk.RIGHT)
def on_card_selected(self, card_id, card_name, level=None):
"""Handle card selection from card list"""
# Store level if provided
if level is not None:
self.last_selected_levels[card_id] = level
self.selected_card_id = card_id # Update selected_card_id
self.selected_card_id = card_id
# Update other tabs with selected card
# Update other tabs
if hasattr(self, 'effects_frame'):
self.effects_frame.set_card(card_id)
if hasattr(self, 'deck_skills_frame'):
self.deck_skills_frame.set_card(card_id)
self.status_label.config(text=f"📌 Selected: {card_name}")
self.status_label.configure(text=f"📌 Selected: {card_name}")
def refresh_stats(self):
"""Refresh the statistics display"""
@@ -249,70 +222,13 @@ class MainWindow:
]
stats_text = "".join(stats_parts)
self.stats_label.config(text=stats_text)
if hasattr(self, 'stats_label'):
self.stats_label.configure(text=stats_text)
def show_update_dialog(self):
"""Show the update dialog"""
show_update_dialog(self.root)
def show_diagnostics(self):
"""Show diagnostics information for debugging"""
diag_win = tk.Toplevel(self.root)
diag_win.title("System Diagnostics")
diag_win.geometry("700x500")
diag_win.configure(bg=BG_DARK)
from db.db_queries import DB_PATH
import platform
# Info text
info = [
f"--- Application Info ---",
f"Version: {VERSION}",
f"Frozen (EXE): {getattr(sys, 'frozen', False)}",
f"Python: {sys.version}",
f"Platform: {platform.platform()}",
"",
f"--- Database ---",
f"DB Path: {DB_PATH}",
f"DB Exists: {os.path.exists(DB_PATH)}",
"",
f"--- Search Paths ---",
f"Executable: {sys.executable}",
f"Script Root: {os.path.dirname(os.path.abspath(__file__))}",
f"MEIPASS (Temp): {getattr(sys, '_MEIPASS', 'N/A')}",
"",
f"--- Image Check ---"
]
# Check some images
img_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'images')
info.append(f"Images Dir (Source): {img_dir}")
info.append(f"Exists: {os.path.exists(img_dir)}")
if os.path.exists(img_dir):
files = os.listdir(img_dir)
info.append(f"Files found: {len(files)}")
if len(files) > 0:
info.append(f"Sample: {files[0]}")
content = "\n".join(info)
# Display area
text_frame = tk.Frame(diag_win, bg=BG_DARK, padx=20, pady=20)
text_frame.pack(fill=tk.BOTH, expand=True)
from gui.theme import create_styled_text
text_area = create_styled_text(text_frame)
text_area.pack(fill=tk.BOTH, expand=True)
text_area.insert(tk.END, content)
text_area.config(state=tk.DISABLED)
# Close button
btn_frame = tk.Frame(diag_win, bg=BG_DARK, pady=15)
btn_frame.pack(fill=tk.X)
create_styled_button(btn_frame, text="Close", command=diag_win.destroy).pack()
def run(self):
"""
Start the GUI application and display the main window.