feat: Introduce db_queries for card data, migrations, and updates, and effects_view and theme for GUI components.
This commit is contained in:
@@ -1,298 +1,139 @@
|
||||
"""
|
||||
Effects View - Display support effects at all levels with interactive slider
|
||||
Effects Search View - Search for effects across all owned cards
|
||||
"""
|
||||
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, messagebox
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
|
||||
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 db.db_queries import search_owned_effects
|
||||
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
|
||||
FONT_HEADER, FONT_SUBHEADER, FONT_BODY, FONT_BODY_BOLD, FONT_SMALL,
|
||||
create_styled_button, create_styled_entry
|
||||
)
|
||||
|
||||
from utils import resolve_image_path
|
||||
|
||||
class EffectsFrame(ttk.Frame):
|
||||
"""Frame for viewing support effects at different levels"""
|
||||
"""Frame for searching effects across owned cards"""
|
||||
|
||||
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
|
||||
"""Create the effects search interface"""
|
||||
# Header / Search Bar
|
||||
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)
|
||||
# Search container
|
||||
search_container = tk.Frame(header_frame, bg=BG_DARK)
|
||||
search_container.pack(fill=tk.X)
|
||||
|
||||
# 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)
|
||||
tk.Label(search_container, text="🔍 Search Effect:",
|
||||
font=FONT_HEADER, bg=BG_DARK, fg=TEXT_PRIMARY).pack(side=tk.LEFT, padx=(0, 10))
|
||||
|
||||
# Level control frame
|
||||
control_frame = tk.Frame(self, bg=BG_MEDIUM, padx=15, pady=12)
|
||||
control_frame.pack(fill=tk.X, padx=20)
|
||||
self.search_var = tk.StringVar()
|
||||
self.search_entry = create_styled_entry(search_container, textvariable=self.search_var)
|
||||
self.search_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 10))
|
||||
self.search_entry.bind('<Return>', lambda e: self.perform_search())
|
||||
|
||||
# Level label
|
||||
tk.Label(control_frame, text="Level:", font=FONT_BODY,
|
||||
bg=BG_MEDIUM, fg=TEXT_SECONDARY).pack(side=tk.LEFT)
|
||||
search_btn = create_styled_button(search_container, text="Search",
|
||||
command=self.perform_search, style_type='primary')
|
||||
search_btn.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)
|
||||
# Example/Help text
|
||||
help_frame = tk.Frame(header_frame, bg=BG_DARK)
|
||||
help_frame.pack(fill=tk.X, pady=(5, 0))
|
||||
tk.Label(help_frame, text="Examples: Friendship, Motivation, Race Bonus, Skill Pt",
|
||||
font=FONT_SMALL, bg=BG_DARK, fg=TEXT_MUTED).pack(side=tk.LEFT)
|
||||
|
||||
# Results Area
|
||||
results_frame = ttk.LabelFrame(self, text=" Search Results (Owned Cards) ", padding=10)
|
||||
results_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=(0, 20))
|
||||
|
||||
# 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)
|
||||
# Treeview
|
||||
columns = ('card', 'level', 'current_value', 'effect_name')
|
||||
self.tree = ttk.Treeview(results_frame, columns=columns, show='headings', selectmode='browse')
|
||||
|
||||
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)
|
||||
self.tree.heading('card', text='Card Name', anchor='w')
|
||||
self.tree.heading('level', text='Level', anchor='center')
|
||||
self.tree.heading('current_value', text='Value', anchor='center')
|
||||
self.tree.heading('effect_name', text='Effect Name', anchor='w')
|
||||
|
||||
# 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)
|
||||
self.tree.column('card', width=250)
|
||||
self.tree.column('level', width=60, anchor='center')
|
||||
self.tree.column('current_value', width=80, anchor='center')
|
||||
self.tree.column('effect_name', width=150)
|
||||
|
||||
# Quick level buttons
|
||||
button_frame = tk.Frame(control_frame, bg=BG_MEDIUM)
|
||||
button_frame.pack(side=tk.LEFT, padx=25)
|
||||
scrollbar = ttk.Scrollbar(results_frame, orient=tk.VERTICAL, command=self.tree.yview)
|
||||
self.tree.configure(yscrollcommand=scrollbar.set)
|
||||
|
||||
quick_levels = [25, 30, 35, 40, 45, 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)
|
||||
self.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)
|
||||
# Status Label
|
||||
self.status_label = tk.Label(results_frame, text="", bg=BG_MEDIUM, fg=TEXT_SECONDARY, font=FONT_SMALL)
|
||||
self.status_label.pack(side=tk.BOTTOM, fill=tk.X, pady=(5, 0))
|
||||
|
||||
def parse_value(self, value_str):
|
||||
"""Parse effect value string to float for sorting"""
|
||||
try:
|
||||
# Extract number from string (e.g. "20%" -> 20, "+15" -> 15)
|
||||
# Remove non-numeric characters except . and -
|
||||
clean = re.sub(r'[^\d.-]', '', str(value_str))
|
||||
return float(clean)
|
||||
except:
|
||||
return -999999.0 # Sort to bottom if invalid
|
||||
|
||||
def perform_search(self):
|
||||
"""Execute search and update results"""
|
||||
term = self.search_var.get().strip()
|
||||
if not term:
|
||||
messagebox.showwarning("Search", "Please enter a search term")
|
||||
return
|
||||
|
||||
# clear current
|
||||
for item in self.tree.get_children():
|
||||
self.tree.delete(item)
|
||||
|
||||
# Query DB
|
||||
results = search_owned_effects(term)
|
||||
|
||||
if not results:
|
||||
self.status_label.config(text="No matching effects found among owned cards.")
|
||||
return
|
||||
|
||||
# Process and Sort
|
||||
# Row: (card_id, card_name, image_path, effect_name, effect_value, level)
|
||||
processed_results = []
|
||||
for r in results:
|
||||
val_num = self.parse_value(r[4])
|
||||
processed_results.append({
|
||||
'data': r,
|
||||
'sort_val': val_num
|
||||
})
|
||||
|
||||
# Sort by value descending
|
||||
processed_results.sort(key=lambda x: x['sort_val'], reverse=True)
|
||||
|
||||
# Populate Tree
|
||||
for item in processed_results:
|
||||
r = item['data']
|
||||
#Columns: card, level, current_value, effect_name
|
||||
values = (r[1], f"Lv {r[5]}", r[4], r[3])
|
||||
self.tree.insert('', tk.END, values=values)
|
||||
|
||||
self.status_label.config(text=f"Found {len(processed_results)} owned cards with matching effects.")
|
||||
|
||||
# Compatibility methods for main_window integration (empty as we don't need them anymore)
|
||||
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)
|
||||
pass
|
||||
|
||||
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)
|
||||
|
||||
11
gui/theme.py
11
gui/theme.py
@@ -256,6 +256,17 @@ def configure_styles(root: tk.Tk):
|
||||
# WIDGET HELPER FUNCTIONS
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
|
||||
def create_styled_entry(parent, textvariable=None, **kwargs):
|
||||
"""Create a styled tk.Entry with modern appearance"""
|
||||
entry = ttk.Entry(
|
||||
parent,
|
||||
textvariable=textvariable,
|
||||
font=FONT_BODY,
|
||||
**kwargs
|
||||
)
|
||||
return entry
|
||||
|
||||
def create_styled_button(parent, text, command=None, style_type='default', **kwargs):
|
||||
"""Create a styled tk.Button with modern appearance"""
|
||||
bg_colors = {
|
||||
|
||||
Reference in New Issue
Block a user