new features, play menu, deck builder, deck selection

This commit is contained in:
2026-01-28 20:22:09 -05:00
parent f4c7bab6b0
commit bf9aa3fa23
80 changed files with 4501 additions and 58 deletions

470
scripts/ui/DeckBuilder.gd Normal file
View File

@@ -0,0 +1,470 @@
class_name DeckBuilder
extends CanvasLayer
## DeckBuilder - Main deck builder screen with three-panel layout
signal back_pressed
signal deck_selected(deck: Deck)
const WINDOW_SIZE = Vector2i(1600, 900)
var current_deck: Deck = null
var current_deck_filename: String = ""
# UI Components
var detail_viewer: CardDetailViewer
var filter_bar: CardFilterBar
var card_grid: CardGrid
var deck_panel: DeckListPanel
# Header elements
var back_button: Button
var deck_name_field: LineEdit
var card_count_label: Label
var save_button: Button
var load_button: Button
var new_button: Button
var play_button: Button
# Dialogs
var save_dialog: Control
var load_dialog: Control
func _ready() -> void:
layer = 10
_create_ui()
_connect_signals()
_new_deck()
_load_all_cards()
func _create_ui() -> void:
# Root control
var root = Control.new()
root.set_anchors_preset(Control.PRESET_FULL_RECT)
root.mouse_filter = Control.MOUSE_FILTER_STOP
add_child(root)
# Background
var bg = ColorRect.new()
bg.set_anchors_preset(Control.PRESET_FULL_RECT)
bg.color = Color(0.05, 0.05, 0.08, 1.0)
root.add_child(bg)
# Main layout
var main_vbox = VBoxContainer.new()
main_vbox.set_anchors_preset(Control.PRESET_FULL_RECT)
main_vbox.add_theme_constant_override("separation", 0)
root.add_child(main_vbox)
# Header bar
_create_header(main_vbox)
# Content area (3 panels)
var content_hbox = HBoxContainer.new()
content_hbox.size_flags_vertical = Control.SIZE_EXPAND_FILL
content_hbox.add_theme_constant_override("separation", 0)
main_vbox.add_child(content_hbox)
# Left panel - Card Detail Viewer
detail_viewer = CardDetailViewer.new()
content_hbox.add_child(detail_viewer)
# Center panel container
var center_panel = VBoxContainer.new()
center_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
center_panel.add_theme_constant_override("separation", 0)
content_hbox.add_child(center_panel)
# Filter bar
filter_bar = CardFilterBar.new()
center_panel.add_child(filter_bar)
# Results count
var results_bar = HBoxContainer.new()
results_bar.custom_minimum_size = Vector2(0, 30)
var results_style = StyleBoxFlat.new()
results_style.bg_color = Color(0.08, 0.08, 0.1)
results_style.content_margin_left = 12
results_style.content_margin_top = 4
var results_panel = PanelContainer.new()
results_panel.add_theme_stylebox_override("panel", results_style)
results_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
center_panel.add_child(results_panel)
card_count_label = Label.new()
card_count_label.text = "Loading cards..."
card_count_label.add_theme_font_size_override("font_size", 12)
card_count_label.add_theme_color_override("font_color", Color(0.6, 0.6, 0.6))
results_panel.add_child(card_count_label)
# Card grid
card_grid = CardGrid.new()
card_grid.size_flags_vertical = Control.SIZE_EXPAND_FILL
center_panel.add_child(card_grid)
# Right panel - Deck List
deck_panel = DeckListPanel.new()
content_hbox.add_child(deck_panel)
# Create dialogs (hidden)
_create_save_dialog(root)
_create_load_dialog(root)
func _create_header(parent: Control) -> void:
var header = PanelContainer.new()
header.custom_minimum_size = Vector2(0, 50)
var header_style = StyleBoxFlat.new()
header_style.bg_color = Color(0.1, 0.1, 0.14)
header_style.border_color = Color(0.3, 0.25, 0.15)
header_style.border_width_bottom = 2
header_style.content_margin_left = 15
header_style.content_margin_right = 15
header.add_theme_stylebox_override("panel", header_style)
parent.add_child(header)
var header_hbox = HBoxContainer.new()
header_hbox.add_theme_constant_override("separation", 15)
header_hbox.alignment = BoxContainer.ALIGNMENT_BEGIN
header.add_child(header_hbox)
# Back button
back_button = _create_header_button("< Back")
header_hbox.add_child(back_button)
# Deck name
var name_label = Label.new()
name_label.text = "Deck:"
name_label.add_theme_font_size_override("font_size", 14)
header_hbox.add_child(name_label)
deck_name_field = LineEdit.new()
deck_name_field.text = "New Deck"
deck_name_field.custom_minimum_size = Vector2(200, 0)
deck_name_field.text_changed.connect(_on_deck_name_changed)
header_hbox.add_child(deck_name_field)
# Spacer
var spacer = Control.new()
spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL
header_hbox.add_child(spacer)
# Action buttons
new_button = _create_header_button("New")
save_button = _create_header_button("Save")
load_button = _create_header_button("Load")
play_button = _create_header_button("Play with Deck")
play_button.add_theme_color_override("font_color", Color(0.4, 0.8, 0.4))
header_hbox.add_child(new_button)
header_hbox.add_child(save_button)
header_hbox.add_child(load_button)
header_hbox.add_child(play_button)
func _create_header_button(text: String) -> Button:
var button = Button.new()
button.text = text
button.custom_minimum_size = Vector2(80, 32)
var style_normal = StyleBoxFlat.new()
style_normal.bg_color = Color(0.2, 0.2, 0.25)
style_normal.border_color = Color(0.4, 0.4, 0.5)
style_normal.set_border_width_all(1)
style_normal.set_corner_radius_all(4)
button.add_theme_stylebox_override("normal", style_normal)
var style_hover = StyleBoxFlat.new()
style_hover.bg_color = Color(0.3, 0.3, 0.38)
style_hover.border_color = Color(0.6, 0.5, 0.3)
style_hover.set_border_width_all(1)
style_hover.set_corner_radius_all(4)
button.add_theme_stylebox_override("hover", style_hover)
button.add_theme_font_size_override("font_size", 13)
return button
func _create_save_dialog(parent: Control) -> void:
save_dialog = _create_dialog_base("Save Deck")
parent.add_child(save_dialog)
var content = save_dialog.get_node("Panel/VBox")
var name_hbox = HBoxContainer.new()
name_hbox.add_theme_constant_override("separation", 8)
content.add_child(name_hbox)
var label = Label.new()
label.text = "Filename:"
name_hbox.add_child(label)
var save_name_field = LineEdit.new()
save_name_field.name = "SaveNameField"
save_name_field.custom_minimum_size = Vector2(200, 0)
name_hbox.add_child(save_name_field)
var btn_hbox = HBoxContainer.new()
btn_hbox.add_theme_constant_override("separation", 10)
btn_hbox.alignment = BoxContainer.ALIGNMENT_CENTER
content.add_child(btn_hbox)
var save_btn = _create_header_button("Save")
save_btn.pressed.connect(_on_save_confirmed)
btn_hbox.add_child(save_btn)
var cancel_btn = _create_header_button("Cancel")
cancel_btn.pressed.connect(func(): save_dialog.visible = false)
btn_hbox.add_child(cancel_btn)
func _create_load_dialog(parent: Control) -> void:
load_dialog = _create_dialog_base("Load Deck")
parent.add_child(load_dialog)
var content = load_dialog.get_node("Panel/VBox")
var deck_list = ItemList.new()
deck_list.name = "DeckList"
deck_list.custom_minimum_size = Vector2(300, 200)
deck_list.item_activated.connect(_on_deck_item_activated)
content.add_child(deck_list)
var btn_hbox = HBoxContainer.new()
btn_hbox.add_theme_constant_override("separation", 10)
btn_hbox.alignment = BoxContainer.ALIGNMENT_CENTER
content.add_child(btn_hbox)
var load_btn = _create_header_button("Load")
load_btn.pressed.connect(_on_load_confirmed)
btn_hbox.add_child(load_btn)
var delete_btn = _create_header_button("Delete")
delete_btn.add_theme_color_override("font_color", Color(1.0, 0.5, 0.5))
delete_btn.pressed.connect(_on_delete_deck)
btn_hbox.add_child(delete_btn)
var cancel_btn = _create_header_button("Cancel")
cancel_btn.pressed.connect(func(): load_dialog.visible = false)
btn_hbox.add_child(cancel_btn)
func _create_dialog_base(title: String) -> Control:
var overlay = Control.new()
overlay.set_anchors_preset(Control.PRESET_FULL_RECT)
overlay.visible = false
var bg = ColorRect.new()
bg.set_anchors_preset(Control.PRESET_FULL_RECT)
bg.color = Color(0, 0, 0, 0.6)
bg.gui_input.connect(func(event):
if event is InputEventMouseButton and event.pressed:
overlay.visible = false
)
overlay.add_child(bg)
var panel = PanelContainer.new()
panel.name = "Panel"
panel.set_anchors_preset(Control.PRESET_CENTER)
panel.custom_minimum_size = Vector2(350, 250)
var style = StyleBoxFlat.new()
style.bg_color = Color(0.1, 0.1, 0.14, 0.98)
style.border_color = Color(0.5, 0.4, 0.2)
style.set_border_width_all(2)
style.set_corner_radius_all(8)
style.content_margin_left = 20
style.content_margin_right = 20
style.content_margin_top = 15
style.content_margin_bottom = 15
panel.add_theme_stylebox_override("panel", style)
overlay.add_child(panel)
var vbox = VBoxContainer.new()
vbox.name = "VBox"
vbox.add_theme_constant_override("separation", 15)
panel.add_child(vbox)
var title_label = Label.new()
title_label.text = title
title_label.add_theme_font_size_override("font_size", 18)
title_label.add_theme_color_override("font_color", Color(1.0, 0.95, 0.8))
title_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
vbox.add_child(title_label)
return overlay
func _connect_signals() -> void:
back_button.pressed.connect(_on_back_pressed)
new_button.pressed.connect(_new_deck)
save_button.pressed.connect(_show_save_dialog)
load_button.pressed.connect(_show_load_dialog)
play_button.pressed.connect(_on_play_pressed)
filter_bar.filters_changed.connect(_on_filters_changed)
card_grid.card_selected.connect(_on_card_selected)
card_grid.card_double_clicked.connect(_on_card_double_clicked)
detail_viewer.add_to_deck_requested.connect(_on_add_to_deck)
deck_panel.card_clicked.connect(_on_deck_card_clicked)
deck_panel.card_removed.connect(_on_deck_card_removed)
deck_panel.deck_cleared.connect(_on_deck_cleared)
func _load_all_cards() -> void:
var all_cards = CardDatabase.get_all_cards()
card_grid.set_cards(all_cards)
card_count_label.text = "Showing %d of %d cards" % [all_cards.size(), all_cards.size()]
func _new_deck() -> void:
current_deck = Deck.new()
current_deck.name = DeckManager.generate_unique_name()
current_deck_filename = ""
deck_name_field.text = current_deck.name
deck_panel.set_deck(current_deck)
detail_viewer.clear()
func _on_back_pressed() -> void:
back_pressed.emit()
func _on_play_pressed() -> void:
if current_deck and current_deck.is_valid():
deck_selected.emit(current_deck)
else:
# Show validation errors
var errors = current_deck.validate() if current_deck else ["No deck loaded"]
push_warning("Cannot play - deck invalid: " + ", ".join(errors))
func _on_deck_name_changed(new_name: String) -> void:
if current_deck:
current_deck.name = new_name
func _on_filters_changed(filters: Dictionary) -> void:
var results = CardDatabase.filter_cards(filters)
card_grid.set_cards(results)
card_count_label.text = "Showing %d of %d cards" % [results.size(), CardDatabase.get_card_count()]
func _on_card_selected(card: CardDatabase.CardData) -> void:
var deck_count = current_deck.get_card_count(card.id) if current_deck else 0
detail_viewer.show_card(card, deck_count)
func _on_card_double_clicked(card: CardDatabase.CardData) -> void:
if current_deck:
var error = current_deck.add_card(card.id)
if error.is_empty():
detail_viewer.update_deck_count(current_deck.get_card_count(card.id))
func _on_add_to_deck(card: CardDatabase.CardData, quantity: int) -> void:
if not current_deck:
return
for i in range(quantity):
var error = current_deck.add_card(card.id)
if not error.is_empty():
break
detail_viewer.update_deck_count(current_deck.get_card_count(card.id))
func _on_deck_card_clicked(card_id: String) -> void:
var card_data = CardDatabase.get_card(card_id)
if card_data:
var deck_count = current_deck.get_card_count(card_id) if current_deck else 0
detail_viewer.show_card(card_data, deck_count)
func _on_deck_card_removed(card_id: String) -> void:
if current_deck:
current_deck.remove_card(card_id)
# Update detail viewer if showing this card
var card_data = CardDatabase.get_card(card_id)
if card_data:
detail_viewer.update_deck_count(current_deck.get_card_count(card_id))
func _on_deck_cleared() -> void:
if current_deck:
current_deck.clear()
detail_viewer.clear()
func _show_save_dialog() -> void:
var save_name_field = save_dialog.get_node("Panel/VBox/HBoxContainer/SaveNameField") as LineEdit
save_name_field.text = current_deck.name if current_deck else "New Deck"
save_dialog.visible = true
func _on_save_confirmed() -> void:
var save_name_field = save_dialog.get_node("Panel/VBox/HBoxContainer/SaveNameField") as LineEdit
var filename = save_name_field.text.strip_edges()
if filename.is_empty():
return
if current_deck:
current_deck.name = filename
deck_name_field.text = filename
if DeckManager.save_deck(current_deck, filename):
current_deck_filename = filename
print("Deck saved: ", filename)
else:
push_error("Failed to save deck")
save_dialog.visible = false
func _show_load_dialog() -> void:
var deck_list = load_dialog.get_node("Panel/VBox/DeckList") as ItemList
deck_list.clear()
for deck_name in DeckManager.list_decks():
deck_list.add_item(deck_name)
load_dialog.visible = true
func _on_deck_item_activated(index: int) -> void:
_on_load_confirmed()
func _on_load_confirmed() -> void:
var deck_list = load_dialog.get_node("Panel/VBox/DeckList") as ItemList
var selected = deck_list.get_selected_items()
if selected.is_empty():
return
var filename = deck_list.get_item_text(selected[0])
var loaded_deck = DeckManager.load_deck(filename)
if loaded_deck:
current_deck = loaded_deck
current_deck_filename = filename
deck_name_field.text = current_deck.name
deck_panel.set_deck(current_deck)
detail_viewer.clear()
print("Deck loaded: ", filename)
else:
push_error("Failed to load deck")
load_dialog.visible = false
func _on_delete_deck() -> void:
var deck_list = load_dialog.get_node("Panel/VBox/DeckList") as ItemList
var selected = deck_list.get_selected_items()
if selected.is_empty():
return
var filename = deck_list.get_item_text(selected[0])
if DeckManager.delete_deck(filename):
deck_list.remove_item(selected[0])
print("Deck deleted: ", filename)