class_name ActionLog extends Control ## ActionLog - Collapsible panel showing game actions signal undo_requested # UI Components var panel: PanelContainer var toggle_button: Button var scroll_container: ScrollContainer var log_container: VBoxContainer var undo_button: Button var clear_button: Button # State var is_expanded: bool = true var action_entries: Array[Control] = [] # Layout constants const COLLAPSED_WIDTH: float = 40.0 const EXPANDED_WIDTH: float = 280.0 const PANEL_HEIGHT: float = 400.0 func _ready() -> void: _create_ui() _connect_signals() func _create_ui() -> void: # Set up this control to anchor to right side set_anchors_preset(Control.PRESET_CENTER_RIGHT) custom_minimum_size = Vector2(EXPANDED_WIDTH, PANEL_HEIGHT) size = Vector2(EXPANDED_WIDTH, PANEL_HEIGHT) mouse_filter = Control.MOUSE_FILTER_IGNORE # Main horizontal container var hbox = HBoxContainer.new() add_child(hbox) hbox.set_anchors_preset(Control.PRESET_FULL_RECT) hbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL hbox.size_flags_vertical = Control.SIZE_EXPAND_FILL hbox.mouse_filter = Control.MOUSE_FILTER_IGNORE # Toggle button on the left edge toggle_button = Button.new() toggle_button.text = ">" toggle_button.custom_minimum_size = Vector2(30, 0) toggle_button.size_flags_vertical = Control.SIZE_EXPAND_FILL toggle_button.tooltip_text = "Toggle Action Log (L)" hbox.add_child(toggle_button) # Panel container for the log panel = PanelContainer.new() hbox.add_child(panel) panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL panel.size_flags_vertical = Control.SIZE_EXPAND_FILL var style = StyleBoxFlat.new() style.bg_color = Color(0.08, 0.08, 0.12, 0.95) style.border_color = Color(0.3, 0.3, 0.4) style.set_border_width_all(2) style.set_corner_radius_all(5) style.set_content_margin_all(8) panel.add_theme_stylebox_override("panel", style) # Panel content var panel_vbox = VBoxContainer.new() panel.add_child(panel_vbox) panel_vbox.add_theme_constant_override("separation", 5) # Header var header = HBoxContainer.new() panel_vbox.add_child(header) header.add_theme_constant_override("separation", 5) var title = Label.new() title.text = "Action Log" title.add_theme_font_size_override("font_size", 14) title.size_flags_horizontal = Control.SIZE_EXPAND_FILL header.add_child(title) # Undo button undo_button = Button.new() undo_button.text = "Undo" undo_button.custom_minimum_size = Vector2(50, 25) undo_button.add_theme_font_size_override("font_size", 11) undo_button.tooltip_text = "Undo last action (Ctrl+Z)" undo_button.disabled = true header.add_child(undo_button) # Clear button clear_button = Button.new() clear_button.text = "Clear" clear_button.custom_minimum_size = Vector2(45, 25) clear_button.add_theme_font_size_override("font_size", 11) header.add_child(clear_button) # Separator var separator = HSeparator.new() panel_vbox.add_child(separator) # Scroll container for log entries scroll_container = ScrollContainer.new() panel_vbox.add_child(scroll_container) scroll_container.size_flags_vertical = Control.SIZE_EXPAND_FILL scroll_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL scroll_container.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED scroll_container.custom_minimum_size = Vector2(200, 250) # Log entries container log_container = VBoxContainer.new() scroll_container.add_child(log_container) log_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL log_container.size_flags_vertical = Control.SIZE_EXPAND_FILL log_container.add_theme_constant_override("separation", 3) func _connect_signals() -> void: toggle_button.pressed.connect(_on_toggle_pressed) undo_button.pressed.connect(_on_undo_pressed) clear_button.pressed.connect(clear_log) # Defer GameManager connections to ensure it's ready call_deferred("_connect_game_manager_signals") func _connect_game_manager_signals() -> void: # Connect to GameManager signals if GameManager: if not GameManager.message.is_connected(_on_game_message): GameManager.message.connect(_on_game_message) if not GameManager.card_played.is_connected(_on_card_played): GameManager.card_played.connect(_on_card_played) if not GameManager.turn_changed.is_connected(_on_turn_changed): GameManager.turn_changed.connect(_on_turn_changed) if not GameManager.phase_changed.is_connected(_on_phase_changed): GameManager.phase_changed.connect(_on_phase_changed) if not GameManager.damage_dealt.is_connected(_on_damage_dealt): GameManager.damage_dealt.connect(_on_damage_dealt) if not GameManager.action_undone.is_connected(_on_action_undone): GameManager.action_undone.connect(_on_action_undone) func _on_toggle_pressed() -> void: toggle_panel() func toggle_panel() -> void: is_expanded = not is_expanded panel.visible = is_expanded toggle_button.text = ">" if is_expanded else "<" if is_expanded: custom_minimum_size.x = EXPANDED_WIDTH size.x = EXPANDED_WIDTH else: custom_minimum_size.x = COLLAPSED_WIDTH size.x = COLLAPSED_WIDTH func _on_undo_pressed() -> void: undo_requested.emit() ## Add a log entry with a specific type for styling func add_entry(text: String, entry_type: String = "info") -> void: if not log_container: return var entry = _create_entry(text, entry_type) log_container.add_child(entry) action_entries.append(entry) # Keep log at reasonable size while action_entries.size() > 100: var old_entry = action_entries.pop_front() old_entry.queue_free() # Scroll to bottom after adding call_deferred("_scroll_to_bottom") func _create_entry(text: String, entry_type: String) -> Control: var entry_panel = PanelContainer.new() var style = StyleBoxFlat.new() style.set_content_margin_all(4) style.set_corner_radius_all(3) # Color based on type match entry_type: "turn": style.bg_color = Color(0.2, 0.3, 0.4, 0.8) "phase": style.bg_color = Color(0.15, 0.25, 0.35, 0.6) "action": style.bg_color = Color(0.2, 0.2, 0.3, 0.7) "damage": style.bg_color = Color(0.4, 0.15, 0.15, 0.7) "card": style.bg_color = Color(0.15, 0.3, 0.2, 0.7) _: style.bg_color = Color(0.12, 0.12, 0.18, 0.5) entry_panel.add_theme_stylebox_override("panel", style) var label = Label.new() label.text = text label.add_theme_font_size_override("font_size", 11) label.autowrap_mode = TextServer.AUTOWRAP_WORD label.custom_minimum_size.x = 200 entry_panel.add_child(label) return entry_panel func _scroll_to_bottom() -> void: await get_tree().process_frame scroll_container.scroll_vertical = int(scroll_container.get_v_scroll_bar().max_value) func clear_log() -> void: for entry in action_entries: entry.queue_free() action_entries.clear() ## Enable or disable undo button func set_undo_available(available: bool) -> void: undo_button.disabled = not available ## Manually add a message (can be called from Main.gd if signal connection fails) func log_message(text: String) -> void: _on_game_message(text) ## Manually log a turn change func log_turn_change(player_name: String, turn_number: int) -> void: _on_turn_changed(player_name, turn_number) ## Manually log a phase change func log_phase_change(phase_name: String) -> void: _on_phase_changed(phase_name) ## Signal handlers func _on_game_message(text: String) -> void: # Determine entry type based on message content var entry_type = "info" if "attacks" in text or "blocks" in text: entry_type = "action" elif "damage" in text or "broken" in text: entry_type = "damage" elif "Played" in text or "Discarded" in text or "Dulled" in text: entry_type = "card" add_entry(text, entry_type) func _on_card_played(_card_data: Dictionary) -> void: # Already handled by message signal pass func _on_turn_changed(player_name: String, turn_number: int) -> void: add_entry("=== " + player_name + " - Turn " + str(turn_number) + " ===", "turn") func _on_phase_changed(phase_name: String) -> void: add_entry(phase_name, "phase") func _on_damage_dealt(_player_name: String, _amount: int) -> void: # Already handled by message signal pass func _on_action_undone(action_name: String) -> void: add_entry("UNDO: " + action_name, "action") # Input is handled by Main.gd to avoid duplicate handling