class_name HandDisplay extends Control ## HandDisplay - Shows the current player's hand in a fan layout signal card_selected(card_instance: CardInstance) signal card_hovered(card_instance: CardInstance) signal card_unhovered # Hand card visuals var hand_cards: Array[Control] = [] var card_instances: Array[CardInstance] = [] # Layout settings const CARD_WIDTH: float = 100.0 const CARD_HEIGHT: float = 140.0 const CARD_OVERLAP: float = 70.0 const FAN_ANGLE: float = 2.0 # Degrees per card from center const HOVER_LIFT: float = 25.0 # Current hover state var hovered_card_index: int = -1 func _ready() -> void: mouse_filter = Control.MOUSE_FILTER_IGNORE # Connect to resized signal to re-layout when size changes resized.connect(_on_resized) func _on_resized() -> void: _layout_cards() ## Update hand display with cards func update_hand(cards: Array) -> void: # Clear existing for card_ui in hand_cards: card_ui.queue_free() hand_cards.clear() card_instances.clear() # Store card instances for card in cards: if card is CardInstance: card_instances.append(card) # Create card UI elements _create_card_visuals() # Defer layout to ensure size is set call_deferred("_layout_cards") func _create_card_visuals() -> void: for i in range(card_instances.size()): var card = card_instances[i] var card_ui = _create_card_ui(card, i) add_child(card_ui) hand_cards.append(card_ui) func _create_card_ui(card: CardInstance, index: int) -> Control: # Use Panel for better input handling var container = Panel.new() container.custom_minimum_size = Vector2(CARD_WIDTH, CARD_HEIGHT) container.size = Vector2(CARD_WIDTH, CARD_HEIGHT) container.mouse_filter = Control.MOUSE_FILTER_STOP # Style the panel with element color var style = StyleBoxFlat.new() style.bg_color = _get_element_color(card.card_data.get_primary_element()) style.border_color = Color.BLACK style.set_border_width_all(2) container.add_theme_stylebox_override("panel", style) # Card background (keep for highlighting) var bg = ColorRect.new() bg.size = Vector2(CARD_WIDTH, CARD_HEIGHT) bg.color = _get_element_color(card.card_data.get_primary_element()) bg.mouse_filter = Control.MOUSE_FILTER_IGNORE # Let parent handle input container.add_child(bg) # Card name var name_label = Label.new() name_label.text = card.card_data.name name_label.position = Vector2(5, 5) name_label.size = Vector2(CARD_WIDTH - 10, 50) name_label.add_theme_font_size_override("font_size", 11) name_label.add_theme_color_override("font_color", Color.BLACK) name_label.autowrap_mode = TextServer.AUTOWRAP_WORD name_label.mouse_filter = Control.MOUSE_FILTER_IGNORE container.add_child(name_label) # Cost indicator (top right) var cost_bg = ColorRect.new() cost_bg.size = Vector2(28, 28) cost_bg.position = Vector2(CARD_WIDTH - 32, 4) cost_bg.color = Color(0.2, 0.2, 0.2) cost_bg.mouse_filter = Control.MOUSE_FILTER_IGNORE container.add_child(cost_bg) var cost_label = Label.new() cost_label.text = str(card.card_data.cost) cost_label.position = Vector2(CARD_WIDTH - 32, 4) cost_label.size = Vector2(28, 28) cost_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER cost_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER cost_label.add_theme_font_size_override("font_size", 16) cost_label.add_theme_color_override("font_color", Color.WHITE) cost_label.mouse_filter = Control.MOUSE_FILTER_IGNORE container.add_child(cost_label) # Type indicator (bottom left) var type_label = Label.new() type_label.text = Enums.card_type_to_string(card.card_data.type) type_label.position = Vector2(5, CARD_HEIGHT - 22) type_label.add_theme_font_size_override("font_size", 10) type_label.add_theme_color_override("font_color", Color.BLACK) type_label.mouse_filter = Control.MOUSE_FILTER_IGNORE container.add_child(type_label) # Power for forwards (bottom right) if card.card_data.type == Enums.CardType.FORWARD: var power_bg = ColorRect.new() power_bg.size = Vector2(45, 20) power_bg.position = Vector2(CARD_WIDTH - 50, CARD_HEIGHT - 24) power_bg.color = Color(0.3, 0.1, 0.1, 0.8) power_bg.mouse_filter = Control.MOUSE_FILTER_IGNORE container.add_child(power_bg) var power_label = Label.new() power_label.text = str(card.card_data.power) power_label.position = Vector2(CARD_WIDTH - 50, CARD_HEIGHT - 24) power_label.size = Vector2(45, 20) power_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER power_label.add_theme_font_size_override("font_size", 12) power_label.add_theme_color_override("font_color", Color.WHITE) power_label.mouse_filter = Control.MOUSE_FILTER_IGNORE container.add_child(power_label) # Connect signals container.gui_input.connect(_on_card_gui_input.bind(index)) container.mouse_entered.connect(_on_card_mouse_entered.bind(index)) container.mouse_exited.connect(_on_card_mouse_exited.bind(index)) return container func _get_element_color(element: Enums.Element) -> Color: var color = Enums.get_element_color(element) # Lighten for card background return color.lightened(0.4) func _layout_cards() -> void: var card_count = hand_cards.size() if card_count == 0: return # Get actual display width - use size if available, otherwise viewport var display_width = size.x if display_width <= 0: var viewport = get_viewport() if viewport: display_width = viewport.get_visible_rect().size.x - 100 # Leave margins if display_width <= 0: display_width = 1820 # Fallback # Calculate total width needed var total_width = CARD_WIDTH + (card_count - 1) * CARD_OVERLAP var start_x = (display_width - total_width) / 2.0 # Position each card for i in range(card_count): var card_ui = hand_cards[i] # X position with overlap var x = start_x + i * CARD_OVERLAP # Y position (near top of container, with hover lift) var y = 10.0 if i == hovered_card_index: y -= HOVER_LIFT # Rotation based on position in fan var center_offset = i - (card_count - 1) / 2.0 var angle = center_offset * FAN_ANGLE card_ui.position = Vector2(x, y) card_ui.rotation = deg_to_rad(angle) card_ui.z_index = i if i == hovered_card_index: card_ui.z_index = 100 func _on_card_gui_input(event: InputEvent, index: int) -> void: if event is InputEventMouseButton: if event.button_index == MOUSE_BUTTON_LEFT and event.pressed: print("HandDisplay: Card clicked, index=", index) if index >= 0 and index < card_instances.size(): print("HandDisplay: Emitting card_selected for ", card_instances[index].card_data.name) card_selected.emit(card_instances[index]) func _on_card_mouse_entered(index: int) -> void: hovered_card_index = index _layout_cards() if index >= 0 and index < card_instances.size(): card_hovered.emit(card_instances[index]) func _on_card_mouse_exited(index: int) -> void: if hovered_card_index == index: hovered_card_index = -1 _layout_cards() card_unhovered.emit() ## Highlight playable cards func highlight_playable(predicate: Callable) -> void: for i in range(hand_cards.size()): var card_ui = hand_cards[i] var card = card_instances[i] var bg = card_ui.get_child(0) as ColorRect if bg: if predicate.call(card): # Add glow effect - brighter bg.color = _get_element_color(card.card_data.get_primary_element()).lightened(0.2) else: # Dim non-playable bg.color = _get_element_color(card.card_data.get_primary_element()).darkened(0.4) ## Clear all highlights func clear_highlights() -> void: for i in range(hand_cards.size()): var card_ui = hand_cards[i] var card = card_instances[i] var bg = card_ui.get_child(0) as ColorRect if bg: bg.color = _get_element_color(card.card_data.get_primary_element()) func _notification(what: int) -> void: if what == NOTIFICATION_RESIZED: _layout_cards()