feat: Add skill search and deck skill views, integrating them into the main application window.
This commit is contained in:
215
gui/deck_skills_view.py
Normal file
215
gui/deck_skills_view.py
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
"""
|
||||||
|
Deck Skills View - Detailed breakdown of all skills in a deck or for a single card
|
||||||
|
"""
|
||||||
|
|
||||||
|
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_decks, get_deck_cards, get_card_by_id,
|
||||||
|
get_hints, get_all_event_skills
|
||||||
|
)
|
||||||
|
from utils import resolve_image_path
|
||||||
|
from gui.theme import (
|
||||||
|
BG_DARK, BG_MEDIUM, BG_LIGHT, BG_HIGHLIGHT,
|
||||||
|
ACCENT_PRIMARY, ACCENT_SECONDARY, ACCENT_TERTIARY,
|
||||||
|
TEXT_PRIMARY, TEXT_SECONDARY, TEXT_MUTED,
|
||||||
|
FONT_HEADER, FONT_SUBHEADER, FONT_BODY, FONT_BODY_BOLD, FONT_SMALL,
|
||||||
|
create_card_frame, get_type_icon
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DeckSkillsFrame(ttk.Frame):
|
||||||
|
"""Frame for viewing combined skills of a deck or individual cards"""
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.icon_cache = {}
|
||||||
|
self.current_mode = "Deck" # or "Single"
|
||||||
|
|
||||||
|
self.create_widgets()
|
||||||
|
self.refresh_decks()
|
||||||
|
|
||||||
|
def create_widgets(self):
|
||||||
|
"""Create the deck skills interface"""
|
||||||
|
# Header / Controls
|
||||||
|
ctrl_frame = tk.Frame(self, bg=BG_DARK)
|
||||||
|
ctrl_frame.pack(fill=tk.X, padx=20, pady=15)
|
||||||
|
|
||||||
|
# Left side: Mode/Deck selection
|
||||||
|
selection_frame = tk.Frame(ctrl_frame, bg=BG_DARK)
|
||||||
|
selection_frame.pack(side=tk.LEFT)
|
||||||
|
|
||||||
|
tk.Label(selection_frame, text="🎴 Select Deck:", font=FONT_BODY,
|
||||||
|
bg=BG_DARK, fg=TEXT_SECONDARY).pack(side=tk.LEFT)
|
||||||
|
|
||||||
|
self.deck_combo = ttk.Combobox(selection_frame, width=30, state='readonly')
|
||||||
|
self.deck_combo.pack(side=tk.LEFT, padx=10)
|
||||||
|
self.deck_combo.bind('<<ComboboxSelected>>', self.on_deck_selected)
|
||||||
|
|
||||||
|
# Mode indicator/Description
|
||||||
|
self.mode_label = tk.Label(ctrl_frame, text="Showing skills for selected deck",
|
||||||
|
font=FONT_HEADER, bg=BG_DARK, fg=ACCENT_PRIMARY)
|
||||||
|
self.mode_label.pack(side=tk.RIGHT)
|
||||||
|
|
||||||
|
# Main Results Tree
|
||||||
|
tree_container = create_card_frame(self)
|
||||||
|
tree_container.pack(fill=tk.BOTH, expand=True, padx=20, pady=(0, 15))
|
||||||
|
|
||||||
|
cols = ('owned', 'skill', 'card', 'rarity', 'source', 'details')
|
||||||
|
self.tree = ttk.Treeview(tree_container, columns=cols, show='tree headings',
|
||||||
|
style="Treeview")
|
||||||
|
|
||||||
|
self.tree.heading('#0', text='')
|
||||||
|
self.tree.heading('owned', text='★')
|
||||||
|
self.tree.heading('skill', text='Skill Name')
|
||||||
|
self.tree.heading('card', text='Provided By')
|
||||||
|
self.tree.heading('rarity', text='Rarity')
|
||||||
|
self.tree.heading('source', text='Source')
|
||||||
|
self.tree.heading('details', text='Details / Other Event Skills')
|
||||||
|
|
||||||
|
self.tree.column('#0', width=45, anchor='center')
|
||||||
|
self.tree.column('owned', width=30, anchor='center')
|
||||||
|
self.tree.column('skill', width=150)
|
||||||
|
self.tree.column('card', width=180)
|
||||||
|
self.tree.column('rarity', width=50, anchor='center')
|
||||||
|
self.tree.column('source', width=100)
|
||||||
|
self.tree.column('details', width=400)
|
||||||
|
|
||||||
|
scrollbar = ttk.Scrollbar(tree_container, orient=tk.VERTICAL, command=self.tree.yview)
|
||||||
|
self.tree.configure(yscrollcommand=scrollbar.set)
|
||||||
|
|
||||||
|
self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=2, pady=2)
|
||||||
|
scrollbar.pack(side=tk.RIGHT, fill=tk.Y, pady=2)
|
||||||
|
|
||||||
|
# Footer
|
||||||
|
self.stats_label = tk.Label(self, text="", font=FONT_SMALL,
|
||||||
|
bg=BG_DARK, fg=TEXT_MUTED)
|
||||||
|
self.stats_label.pack(anchor='e', pady=5, padx=20)
|
||||||
|
|
||||||
|
def refresh_decks(self):
|
||||||
|
"""Load decks into combobox"""
|
||||||
|
decks = get_all_decks()
|
||||||
|
self.deck_combo['values'] = [f"{d[0]}: {d[1]}" for d in decks]
|
||||||
|
if decks:
|
||||||
|
self.deck_combo.current(0)
|
||||||
|
self.on_deck_selected(None)
|
||||||
|
|
||||||
|
def on_deck_selected(self, event):
|
||||||
|
"""Handle deck selection"""
|
||||||
|
selection = self.deck_combo.get()
|
||||||
|
if not selection: return
|
||||||
|
|
||||||
|
deck_id = int(selection.split(':')[0])
|
||||||
|
deck_name = selection.split(': ')[1]
|
||||||
|
|
||||||
|
self.current_mode = "Deck"
|
||||||
|
self.mode_label.config(text=f"Deck: {deck_name}", fg=ACCENT_PRIMARY)
|
||||||
|
self.show_deck_skills(deck_id)
|
||||||
|
|
||||||
|
def show_deck_skills(self, deck_id):
|
||||||
|
"""Fetch and display all skills from a deck"""
|
||||||
|
# Clear tree
|
||||||
|
for item in self.tree.get_children():
|
||||||
|
self.tree.delete(item)
|
||||||
|
|
||||||
|
deck_cards = get_deck_cards(deck_id)
|
||||||
|
if not deck_cards:
|
||||||
|
self.stats_label.config(text="Deck is empty")
|
||||||
|
return
|
||||||
|
|
||||||
|
total_skills = 0
|
||||||
|
for card_row in deck_cards:
|
||||||
|
# card_row format: (slot_pos, level, card_id, name, rarity, type, image_path)
|
||||||
|
# But wait, get_deck_cards in db_queries has a specific return
|
||||||
|
# Let's re-verify: ds.slot_position, ds.level, sc.card_id, sc.name, sc.rarity, sc.card_type, sc.image_path
|
||||||
|
slot_pos, level, card_id, name, rarity, card_type, image_path = card_row
|
||||||
|
|
||||||
|
# Since get_deck_cards doesn't return ownership (usually cards in deck ARE owned but maybe not)
|
||||||
|
# Let's get full card data for set_card logic consistency
|
||||||
|
card_full = get_card_by_id(card_id)
|
||||||
|
is_owned = bool(card_full[7]) if card_full else False
|
||||||
|
|
||||||
|
# 1. Hints
|
||||||
|
hints = get_hints(card_id)
|
||||||
|
for h_name, h_desc in hints:
|
||||||
|
self.add_skill_row(h_name, name, rarity, card_type, image_path, "Training Hint", h_desc, is_owned, card_id)
|
||||||
|
total_skills += 1
|
||||||
|
|
||||||
|
# 2. Event Skills
|
||||||
|
events = get_all_event_skills(card_id)
|
||||||
|
for ev_name, skills in events.items():
|
||||||
|
summary = ", ".join(skills)
|
||||||
|
for s_name in skills:
|
||||||
|
self.add_skill_row(s_name, name, rarity, card_type, image_path, "Event", f"{ev_name} ({summary})", is_owned, card_id)
|
||||||
|
total_skills += 1
|
||||||
|
|
||||||
|
self.stats_label.config(text=f"Found {total_skills} total skill sources in deck")
|
||||||
|
|
||||||
|
def add_skill_row(self, skill_name, card_name, rarity, card_type, image_path, source, details, is_owned, card_id):
|
||||||
|
"""Add a single skill row to the tree"""
|
||||||
|
# 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
|
||||||
|
|
||||||
|
owned_mark = "★" if is_owned else ""
|
||||||
|
type_display = f"{get_type_icon(card_type)} {card_type}"
|
||||||
|
|
||||||
|
values = (
|
||||||
|
owned_mark,
|
||||||
|
skill_name,
|
||||||
|
card_name,
|
||||||
|
rarity,
|
||||||
|
source,
|
||||||
|
details
|
||||||
|
)
|
||||||
|
|
||||||
|
if img:
|
||||||
|
self.tree.insert('', tk.END, text='', image=img, values=values)
|
||||||
|
else:
|
||||||
|
self.tree.insert('', tk.END, text='?', values=values)
|
||||||
|
|
||||||
|
def set_card(self, card_id):
|
||||||
|
"""Show skills for a single card selection"""
|
||||||
|
card = get_card_by_id(card_id)
|
||||||
|
if not card: return
|
||||||
|
|
||||||
|
card_id, name, rarity, card_type, max_level, url, image_path, is_owned, owned_level = card
|
||||||
|
|
||||||
|
self.current_mode = "Single"
|
||||||
|
self.mode_label.config(text=f"Card: {name}", fg=ACCENT_SECONDARY)
|
||||||
|
|
||||||
|
# Clear tree
|
||||||
|
for item in self.tree.get_children():
|
||||||
|
self.tree.delete(item)
|
||||||
|
|
||||||
|
total_skills = 0
|
||||||
|
|
||||||
|
# 1. Hints
|
||||||
|
hints = get_hints(card_id)
|
||||||
|
for h_name, h_desc in hints:
|
||||||
|
self.add_skill_row(h_name, name, rarity, card_type, image_path, "Training Hint", h_desc, bool(is_owned), card_id)
|
||||||
|
total_skills += 1
|
||||||
|
|
||||||
|
# 2. Event Skills
|
||||||
|
events = get_all_event_skills(card_id)
|
||||||
|
for ev_name, skills in events.items():
|
||||||
|
summary = ", ".join(skills)
|
||||||
|
for s_name in skills:
|
||||||
|
self.add_skill_row(s_name, name, rarity, card_type, image_path, "Event", f"{ev_name} ({summary})", bool(is_owned), card_id)
|
||||||
|
total_skills += 1
|
||||||
|
|
||||||
|
self.stats_label.config(text=f"Showing {total_skills} skill sources for {name}")
|
||||||
@@ -194,78 +194,5 @@ class SkillSearchFrame(ttk.Frame):
|
|||||||
self.stats_label.config(text=f"Found {len(cards)} cards")
|
self.stats_label.config(text=f"Found {len(cards)} cards")
|
||||||
|
|
||||||
def set_card(self, card_id):
|
def set_card(self, card_id):
|
||||||
"""
|
"""No longer responsive to card selection in this tab"""
|
||||||
Show all skills (Hints and Events) for a specific card.
|
pass
|
||||||
Called by main window when a card is selected in the list.
|
|
||||||
"""
|
|
||||||
card = get_card_by_id(card_id)
|
|
||||||
if not card: return
|
|
||||||
|
|
||||||
card_name = card[1]
|
|
||||||
self.result_header.config(text=f"Skills for: {card_name}")
|
|
||||||
|
|
||||||
# Clear tree
|
|
||||||
for item in self.tree.get_children():
|
|
||||||
self.tree.delete(item)
|
|
||||||
|
|
||||||
all_skills = []
|
|
||||||
|
|
||||||
# 1. Get Hints
|
|
||||||
hints = get_hints(card_id)
|
|
||||||
for h_name, h_desc in hints:
|
|
||||||
all_skills.append({
|
|
||||||
'card_id': card_id,
|
|
||||||
'name': card_name,
|
|
||||||
'rarity': card[2],
|
|
||||||
'type': card[3],
|
|
||||||
'image_path': card[6],
|
|
||||||
'source': 'Training Hint',
|
|
||||||
'details': f"{h_name}: {h_desc}",
|
|
||||||
'is_owned': bool(card[7])
|
|
||||||
})
|
|
||||||
|
|
||||||
# 2. Get Event Skills
|
|
||||||
event_dict = get_all_event_skills(card_id)
|
|
||||||
for ev_name, skills in event_dict.items():
|
|
||||||
all_skills.append({
|
|
||||||
'card_id': card_id,
|
|
||||||
'name': card_name,
|
|
||||||
'rarity': card[2],
|
|
||||||
'type': card[3],
|
|
||||||
'image_path': card[6],
|
|
||||||
'source': 'Event',
|
|
||||||
'details': f"{ev_name} ({', '.join(skills)})",
|
|
||||||
'is_owned': bool(card[7])
|
|
||||||
})
|
|
||||||
|
|
||||||
# Display them
|
|
||||||
for skill in all_skills:
|
|
||||||
img = self.icon_cache.get(skill['card_id'])
|
|
||||||
if not img:
|
|
||||||
resolved_path = resolve_image_path(skill['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[skill['card_id']] = img
|
|
||||||
except: pass
|
|
||||||
|
|
||||||
type_display = f"{get_type_icon(skill['type'])} {skill['type']}"
|
|
||||||
owned_mark = "★" if skill.get('is_owned') else ""
|
|
||||||
|
|
||||||
values = (
|
|
||||||
owned_mark,
|
|
||||||
skill['name'],
|
|
||||||
skill['rarity'],
|
|
||||||
type_display,
|
|
||||||
skill['source'],
|
|
||||||
skill['details']
|
|
||||||
)
|
|
||||||
|
|
||||||
if img:
|
|
||||||
self.tree.insert('', tk.END, text='', image=img, values=values)
|
|
||||||
else:
|
|
||||||
self.tree.insert('', tk.END, text='?', values=values)
|
|
||||||
|
|
||||||
self.stats_label.config(text=f"Showing {len(all_skills)} skill sources for {card_name}")
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from db.db_queries import get_database_stats, get_owned_count
|
|||||||
from gui.card_view import CardListFrame
|
from gui.card_view import CardListFrame
|
||||||
from gui.effects_view import EffectsFrame
|
from gui.effects_view import EffectsFrame
|
||||||
from gui.hints_skills_view import SkillSearchFrame
|
from gui.hints_skills_view import SkillSearchFrame
|
||||||
|
from gui.deck_skills_view import DeckSkillsFrame
|
||||||
from gui.deck_builder import DeckBuilderFrame
|
from gui.deck_builder import DeckBuilderFrame
|
||||||
from gui.update_dialog import show_update_dialog
|
from gui.update_dialog import show_update_dialog
|
||||||
from gui.theme import (
|
from gui.theme import (
|
||||||
@@ -161,7 +162,11 @@ class MainWindow:
|
|||||||
|
|
||||||
# Skill Search Tab
|
# Skill Search Tab
|
||||||
self.hints_frame = SkillSearchFrame(self.notebook)
|
self.hints_frame = SkillSearchFrame(self.notebook)
|
||||||
self.notebook.add(self.hints_frame, text=" 🔍 Skill Search (Beta) ")
|
self.notebook.add(self.hints_frame, text=" 🔍 Skill Search ")
|
||||||
|
|
||||||
|
# Deck Skills Tab
|
||||||
|
self.deck_skills_frame = DeckSkillsFrame(self.notebook)
|
||||||
|
self.notebook.add(self.deck_skills_frame, text=" 📜 Deck Skills ")
|
||||||
|
|
||||||
def create_status_bar(self, parent):
|
def create_status_bar(self, parent):
|
||||||
"""Create status bar at bottom"""
|
"""Create status bar at bottom"""
|
||||||
@@ -201,8 +206,8 @@ class MainWindow:
|
|||||||
# Update other tabs with selected card
|
# Update other tabs with selected card
|
||||||
if hasattr(self, 'effects_frame'):
|
if hasattr(self, 'effects_frame'):
|
||||||
self.effects_frame.set_card(card_id)
|
self.effects_frame.set_card(card_id)
|
||||||
if hasattr(self, 'hints_frame'):
|
if hasattr(self, 'deck_skills_frame'):
|
||||||
self.hints_frame.set_card(card_id)
|
self.deck_skills_frame.set_card(card_id)
|
||||||
|
|
||||||
self.status_label.config(text=f"📌 Selected: {card_name}")
|
self.status_label.config(text=f"📌 Selected: {card_name}")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user