feat: Introduce db_queries for card data, migrations, and updates, and effects_view and theme for GUI components.

This commit is contained in:
kiyreload27
2026-01-02 02:34:34 +00:00
parent 6e2fe461ae
commit ebc0f132db
3 changed files with 154 additions and 259 deletions

View File

@@ -522,6 +522,49 @@ def get_unique_effect_names(card_id):
conn.close() conn.close()
return rows return rows
def search_owned_effects(search_term):
"""
Search for effects among owned cards.
Returns list of (card_id, card_name, image_path, effect_name, effect_value, level)
"""
conn = get_conn()
cur = conn.cursor()
# We need to join support_effects with owned_cards to get the level
# But wait, owned_cards has a level column. support_effects stores effects for specific levels.
# So we need to match support_effects.level with owned_cards.level
# OR find the effect for the closest level <= owned level (if effects aren't stored for every single level)
# The current DB schema seems to store effects for specific levels (1, 5, 10, ...).
# If a card is level 49, `get_effects_at_level` usually queries for exact level match.
# Let's check `get_effects_at_level` implementation: "WHERE card_id = ? AND level = ?"
# So if I have a card at level 49, and effects are only defined at 45 and 50, query for 49 returns nothing?
# That would be a bug or assumption in the current app.
# Let's look at `update_progression_table` in `effects_view.py`. It does some "nearest level" logic.
# For this search feature, to be robust, we should probably fetch ALL effects for the card
# and filter for the one active at the owned level.
# OR, assuming the scraper/DB populates "current" effects effectively.
# Actually, the most robust way in SQL for "value at level X" given sparse data is complex.
# However, let's assume for now we want exact matches or we'll handle the "effective level" logic in Python?
# No, that's too slow for search.
# Let's look at how `get_effects_at_level` is used.
# It is used in `update_current_effects` with `self.level_var.get()`.
# It expects an exact match.
# So we should probably join on `oc.level`.
query = """
SELECT sc.card_id, sc.name, sc.image_path, se.effect_name, se.effect_value, oc.level
FROM owned_cards oc
JOIN support_cards sc ON oc.card_id = sc.card_id
JOIN support_effects se ON oc.card_id = se.card_id AND oc.level = se.level
WHERE se.effect_name LIKE ?
ORDER BY sc.name
"""
cur.execute(query, (f"%{search_term}%",))
rows = cur.fetchall()
conn.close()
return rows
# ============================================ # ============================================
# Hint Queries # Hint Queries
# ============================================ # ============================================

View File

