feat: Implement initial GUI for deck, card, and skill management with CustomTkinter components.
This commit is contained in:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user