# theme.gd (更新版 - 对齐 gameplay.gd 逻辑) extends NinePatchRect # UI Node References @onready var list_container = $Option/Info @onready var previous_button = $Title/previous/Button @onready var next_button = $Title/next/Button @onready var title_label = $Title/Title # Configuration var items_per_page: int = 7 # 每页显示的选项数量 (保持与原 theme.gd 一致) # Data Storage var theme_list_nodes: Array[VBoxContainer] = [] # 存储 List_1, List_2 等节点 var option_nodes_per_list: Array = [] # 存储每个 List 对应的 option_X 按钮数组 var all_enabled_themes: Array = [] # 存储筛选后的题材数据 { "name": String, "data": Dictionary } # State var total_pages: int = 0 var current_page_index: int = 0 var selected_theme_key: String = "" # 当前逻辑上选中的项的 Key (用于确认) var currently_selected_button: Button = null # 当前高亮/聚焦的按钮节点引用 var initial_key_to_highlight: String = "" # 弹窗打开时需要高亮的 Key (由父节点设置) func _ready(): # 1. 获取 List 节点 (与原版 theme.gd 类似,但使用 gameplay.gd 的错误处理) for i in range(1, 7): # 假设最多有 List_6 (与原 theme.gd 匹配) var list_node = list_container.get_node_or_null("List_" + str(i)) if list_node: theme_list_nodes.append(list_node) else: break # 假设 List 节点是连续的 if theme_list_nodes.is_empty(): printerr("错误:在 theme.tscn 中未找到 List 节点!无法显示选项。") set_process(false) # 禁用处理,因为没有UI元素 return # 2. 获取每个 List 内的 Option 节点 (与原版 theme.gd 类似) for list_node in theme_list_nodes: var current_list_options: Array = [] for i in range(1, items_per_page + 1): var option_node = list_node.get_node_or_null("option_" + str(i)) if option_node and option_node is Button: current_list_options.append(option_node) else: # 使用 gameplay.gd 的警告格式 push_warning("警告:在 %s 中未能找到 option_%d 按钮" % [list_node.name, i]) option_nodes_per_list.append(current_list_options) # 3. 连接按钮信号 (与 gameplay.gd 一致) if previous_button: if not previous_button.pressed.is_connected(_on_previous_pressed): previous_button.pressed.connect(_on_previous_pressed) else: printerr("错误:在 theme.tscn 中未找到 Previous 按钮") if next_button: if not next_button.pressed.is_connected(_on_next_pressed): next_button.pressed.connect(_on_next_pressed) else: printerr("错误:在 theme.tscn 中未找到 Next 按钮") # 4. 连接 visibility_changed 信号 (与 gameplay.gd 一致) if not is_connected("visibility_changed", _on_visibility_changed): visibility_changed.connect(_on_visibility_changed) # 5. 移除 _ready 中的初始数据加载和显示更新 # 这些逻辑现在完全由 _on_visibility_changed -> reset_display 处理 # --- Visibility Change Handler (与 gameplay.gd 一致) --- func _on_visibility_changed(): if is_visible_in_tree(): print("Theme: 弹窗变为可见,调用 reset_display()") reset_display() # 当变为可见时调用 reset # else: # 可选:隐藏时的清理逻辑 # print("Theme: 弹窗变为隐藏。") # --- 新增:用于父节点设置初始选中项 (与 gameplay.gd 一致) --- func set_initial_selection(key: String): """ 由父节点 (task_development) 在显示此弹窗前调用, 传递当前在 task_info 中选中的题材 Key。 """ initial_key_to_highlight = key print("Theme: 父节点设置初始高亮 Key 为: '%s'" % initial_key_to_highlight) # --- Data Processing and Display Logic --- func _process_theme_data(all_themes_data: Dictionary): """【修改】根据从 GameState 获取的题材数据,筛选出启用的题材,并计算总页数。""" all_enabled_themes.clear() if all_themes_data.is_empty(): push_warning("警告:接收到的题材数据为空。") total_pages = 0 return # 遍历从 GameState 获取的题材字典 for theme_name in all_themes_data: var theme_info = all_themes_data[theme_name] # 检查是否为字典且 "enabled" 字段为 true if typeof(theme_info) == TYPE_DICTIONARY and theme_info.get("enabled", false) == true: all_enabled_themes.append({"name": theme_name, "data": theme_info}) # else: # Debugging # print("Theme: 过滤掉未启用或格式错误的题材: ", theme_name) # 根据筛选结果计算总页数 if items_per_page > 0: total_pages = ceil(float(all_enabled_themes.size()) / items_per_page) else: total_pages = 0 printerr("错误:items_per_page 为零,无法计算页数。") print("Theme: 处理完成,找到 %d 个启用的题材,共 %d 页。" % [all_enabled_themes.size(), total_pages]) func reset_display(): """【修改】重置状态,从 GameState 重新加载数据,并准备更新显示。""" print("Theme: 重置显示状态 (reset_display)。") # 1. 重置本地状态变量 currently_selected_button = null selected_theme_key = "" # initial_key_to_highlight 由 set_initial_selection 设置,这里不清空 # 2. 【修改】从 GameState 重新加载并处理数据 if not GameState: printerr("错误:GameState 在 reset_display 期间不可用。") total_pages = 0 all_enabled_themes.clear() _update_display() # 即使没有数据也要更新显示(显示空状态) return # --- 获取题材数据 --- var task_dev_data = GameState.get_value("task_development", {}) if task_dev_data.is_empty(): printerr("错误:从 GameState 获取 'task_development' 数据失败。") total_pages = 0 all_enabled_themes.clear() else: # --- 从 task_development 中获取 themes --- var all_themes_data = task_dev_data.get("themes", {}) _process_theme_data(all_themes_data) # 重新处理数据,计算 total_pages # 3. 【修改】根据 initial_key_to_highlight 确定初始页面 current_page_index = 0 # 默认为第一页 var found_initial_key = false if total_pages > 0 and not initial_key_to_highlight.is_empty(): # 查找 initial_key_to_highlight 所在的索引和页面 for i in range(all_enabled_themes.size()): if all_enabled_themes[i].name == initial_key_to_highlight: current_page_index = int(floor(float(i) / items_per_page)) found_initial_key = true print("Theme: 找到需要初始高亮的 Key '%s' 在索引 %d, 目标页面 %d" % [initial_key_to_highlight, i, current_page_index]) break # 找到了,停止循环 if not found_initial_key: print("Theme: 需要初始高亮的 Key '%s' 未在启用的题材中找到,将显示第一页。" % initial_key_to_highlight) # 保持 current_page_index 为 0 initial_key_to_highlight = "" # 清空,避免后续 _update_display 尝试高亮不存在的项 # 确保页面索引有效 (与 gameplay.gd 一致) if total_pages == 0: current_page_index = 0 elif current_page_index >= total_pages: current_page_index = total_pages - 1 elif current_page_index < 0: current_page_index = 0 # 4. 触发显示更新 _update_display() func _update_display(): """【更新版】更新列表的可见性并填充当前页面的内容。 现在将“经验”和“成本”显示为整数。 """ # --- 处理无数据情况 (与 gameplay.gd 一致) --- if theme_list_nodes.is_empty(): print("Theme: 无可用的 List 节点。") if title_label: title_label.text = "题材选择" return if total_pages == 0: for list_node in theme_list_nodes: list_node.visible = false print("Theme: 没有启用的题材选项可供显示。") if title_label: title_label.text = "题材选择 (无可用)" if previous_button: previous_button.disabled = true if next_button: next_button.disabled = true return else: if previous_button: previous_button.disabled = (total_pages <= 1) if next_button: next_button.disabled = (total_pages <= 1) # --- 更新页面索引 (循环) (与 gameplay.gd 一致) --- if total_pages > 0 : current_page_index = current_page_index % total_pages if current_page_index < 0: current_page_index += total_pages else: current_page_index = 0 # --- 更新标题 (与 gameplay.gd 一致) --- if title_label: var display_page_number = current_page_index + 1 title_label.text = "题材选择 %d/%d" % [display_page_number, total_pages] # --- 更新 List 可见性 (与 gameplay.gd 一致) --- for i in range(theme_list_nodes.size()): theme_list_nodes[i].visible = (i == current_page_index) # --- 填充可见列表并查找要高亮的按钮 (与 gameplay.gd 类似) --- var button_to_highlight: Button = null var first_enabled_button_on_page: Button = null if current_page_index < option_nodes_per_list.size(): var current_options = option_nodes_per_list[current_page_index] var start_index = current_page_index * items_per_page for i in range(current_options.size()): var option_button = current_options[i] var item_index = start_index + i # --- 断开旧信号 --- if option_button.pressed.is_connected(_on_option_selected): option_button.pressed.disconnect(_on_option_selected) if item_index < all_enabled_themes.size(): var theme_entry = all_enabled_themes[item_index] var theme_name = theme_entry.name var theme_data = theme_entry.data # 包含 Cost, Experience, Popularity 等 var row = option_button.get_node_or_null("Row") if row: # 填充标签文本 var title_label_in_row = row.get_node_or_null("Title") var exp_label = row.get_node_or_null("Experience") var pop_label = row.get_node_or_null("Popularity") var cost_label = row.get_node_or_null("Cost") if row.has_node("Cost") else row.get_node_or_null("cost") if title_label_in_row: title_label_in_row.text = theme_name # 【修改】设置经验值,显示为整数 if exp_label: var experience_value = theme_data.get("Experience", 0.0) exp_label.text = str(int(experience_value)) # 【保持】设置流行度,保持原始字符串显示 if pop_label: var popularity_value = theme_data.get("Popularity", "未知") pop_label.text = str(popularity_value) # 【修改】设置成本,显示为整数 if cost_label: var cost_value = theme_data.get("Cost", 0.0) cost_label.text = str(int(cost_value)) option_button.visible = true option_button.disabled = false option_button.set_meta("theme_name", theme_name) # 存储题材名称 # --- 连接新信号 (使用 bind) --- option_button.pressed.connect(_on_option_selected.bind(theme_name, option_button)) if first_enabled_button_on_page == null: first_enabled_button_on_page = option_button if not initial_key_to_highlight.is_empty() and theme_name == initial_key_to_highlight: button_to_highlight = option_button # print("Theme: 找到需要初始高亮的按钮: ", button_to_highlight.name) # Debug else: # 未找到 Row 节点 push_warning("警告:在按钮 %s 中未找到 Row 节点" % option_button.get_path()) option_button.visible = false option_button.disabled = true else: # 此槽位没有对应的启用题材数据 option_button.visible = false option_button.disabled = true else: printerr("错误:当前页面索引 %d 超出 option_nodes_per_list 的范围 (大小 %d)" % [current_page_index, option_nodes_per_list.size()]) # --- 设置初始高亮和焦点 (与 gameplay.gd 一致) --- if button_to_highlight: _set_initial_highlight(button_to_highlight) elif first_enabled_button_on_page: print("Theme: 初始 Key '%s' 不在本页或为空。高亮本页第一个按钮: %s" % [initial_key_to_highlight, first_enabled_button_on_page.name]) _set_initial_highlight(first_enabled_button_on_page) else: print("Theme: 本页没有可供高亮的按钮。") currently_selected_button = null selected_theme_key = "" # (Helper) 设置初始高亮和焦点 (与 gameplay.gd 一致) func _set_initial_highlight(button_to_highlight: Button): if not is_instance_valid(button_to_highlight): printerr("错误:传递给 _set_initial_highlight 的按钮无效。") currently_selected_button = null selected_theme_key = "" return # print("Theme: 设置高亮按钮: ", button_to_highlight.name) # Debug currently_selected_button = button_to_highlight # --- 修改元数据键名 --- selected_theme_key = button_to_highlight.get_meta("theme_name", "") # 同时设置 Key # print("Theme: currently_selected_button 设置为: ", currently_selected_button.name) # Debug # print("Theme: selected_theme_key 设置为: '", selected_theme_key, "'") # Debug if currently_selected_button and currently_selected_button.is_inside_tree(): # print("Theme: 尝试为按钮 call_deferred('grab_focus'): ", currently_selected_button.name) # Debug currently_selected_button.call_deferred("grab_focus") else: print("Theme: 按钮无效或不在场景树中,无法获取焦点。") # --- Signal Handlers --- # (与 gameplay.gd 一致) func _on_previous_pressed(): if total_pages > 1: current_page_index -= 1 _update_display() # (与 gameplay.gd 一致) func _on_next_pressed(): if total_pages > 1: current_page_index += 1 _update_display() # 【修改】处理选项按钮按下事件 (与 gameplay.gd 一致) func _on_option_selected(theme_name: String, button_node: Button): """当选项按钮被按下时调用。""" if not is_instance_valid(button_node): # --- 修改错误信息中的 Key 类型 --- printerr("错误:在 _on_option_selected 中收到无效按钮节点,Key: " + theme_name) return if button_node == currently_selected_button: # --- 双击 (或点击已选中的按钮) --- print("Theme: 检测到双击或确认点击: ", theme_name) _confirm_and_close(theme_name) # 确认此选择 else: # --- 首次点击或点击不同按钮 --- print("Theme: 选中 (高亮) 题材: ", theme_name) currently_selected_button = button_node selected_theme_key = theme_name # 更新 Key # 更新视觉焦点 button_node.grab_focus() # 【修改】确认选择并关闭弹窗的逻辑 (与 gameplay.gd 一致) func _confirm_and_close(key_to_confirm: String): """更新父节点并关闭此弹窗。""" if key_to_confirm.is_empty(): printerr("错误:尝试确认一个空的题材 Key。") return print("Theme: 确认选择: ", key_to_confirm) # --- 更新父节点 (Task Development Popup) --- var parent_node = get_parent() if parent_node and parent_node.has_method("update_task_options"): # --- 修改传递给父节点的字典键名 --- parent_node.update_task_options({"题材": key_to_confirm}) print("Theme: 已更新父节点的 task_options: 题材 = ", key_to_confirm) else: printerr("错误:Theme: 父节点未找到或缺少 'update_task_options' 方法。") # --- 关闭此弹窗 (与 gameplay.gd 一致) --- if parent_node and parent_node.has_method("_close_child_popup_and_return"): parent_node._close_child_popup_and_return(self) else: printerr("错误:Theme: 父节点未找到或缺少 '_close_child_popup_and_return' 方法。") self.hide() # 备选方案