369 lines
15 KiB
GDScript
369 lines
15 KiB
GDScript
extends UIPage
|
||
|
||
# UI Node References
|
||
@onready var list_container = $BG/Option/Info
|
||
@onready var previous_button = $BG/Title/previous/Button
|
||
@onready var next_button = $BG/Title/next/Button
|
||
@onready var title_label = $BG/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():
|
||
super._ready() # 调用父类的 _ready() (如果 UIPage 中有 _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 = "" # 当前逻辑上选中的项的 Key
|
||
|
||
# 1b. 【修改】从 MainController 的共享数据中获取初始应选中的题材 Key
|
||
# 使用键名 "题材"。如果未找到该键,默认为空字符串。
|
||
initial_key_to_highlight = get_main_data("题材", "") # <--- 从共享数据获取初始高亮项
|
||
|
||
if not initial_key_to_highlight.is_empty():
|
||
print("Theme (reset_display): 从 get_main_data(\"题材\") 获取到初始高亮 Key: '%s'" % initial_key_to_highlight)
|
||
else:
|
||
# 如果 get_main_data 没有提供有效的 Key (例如 "题材" 键不存在或其值为空字符串),
|
||
# initial_key_to_highlight 将是空字符串。
|
||
print("Theme (reset_display): 未从 get_main_data(\"题材\") 获取到初始高亮 Key,或 Key 为空。将尝试高亮第一页的第一个项目。")
|
||
|
||
# 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. 【逻辑不变,但使用来自 get_main_data("题材", "") 的 initial_key_to_highlight】
|
||
# 根据 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' (来自 get_main_data(\"题材\")) 在索引 %d, 目标页面 %d" % [initial_key_to_highlight, i, current_page_index])
|
||
break # 找到了,停止循环
|
||
if not found_initial_key:
|
||
print("Theme: 需要初始高亮的 Key '%s' (来自 get_main_data(\"题材\")) 未在启用的题材中找到。将显示第一页,并尝试高亮该页的第一个项目。" % initial_key_to_highlight)
|
||
# 保持 current_page_index 为 0
|
||
initial_key_to_highlight = "" # 清空,因为未找到,避免后续 _update_display 尝试高亮不存在的项
|
||
# else: initial_key_to_highlight 为空, current_page_index 保持为 0 (第一页)
|
||
|
||
# 确保页面索引有效 (与 gameplay.gd 一致)
|
||
if total_pages == 0:
|
||
current_page_index = 0
|
||
elif current_page_index >= total_pages: # 如果计算出的页面超出范围
|
||
current_page_index = total_pages - 1 if total_pages > 0 else 0
|
||
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)
|
||
|
||
set_main_data("题材", key_to_confirm)
|
||
go_next()
|