forked from datawhale/whale-town-front
- AuthScene: 修复节点路径错误 (WhaleFrame, UsernameInput) - ChatManager: 修复 timestamp 类型转换 (String -> float) - ChatMessage: 修复节点引用获取方式和 UI 显示 - ChatUI: 优化消息列表布局对齐
212 lines
6.8 KiB
GDScript
212 lines
6.8 KiB
GDScript
extends Panel
|
||
|
||
# ============================================================================
|
||
# ChatMessage.gd - 聊天消息气泡组件
|
||
# ============================================================================
|
||
# 显示单条聊天消息的 UI 组件
|
||
#
|
||
# 核心职责:
|
||
# - 显示消息发送者、内容、时间戳
|
||
# - 区分自己和他人的消息样式
|
||
# - 自动格式化时间戳
|
||
#
|
||
# 使用方式:
|
||
# var message := chat_message_scene.instantiate()
|
||
# message.set_message("PlayerName", "Hello!", timestamp, false)
|
||
#
|
||
# 注意事项:
|
||
# - 使用 @onready 缓存节点引用
|
||
# - 最大宽度限制为 400 像素
|
||
# ============================================================================
|
||
|
||
class_name ChatMessage
|
||
|
||
# ============================================================================
|
||
# 导出参数
|
||
# ============================================================================
|
||
|
||
# 最大宽度(像素)
|
||
@export var max_width: int = 400
|
||
|
||
# ============================================================================
|
||
# 节点引用
|
||
# ============================================================================
|
||
|
||
# 用户名标签
|
||
var username_label: Label
|
||
|
||
# 时间戳标签
|
||
var timestamp_label: Label
|
||
|
||
# 内容标签
|
||
var content_label: RichTextLabel
|
||
|
||
# 用户信息容器
|
||
var user_info_container: HBoxContainer
|
||
|
||
# ============================================================================
|
||
# 生命周期方法
|
||
# ============================================================================
|
||
|
||
func _ready() -> void:
|
||
# 使用 get_node 获取节点引用
|
||
username_label = get_node_or_null("VBoxContainer/UserInfoContainer/UsernameLabel")
|
||
timestamp_label = get_node_or_null("VBoxContainer/UserInfoContainer/TimestampLabel")
|
||
content_label = get_node_or_null("VBoxContainer/ContentLabel")
|
||
user_info_container = get_node_or_null("VBoxContainer/UserInfoContainer")
|
||
|
||
# 调试输出
|
||
if username_label:
|
||
print("✅ UsernameLabel found")
|
||
else:
|
||
print("❌ UsernameLabel NOT found!")
|
||
# 打印所有子节点帮助调试
|
||
print("Available children: ", _get_all_children_names(self))
|
||
|
||
# 应用最大宽度限制
|
||
custom_minimum_size.x = min(max_width, get_parent().size.x)
|
||
|
||
# ============================================================================
|
||
# 成员变量
|
||
# ============================================================================
|
||
|
||
# 是否为自己发送的消息
|
||
var _is_self: bool = false
|
||
|
||
# ============================================================================
|
||
# 公共 API
|
||
# ============================================================================
|
||
|
||
# 设置消息内容
|
||
#
|
||
# 参数:
|
||
# from_user: String - 发送者用户名
|
||
# content: String - 消息内容
|
||
# timestamp: float - Unix 时间戳
|
||
# is_self: bool - 是否为自己发送的消息(默认 false)
|
||
#
|
||
# 使用示例:
|
||
# message.set_message("Alice", "Hello!", 1703500800.0, false)
|
||
func set_message(from_user: String, content: String, timestamp: float, is_self: bool = false) -> void:
|
||
_is_self = is_self
|
||
|
||
# 设置用户名(带空值检查)
|
||
if username_label:
|
||
username_label.text = from_user
|
||
else:
|
||
push_error("ChatMessage: username_label is null!")
|
||
return
|
||
|
||
# 设置内容
|
||
if content_label:
|
||
content_label.clear() # 清除默认文本和所有内容
|
||
content_label.bbcode_text = content # 使用 bbcode_text 因为 bbcode_enabled = true
|
||
else:
|
||
push_error("ChatMessage: content_label is null!")
|
||
return
|
||
|
||
# 设置时间戳
|
||
if timestamp_label:
|
||
timestamp_label.text = _format_timestamp(timestamp)
|
||
else:
|
||
push_error("ChatMessage: timestamp_label is null!")
|
||
return
|
||
|
||
# 应用样式
|
||
_apply_style()
|
||
|
||
# ============================================================================
|
||
# 内部方法 - 样式处理
|
||
# ============================================================================
|
||
|
||
# 应用样式(自己和他人的消息不同)
|
||
func _apply_style() -> void:
|
||
if not username_label or not timestamp_label or not user_info_container:
|
||
return
|
||
|
||
# 设置大小约束 - 宽度固定,高度适应内容
|
||
if get_parent():
|
||
custom_minimum_size.x = min(max_width, get_parent().size.x)
|
||
|
||
# 重要:设置垂直 size flags 让 Panel 适应内容高度
|
||
size_flags_vertical = Control.SIZE_SHRINK_BEGIN
|
||
|
||
if _is_self:
|
||
# 自己的消息:右侧对齐,蓝色背景
|
||
size_flags_horizontal = Control.SIZE_SHRINK_END
|
||
user_info_container.alignment = BoxContainer.ALIGNMENT_END
|
||
|
||
# 设置面板样式
|
||
add_theme_stylebox_override("panel", _get_self_style())
|
||
|
||
# 设置文字颜色 - ID使用金色 #FFD700
|
||
username_label.add_theme_color_override("font_color", Color(1.0, 0.8431373, 0.0))
|
||
timestamp_label.add_theme_color_override("font_color", Color(0.7, 0.7, 0.7))
|
||
else:
|
||
# 他人的消息:左侧对齐,灰色背景
|
||
size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
|
||
user_info_container.alignment = BoxContainer.ALIGNMENT_BEGIN
|
||
|
||
# 设置面板样式
|
||
add_theme_stylebox_override("panel", _get_other_style())
|
||
|
||
# 设置文字颜色 - ID使用蓝色 #69c0ff
|
||
username_label.add_theme_color_override("font_color", Color(0.4117647, 0.7529412, 1.0))
|
||
timestamp_label.add_theme_color_override("font_color", Color(0.5, 0.5, 0.5))
|
||
|
||
# 获取自己消息的样式
|
||
func _get_self_style() -> StyleBoxFlat:
|
||
var style := StyleBoxFlat.new()
|
||
style.bg_color = Color(0.2, 0.6, 1.0, 0.3)
|
||
style.corner_radius_top_left = 10
|
||
style.corner_radius_top_right = 10
|
||
style.corner_radius_bottom_left = 10
|
||
style.corner_radius_bottom_right = 2
|
||
style.content_margin_left = 10
|
||
style.content_margin_right = 10
|
||
style.content_margin_top = 8
|
||
style.content_margin_bottom = 8
|
||
return style
|
||
|
||
# 获取他人消息的样式
|
||
func _get_other_style() -> StyleBoxFlat:
|
||
var style := StyleBoxFlat.new()
|
||
style.bg_color = Color(0.9, 0.9, 0.9, 0.5)
|
||
style.corner_radius_top_left = 10
|
||
style.corner_radius_top_right = 10
|
||
style.corner_radius_bottom_left = 2
|
||
style.corner_radius_bottom_right = 10
|
||
style.content_margin_left = 10
|
||
style.content_margin_right = 10
|
||
style.content_margin_top = 8
|
||
style.content_margin_bottom = 8
|
||
return style
|
||
|
||
# ============================================================================
|
||
# 内部方法 - 工具函数
|
||
# ============================================================================
|
||
|
||
# 格式化时间戳
|
||
#
|
||
# 参数:
|
||
# timestamp: float - Unix 时间戳
|
||
#
|
||
# 返回值:
|
||
# String - 格式化的时间字符串
|
||
func _format_timestamp(timestamp: float) -> String:
|
||
if timestamp == 0:
|
||
return ""
|
||
|
||
var datetime := Time.get_datetime_dict_from_unix_time(timestamp)
|
||
|
||
# 格式化为 HH:MM
|
||
return "%02d:%02d" % [datetime.hour, datetime.minute]
|
||
|
||
# 获取所有子节点名称(调试用)
|
||
func _get_all_children_names(node: Node, _indent: int = 0) -> String:
|
||
var result := ""
|
||
for child in node.get_children():
|
||
result += " ".repeat(_indent) + child.name + "\n"
|
||
result += _get_all_children_names(child, _indent + 1)
|
||
return result
|