new features, play menu, deck builder, deck selection
This commit is contained in:
241
scripts/ui/CardGrid.gd
Normal file
241
scripts/ui/CardGrid.gd
Normal file
@@ -0,0 +1,241 @@
|
||||
class_name CardGrid
|
||||
extends Control
|
||||
|
||||
## CardGrid - Virtualized scrolling grid for displaying cards in the deck builder
|
||||
|
||||
signal card_selected(card: CardDatabase.CardData)
|
||||
signal card_double_clicked(card: CardDatabase.CardData)
|
||||
|
||||
const CARD_WIDTH: float = 140.0
|
||||
const CARD_HEIGHT: float = 196.0
|
||||
const CARD_GAP: float = 8.0
|
||||
const COLUMNS: int = 5
|
||||
const VISIBLE_ROWS_BUFFER: int = 2
|
||||
|
||||
var filtered_cards: Array = [] # Array of CardData
|
||||
var card_cells: Array[Control] = []
|
||||
var scroll_container: ScrollContainer
|
||||
var grid_content: Control
|
||||
var visible_start_row: int = 0
|
||||
var total_rows: int = 0
|
||||
var last_click_time: float = 0.0
|
||||
var last_clicked_card: CardDatabase.CardData = null
|
||||
|
||||
# Loading indicator
|
||||
var loading_label: Label
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_create_ui()
|
||||
|
||||
|
||||
func _create_ui() -> void:
|
||||
# Main scroll container
|
||||
scroll_container = ScrollContainer.new()
|
||||
scroll_container.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
scroll_container.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||||
scroll_container.get_v_scroll_bar().value_changed.connect(_on_scroll_changed)
|
||||
add_child(scroll_container)
|
||||
|
||||
# Grid content container (sized to fit all cards)
|
||||
grid_content = Control.new()
|
||||
grid_content.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
scroll_container.add_child(grid_content)
|
||||
|
||||
# Loading label
|
||||
loading_label = Label.new()
|
||||
loading_label.text = "Loading cards..."
|
||||
loading_label.set_anchors_preset(Control.PRESET_CENTER)
|
||||
loading_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
loading_label.add_theme_color_override("font_color", Color(0.5, 0.5, 0.5))
|
||||
add_child(loading_label)
|
||||
|
||||
# Pre-create card cell pool
|
||||
_create_cell_pool()
|
||||
|
||||
|
||||
func _create_cell_pool() -> void:
|
||||
# Calculate max visible cells needed
|
||||
var viewport_height = get_viewport_rect().size.y if get_viewport() else 900.0
|
||||
var max_visible_rows = ceili(viewport_height / (CARD_HEIGHT + CARD_GAP)) + VISIBLE_ROWS_BUFFER * 2
|
||||
var pool_size = max_visible_rows * COLUMNS
|
||||
|
||||
for i in range(pool_size):
|
||||
var cell = _create_card_cell()
|
||||
cell.visible = false
|
||||
grid_content.add_child(cell)
|
||||
card_cells.append(cell)
|
||||
|
||||
|
||||
func _create_card_cell() -> 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
|
||||
|
||||
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(3)
|
||||
cell.add_theme_stylebox_override("panel", style)
|
||||
|
||||
# Card image
|
||||
var tex_rect = TextureRect.new()
|
||||
tex_rect.name = "TextureRect"
|
||||
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
|
||||
cell.add_child(tex_rect)
|
||||
|
||||
# Fallback color rect
|
||||
var fallback = ColorRect.new()
|
||||
fallback.name = "Fallback"
|
||||
fallback.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
fallback.visible = false
|
||||
cell.add_child(fallback)
|
||||
|
||||
# Card name label (shown on fallback)
|
||||
var name_label = Label.new()
|
||||
name_label.name = "NameLabel"
|
||||
name_label.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
name_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
name_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
name_label.autowrap_mode = TextServer.AUTOWRAP_WORD
|
||||
name_label.add_theme_font_size_override("font_size", 10)
|
||||
name_label.visible = false
|
||||
cell.add_child(name_label)
|
||||
|
||||
# Hover highlight
|
||||
var highlight = ColorRect.new()
|
||||
highlight.name = "Highlight"
|
||||
highlight.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
highlight.color = Color(1.0, 1.0, 1.0, 0.0)
|
||||
highlight.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
cell.add_child(highlight)
|
||||
|
||||
# Input handling
|
||||
cell.gui_input.connect(_on_cell_input.bind(cell))
|
||||
cell.mouse_entered.connect(_on_cell_hover.bind(cell, true))
|
||||
cell.mouse_exited.connect(_on_cell_hover.bind(cell, false))
|
||||
|
||||
return cell
|
||||
|
||||
|
||||
func _on_cell_input(event: InputEvent, cell: Control) -> void:
|
||||
if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
|
||||
var card = cell.get_meta("card", null)
|
||||
if card:
|
||||
var current_time = Time.get_ticks_msec() / 1000.0
|
||||
if card == last_clicked_card and current_time - last_click_time < 0.4:
|
||||
# Double click
|
||||
card_double_clicked.emit(card)
|
||||
last_clicked_card = null
|
||||
else:
|
||||
# Single click
|
||||
card_selected.emit(card)
|
||||
last_clicked_card = card
|
||||
last_click_time = current_time
|
||||
|
||||
|
||||
func _on_cell_hover(cell: Control, entered: bool) -> void:
|
||||
var highlight = cell.get_node("Highlight") as ColorRect
|
||||
if highlight:
|
||||
highlight.color.a = 0.15 if entered else 0.0
|
||||
|
||||
|
||||
func set_cards(cards: Array) -> void:
|
||||
filtered_cards = cards
|
||||
total_rows = ceili(float(cards.size()) / COLUMNS) if cards.size() > 0 else 0
|
||||
|
||||
# Update content size
|
||||
var content_width = COLUMNS * (CARD_WIDTH + CARD_GAP) - CARD_GAP
|
||||
var content_height = total_rows * (CARD_HEIGHT + CARD_GAP)
|
||||
grid_content.custom_minimum_size = Vector2(content_width, content_height)
|
||||
|
||||
loading_label.visible = cards.is_empty()
|
||||
|
||||
# Reset scroll and update visible cells
|
||||
scroll_container.scroll_vertical = 0
|
||||
_update_visible_cells()
|
||||
|
||||
|
||||
func _on_scroll_changed(_value: float) -> void:
|
||||
_update_visible_cells()
|
||||
|
||||
|
||||
func _update_visible_cells() -> void:
|
||||
if filtered_cards.is_empty():
|
||||
for cell in card_cells:
|
||||
cell.visible = false
|
||||
return
|
||||
|
||||
var scroll_y = scroll_container.scroll_vertical
|
||||
var viewport_height = scroll_container.size.y
|
||||
|
||||
# Calculate visible row range
|
||||
var first_visible_row = int(scroll_y / (CARD_HEIGHT + CARD_GAP))
|
||||
var last_visible_row = ceili((scroll_y + viewport_height) / (CARD_HEIGHT + CARD_GAP))
|
||||
|
||||
# Add buffer
|
||||
first_visible_row = maxi(0, first_visible_row - VISIBLE_ROWS_BUFFER)
|
||||
last_visible_row = mini(total_rows - 1, last_visible_row + VISIBLE_ROWS_BUFFER)
|
||||
|
||||
# Update cells
|
||||
var cell_index = 0
|
||||
for row in range(first_visible_row, last_visible_row + 1):
|
||||
for col in range(COLUMNS):
|
||||
var card_index = row * COLUMNS + col
|
||||
if card_index >= filtered_cards.size():
|
||||
break
|
||||
|
||||
if cell_index < card_cells.size():
|
||||
var cell = card_cells[cell_index]
|
||||
var card = filtered_cards[card_index]
|
||||
|
||||
# Position cell
|
||||
cell.position = Vector2(
|
||||
col * (CARD_WIDTH + CARD_GAP),
|
||||
row * (CARD_HEIGHT + CARD_GAP)
|
||||
)
|
||||
|
||||
# Update cell content
|
||||
_update_cell_content(cell, card)
|
||||
cell.visible = true
|
||||
cell_index += 1
|
||||
|
||||
# Hide unused cells
|
||||
for i in range(cell_index, card_cells.size()):
|
||||
card_cells[i].visible = false
|
||||
|
||||
|
||||
func _update_cell_content(cell: Control, card: CardDatabase.CardData) -> void:
|
||||
var current_id = cell.get_meta("card_id", "")
|
||||
if current_id == card.id:
|
||||
return # Already showing this card
|
||||
|
||||
cell.set_meta("card_id", card.id)
|
||||
cell.set_meta("card", card)
|
||||
|
||||
var tex_rect = cell.get_node("TextureRect") as TextureRect
|
||||
var fallback = cell.get_node("Fallback") as ColorRect
|
||||
var name_label = cell.get_node("NameLabel") as Label
|
||||
|
||||
# Load texture
|
||||
var texture = CardDatabase.get_card_texture(card)
|
||||
if texture:
|
||||
tex_rect.texture = texture
|
||||
tex_rect.visible = true
|
||||
fallback.visible = false
|
||||
name_label.visible = false
|
||||
else:
|
||||
tex_rect.visible = false
|
||||
fallback.visible = true
|
||||
fallback.color = Enums.element_to_color(card.get_primary_element()).darkened(0.3)
|
||||
name_label.visible = true
|
||||
name_label.text = card.name
|
||||
|
||||
|
||||
## Get currently displayed card count
|
||||
func get_card_count() -> int:
|
||||
return filtered_cards.size()
|
||||
Reference in New Issue
Block a user