353 lines
11 KiB
GDScript
353 lines
11 KiB
GDScript
class_name DeckListPanel
|
|
extends Control
|
|
|
|
## DeckListPanel - Right panel showing deck contents with thumbnails and stats
|
|
|
|
signal card_clicked(card_id: String)
|
|
signal card_removed(card_id: String)
|
|
signal deck_cleared
|
|
|
|
const PANEL_WIDTH: float = 400.0
|
|
const CARD_WIDTH: float = 90.0
|
|
const CARD_HEIGHT: float = 126.0
|
|
const CARD_GAP: float = 4.0
|
|
const COLUMNS: int = 4
|
|
|
|
var current_deck: Deck = null
|
|
var card_cells: Dictionary = {} # card_id -> Control
|
|
|
|
# UI elements
|
|
var stats_panel: Control
|
|
var element_labels: Dictionary = {}
|
|
var type_labels: Dictionary = {}
|
|
var total_label: Label
|
|
var deck_scroll: ScrollContainer
|
|
var deck_grid: Control
|
|
var validation_label: Label
|
|
var clear_button: Button
|
|
|
|
|
|
func _ready() -> void:
|
|
custom_minimum_size = Vector2(PANEL_WIDTH, 0)
|
|
_create_ui()
|
|
|
|
|
|
func _create_ui() -> void:
|
|
var panel = PanelContainer.new()
|
|
panel.set_anchors_preset(Control.PRESET_FULL_RECT)
|
|
panel.add_theme_stylebox_override("panel", _create_panel_style())
|
|
add_child(panel)
|
|
|
|
var main_vbox = VBoxContainer.new()
|
|
main_vbox.add_theme_constant_override("separation", 10)
|
|
panel.add_child(main_vbox)
|
|
|
|
# Header
|
|
var header = Label.new()
|
|
header.text = "Deck"
|
|
header.add_theme_font_size_override("font_size", 18)
|
|
header.add_theme_color_override("font_color", Color(1.0, 0.95, 0.8))
|
|
main_vbox.add_child(header)
|
|
|
|
# Stats panel
|
|
_create_stats_panel(main_vbox)
|
|
|
|
# Deck grid
|
|
deck_scroll = ScrollContainer.new()
|
|
deck_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
|
deck_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
|
main_vbox.add_child(deck_scroll)
|
|
|
|
deck_grid = Control.new()
|
|
deck_grid.custom_minimum_size = Vector2(COLUMNS * (CARD_WIDTH + CARD_GAP) - CARD_GAP, 0)
|
|
deck_scroll.add_child(deck_grid)
|
|
|
|
# Validation label
|
|
validation_label = Label.new()
|
|
validation_label.text = ""
|
|
validation_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
validation_label.add_theme_font_size_override("font_size", 12)
|
|
validation_label.add_theme_color_override("font_color", Color(1.0, 0.4, 0.4))
|
|
main_vbox.add_child(validation_label)
|
|
|
|
# Clear deck button
|
|
clear_button = _create_styled_button("Clear Deck")
|
|
clear_button.pressed.connect(_on_clear_pressed)
|
|
main_vbox.add_child(clear_button)
|
|
|
|
|
|
func _create_stats_panel(parent: Control) -> void:
|
|
stats_panel = PanelContainer.new()
|
|
var stats_style = StyleBoxFlat.new()
|
|
stats_style.bg_color = Color(0.12, 0.12, 0.16, 0.8)
|
|
stats_style.set_corner_radius_all(4)
|
|
stats_style.content_margin_left = 8
|
|
stats_style.content_margin_right = 8
|
|
stats_style.content_margin_top = 6
|
|
stats_style.content_margin_bottom = 6
|
|
stats_panel.add_theme_stylebox_override("panel", stats_style)
|
|
parent.add_child(stats_panel)
|
|
|
|
var stats_vbox = VBoxContainer.new()
|
|
stats_vbox.add_theme_constant_override("separation", 4)
|
|
stats_panel.add_child(stats_vbox)
|
|
|
|
# Total cards
|
|
total_label = Label.new()
|
|
total_label.text = "Cards: 0/50"
|
|
total_label.add_theme_font_size_override("font_size", 14)
|
|
total_label.add_theme_color_override("font_color", Color(0.9, 0.85, 0.7))
|
|
stats_vbox.add_child(total_label)
|
|
|
|
# Element breakdown
|
|
var elem_hbox = HBoxContainer.new()
|
|
elem_hbox.add_theme_constant_override("separation", 8)
|
|
stats_vbox.add_child(elem_hbox)
|
|
|
|
for element in Enums.Element.values():
|
|
var elem_container = HBoxContainer.new()
|
|
elem_container.add_theme_constant_override("separation", 2)
|
|
|
|
var elem_icon = ColorRect.new()
|
|
elem_icon.custom_minimum_size = Vector2(12, 12)
|
|
elem_icon.color = Enums.element_to_color(element)
|
|
elem_container.add_child(elem_icon)
|
|
|
|
var elem_label = Label.new()
|
|
elem_label.text = "0"
|
|
elem_label.add_theme_font_size_override("font_size", 10)
|
|
elem_container.add_child(elem_label)
|
|
element_labels[element] = elem_label
|
|
|
|
elem_hbox.add_child(elem_container)
|
|
|
|
# Type breakdown
|
|
var type_hbox = HBoxContainer.new()
|
|
type_hbox.add_theme_constant_override("separation", 12)
|
|
stats_vbox.add_child(type_hbox)
|
|
|
|
for card_type in [Enums.CardType.FORWARD, Enums.CardType.BACKUP, Enums.CardType.SUMMON]:
|
|
var type_label = Label.new()
|
|
type_label.text = "%s: 0" % Enums.card_type_to_string(card_type).substr(0, 3)
|
|
type_label.add_theme_font_size_override("font_size", 10)
|
|
type_label.add_theme_color_override("font_color", Color(0.7, 0.7, 0.7))
|
|
type_hbox.add_child(type_label)
|
|
type_labels[card_type] = type_label
|
|
|
|
|
|
func _create_panel_style() -> StyleBoxFlat:
|
|
var style = StyleBoxFlat.new()
|
|
style.bg_color = Color(0.08, 0.08, 0.12, 0.95)
|
|
style.border_color = Color(0.5, 0.4, 0.2)
|
|
style.set_border_width_all(2)
|
|
style.set_corner_radius_all(6)
|
|
style.content_margin_left = 12
|
|
style.content_margin_right = 12
|
|
style.content_margin_top = 12
|
|
style.content_margin_bottom = 12
|
|
return style
|
|
|
|
|
|
func _create_styled_button(text: String) -> Button:
|
|
var button = Button.new()
|
|
button.text = text
|
|
button.custom_minimum_size = Vector2(0, 36)
|
|
|
|
var style_normal = StyleBoxFlat.new()
|
|
style_normal.bg_color = Color(0.4, 0.2, 0.2)
|
|
style_normal.border_color = Color(0.6, 0.3, 0.3)
|
|
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.5, 0.25, 0.25)
|
|
style_hover.border_color = Color(0.8, 0.4, 0.4)
|
|
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", 14)
|
|
return button
|
|
|
|
|
|
## Set the deck to display
|
|
func set_deck(deck: Deck) -> void:
|
|
if current_deck:
|
|
current_deck.deck_changed.disconnect(_on_deck_changed)
|
|
|
|
current_deck = deck
|
|
if current_deck:
|
|
current_deck.deck_changed.connect(_on_deck_changed)
|
|
|
|
_refresh_display()
|
|
|
|
|
|
func _on_deck_changed() -> void:
|
|
_refresh_display()
|
|
|
|
|
|
func _refresh_display() -> void:
|
|
# Clear existing cells
|
|
for cell in card_cells.values():
|
|
cell.queue_free()
|
|
card_cells.clear()
|
|
|
|
if not current_deck:
|
|
total_label.text = "Cards: 0/50"
|
|
validation_label.text = ""
|
|
_update_stats({})
|
|
return
|
|
|
|
# Get stats
|
|
var stats = current_deck.get_stats()
|
|
_update_stats(stats)
|
|
|
|
# Update total
|
|
total_label.text = "Cards: %d/50" % stats.total
|
|
|
|
# Validate
|
|
var errors = current_deck.validate()
|
|
validation_label.text = "\n".join(errors)
|
|
|
|
# Create card cells
|
|
var card_ids = current_deck.get_card_ids()
|
|
card_ids.sort() # Sort alphabetically
|
|
|
|
var row = 0
|
|
var col = 0
|
|
for card_id in card_ids:
|
|
var count = current_deck.get_card_count(card_id)
|
|
var cell = _create_deck_cell(card_id, count)
|
|
cell.position = Vector2(col * (CARD_WIDTH + CARD_GAP), row * (CARD_HEIGHT + CARD_GAP))
|
|
deck_grid.add_child(cell)
|
|
card_cells[card_id] = cell
|
|
|
|
col += 1
|
|
if col >= COLUMNS:
|
|
col = 0
|
|
row += 1
|
|
|
|
# Update grid size
|
|
var total_rows = ceili(float(card_ids.size()) / COLUMNS)
|
|
deck_grid.custom_minimum_size.y = total_rows * (CARD_HEIGHT + CARD_GAP)
|
|
|
|
|
|
func _update_stats(stats: Dictionary) -> void:
|
|
# Update element counts
|
|
for element in element_labels:
|
|
var elem_name = Enums.element_to_string(element)
|
|
var count = stats.get("elements", {}).get(elem_name, 0)
|
|
element_labels[element].text = str(count)
|
|
|
|
# Update type counts
|
|
for card_type in type_labels:
|
|
var type_name = Enums.card_type_to_string(card_type)
|
|
var count = stats.get("types", {}).get(type_name, 0)
|
|
type_labels[card_type].text = "%s: %d" % [type_name.substr(0, 3), count]
|
|
|
|
|
|
func _create_deck_cell(card_id: String, count: int) -> Control:
|
|
var cell = Panel.new()
|
|
cell.custom_minimum_size = Vector2(CARD_WIDTH, CARD_HEIGHT)
|
|
cell.size = Vector2(CARD_WIDTH, CARD_HEIGHT)
|
|
cell.mouse_filter = Control.MOUSE_FILTER_STOP
|
|
cell.set_meta("card_id", card_id)
|
|
|
|
var style = StyleBoxFlat.new()
|
|
style.bg_color = Color(0.15, 0.15, 0.2, 0.8)
|
|
style.border_color = Color(0.3, 0.3, 0.35)
|
|
style.set_border_width_all(1)
|
|
style.set_corner_radius_all(2)
|
|
cell.add_theme_stylebox_override("panel", style)
|
|
|
|
# Card image
|
|
var card_data = CardDatabase.get_card(card_id)
|
|
if card_data:
|
|
var texture = CardDatabase.get_card_texture(card_data)
|
|
if texture:
|
|
var tex_rect = TextureRect.new()
|
|
tex_rect.set_anchors_preset(Control.PRESET_FULL_RECT)
|
|
tex_rect.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
|
tex_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
|
tex_rect.texture = texture
|
|
cell.add_child(tex_rect)
|
|
else:
|
|
var fallback = ColorRect.new()
|
|
fallback.set_anchors_preset(Control.PRESET_FULL_RECT)
|
|
fallback.color = Enums.element_to_color(card_data.get_primary_element()).darkened(0.3)
|
|
cell.add_child(fallback)
|
|
|
|
var name_label = Label.new()
|
|
name_label.text = card_data.name
|
|
name_label.set_anchors_preset(Control.PRESET_CENTER)
|
|
name_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
name_label.autowrap_mode = TextServer.AUTOWRAP_WORD
|
|
name_label.add_theme_font_size_override("font_size", 8)
|
|
cell.add_child(name_label)
|
|
|
|
# Quantity badge
|
|
if count > 1:
|
|
var badge = Panel.new()
|
|
badge.size = Vector2(22, 22)
|
|
badge.position = Vector2(CARD_WIDTH - 26, 4)
|
|
var badge_style = StyleBoxFlat.new()
|
|
badge_style.bg_color = Color(0.8, 0.6, 0.2, 0.95)
|
|
badge_style.set_corner_radius_all(11)
|
|
badge.add_theme_stylebox_override("panel", badge_style)
|
|
cell.add_child(badge)
|
|
|
|
var badge_label = Label.new()
|
|
badge_label.text = "x%d" % count
|
|
badge_label.set_anchors_preset(Control.PRESET_CENTER)
|
|
badge_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
badge_label.add_theme_font_size_override("font_size", 10)
|
|
badge.add_child(badge_label)
|
|
|
|
# Remove button (X in top-left)
|
|
var remove_btn = Button.new()
|
|
remove_btn.text = "X"
|
|
remove_btn.size = Vector2(20, 20)
|
|
remove_btn.position = Vector2(4, 4)
|
|
remove_btn.flat = true
|
|
remove_btn.add_theme_font_size_override("font_size", 10)
|
|
remove_btn.add_theme_color_override("font_color", Color(1.0, 0.5, 0.5))
|
|
remove_btn.add_theme_color_override("font_hover_color", Color(1.0, 0.3, 0.3))
|
|
remove_btn.pressed.connect(_on_remove_card.bind(card_id))
|
|
remove_btn.visible = false
|
|
remove_btn.name = "RemoveBtn"
|
|
cell.add_child(remove_btn)
|
|
|
|
# Hover effects
|
|
cell.mouse_entered.connect(func():
|
|
remove_btn.visible = true
|
|
style.border_color = Color(0.6, 0.5, 0.3)
|
|
cell.add_theme_stylebox_override("panel", style)
|
|
)
|
|
cell.mouse_exited.connect(func():
|
|
remove_btn.visible = false
|
|
style.border_color = Color(0.3, 0.3, 0.35)
|
|
cell.add_theme_stylebox_override("panel", style)
|
|
)
|
|
|
|
# Click to select
|
|
cell.gui_input.connect(func(event):
|
|
if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
|
|
card_clicked.emit(card_id)
|
|
)
|
|
|
|
return cell
|
|
|
|
|
|
func _on_remove_card(card_id: String) -> void:
|
|
card_removed.emit(card_id)
|
|
|
|
|
|
func _on_clear_pressed() -> void:
|
|
deck_cleared.emit()
|
|
|
|
|
|
## Refresh display for a specific card (when count changes)
|
|
func refresh_card(card_id: String) -> void:
|
|
_refresh_display()
|