@@ -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 import tkinter as tk
from tkinter import ttk, messagebox from tkinter import ttk, messagebox
import sys import sys
import os import os
import re
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 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 ( from gui.theme import (
BG_DARK, BG_MEDIUM, BG_LIGHT, BG_HIGHLIGHT, BG_DARK, BG_MEDIUM, BG_LIGHT, BG_HIGHLIGHT,
ACCENT_PRIMARY, ACCENT_SECONDARY, ACCENT_SUCCESS, ACCENT_TERTIARY, ACCENT_PRIMARY, ACCENT_SECONDARY, ACCENT_SUCCESS, ACCENT_TERTIARY,
TEXT_PRIMARY, TEXT_SECONDARY, TEXT_MUTED, TEXT_PRIMARY, TEXT_SECONDARY, TEXT_MUTED,
FONT_HEADER, FONT_SUBHEADER, FONT_BODY, FONT_BODY_BOLD, FONT_SMALL, FONT_MONO, FONT_HEADER, FONT_SUBHEADER, FONT_BODY, FONT_BODY_BOLD, FONT_SMALL,
create_styled_button, create_styled_text, create_card_frame, create_styled_button, create_styled_entry
EFFECT_DESCRIPTIONS
) )
from utils import resolve_image_path
class EffectsFrame(ttk.Frame): class EffectsFrame(ttk.Frame):
"""Frame for viewing support effects at different levels""" """Frame for searching effects across owned cards"""
def __init__(self, parent): def __init__(self, parent):
super().__init__(parent) super().__init__(parent)
self.current_card_id = None
self.current_card_name = None
self.max_level = 50
self.create_widgets() self.create_widgets()
def create_widgets(self): def create_widgets(self):
"""Create the effects view interface""" """Create the effects search interface"""
# Header # Header / Search Bar
header_frame = tk.Frame(self, bg=BG_DARK) header_frame = tk.Frame(self, bg=BG_DARK)
header_frame.pack(fill=tk.X, padx=20, pady=15) 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", # Search container
font=FONT_HEADER, bg=BG_DARK, fg=ACCENT_PRIMARY) search_container = tk.Frame(header_frame, bg=BG_DARK)
self.card_label.pack(side=tk.LEFT) search_container.pack(fill=tk.X)
# Legend Button tk.Label(search_container, text="🔍 Search Effect:",
legend_btn = create_styled_button(header_frame, text="❓ Legend", font=FONT_HEADER, bg=BG_DARK, fg=TEXT_PRIMARY).pack(side=tk.LEFT, padx=(0, 10))
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 self.search_var = tk.StringVar()
control_frame = tk.Frame(self, bg=BG_MEDIUM, padx=15, pady=12) self.search_entry = create_styled_entry(search_container, textvariable=self.search_var)
control_frame.pack(fill=tk.X, padx=20) 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 search_btn = create_styled_button(search_container, text="Search",
tk.Label(control_frame, text="Level:", font=FONT_BODY, command=self.perform_search, style_type='primary')
bg=BG_MEDIUM, fg=TEXT_SECONDARY).pack(side=tk.LEFT) search_btn.pack(side=tk.LEFT)
# Level display with increment/decrement buttons # Example/Help text
level_ctrl = tk.Frame(control_frame, bg=BG_MEDIUM) help_frame = tk.Frame(header_frame, bg=BG_DARK)
level_ctrl.pack(side=tk.LEFT, padx=15) 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 # Treeview
dec_btn = tk.Button(level_ctrl, text="", font=FONT_HEADER, columns = ('card', 'level', 'current_value', 'effect_name')
bg=BG_LIGHT, fg=TEXT_PRIMARY, bd=0, width=2, self.tree = ttk.Treeview(results_frame, columns=columns, show='headings', selectmode='browse')
activebackground=BG_HIGHLIGHT, cursor='hand2',
command=self.decrement_level)
dec_btn.pack(side=tk.LEFT)
self.level_var = tk.IntVar(value=50) self.tree.heading('card', text='Card Name', anchor='w')
self.level_display = tk.Label(level_ctrl, text="50", width=4, font=FONT_HEADER, self.tree.heading('level', text='Level', anchor='center')
bg=BG_LIGHT, fg=ACCENT_PRIMARY, padx=10) self.tree.heading('current_value', text='Value', anchor='center')
self.level_display.pack(side=tk.LEFT, padx=2) self.tree.heading('effect_name', text='Effect Name', anchor='w')
# Increment button self.tree.column('card', width=250)
inc_btn = tk.Button(level_ctrl, text="+", font=FONT_HEADER, self.tree.column('level', width=60, anchor='center')
bg=BG_LIGHT, fg=TEXT_PRIMARY, bd=0, width=2, self.tree.column('current_value', width=80, anchor='center')
activebackground=BG_HIGHLIGHT, cursor='hand2', self.tree.column('effect_name', width=150)
command=self.increment_level)
inc_btn.pack(side=tk.LEFT)
# Quick level buttons scrollbar = ttk.Scrollbar(results_frame, orient=tk.VERTICAL, command=self.tree.yview)
button_frame = tk.Frame(control_frame, bg=BG_MEDIUM) self.tree.configure(yscrollcommand=scrollbar.set)
button_frame.pack(side=tk.LEFT, padx=25)
quick_levels = [25, 30, 35, 40, 45, 50] self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
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) 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" # Status Label
for name, desc in legend.items(): self.status_label = tk.Label(results_frame, text="", bg=BG_MEDIUM, fg=TEXT_SECONDARY, font=FONT_SMALL)
text += f"{name}:\n {desc}\n\n" self.status_label.pack(side=tk.BOTTOM, fill=tk.X, pady=(5, 0))
messagebox.showinfo("Effect Legend", text)
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): def set_card(self, card_id):
"""Load a card's effects""" pass
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

@@ -256,6 +256,17 @@ def configure_styles(root: tk.Tk):
# WIDGET HELPER FUNCTIONS # 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): def create_styled_button(parent, text, command=None, style_type='default', **kwargs):
"""Create a styled tk.Button with modern appearance""" """Create a styled tk.Button with modern appearance"""
bg_colors = { bg_colors = {