Files
whale-town-front/_Core/managers/ToastManager.gd
2026-02-07 14:11:00 +08:00

229 lines
6.8 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
class_name ToastManager
# ============================================================================
# ToastManager.gd - Toast消息管理器
# ============================================================================
# 负责创建和管理Toast消息的显示
#
# 核心功能:
# - 创建Toast消息实例
# - 管理Toast动画和生命周期
# - 支持多个Toast同时显示
# - 自动排列和清理Toast
# - 支持中文字体显示
#
# 使用方式:
# var toast_manager = ToastManager.new()
# toast_manager.setup(toast_container)
# toast_manager.show_toast("消息内容", true)
#
# 注意事项:
# - 需要提供一个容器节点来承载Toast
# - 自动处理Toast的位置计算和动画
# - 支持Web平台的字体处理
# ============================================================================
extends RefCounted
# ============ 成员变量 ============
# Toast容器和管理
var toast_container: Control # Toast消息容器
var active_toasts: Array = [] # 当前显示的Toast消息列表
var toast_counter: int = 0 # Toast计数器用于生成唯一ID
# ============ 初始化方法 ============
# 设置Toast管理器
#
# 参数:
# container: Control - Toast消息的容器节点
func setup(container: Control):
toast_container = container
# ============ 公共方法 ============
# 显示Toast消息
#
# 参数:
# message: String - 消息内容
# is_success: bool - 是否为成功消息(影响颜色)
func show_toast(message: String, is_success: bool = true):
if toast_container == null:
push_error("ToastManager: toast_container 节点不存在")
return
_create_toast_instance(message, is_success)
# 清理所有Toast
func clear_all_toasts():
for toast in active_toasts:
if is_instance_valid(toast):
toast.queue_free()
active_toasts.clear()
# ============ 私有方法 ============
# 创建Toast实例
func _create_toast_instance(message: String, is_success: bool):
toast_counter += 1
# Web平台字体处理
var is_web = OS.get_name() == "Web"
# 1. 创建Toast Panel方框UI
var toast_panel = Panel.new()
toast_panel.name = "Toast_" + str(toast_counter)
# 设置Toast样式
var style = StyleBoxFlat.new()
if is_success:
style.bg_color = Color(0.15, 0.7, 0.15, 0.95)
style.border_color = Color(0.2, 0.9, 0.2, 0.9)
else:
style.bg_color = Color(0.7, 0.15, 0.15, 0.95)
style.border_color = Color(0.9, 0.2, 0.2, 0.9)
style.border_width_left = 3
style.border_width_top = 3
style.border_width_right = 3
style.border_width_bottom = 3
style.corner_radius_top_left = 12
style.corner_radius_top_right = 12
style.corner_radius_bottom_left = 12
style.corner_radius_bottom_right = 12
style.shadow_color = Color(0, 0, 0, 0.3)
style.shadow_size = 4
style.shadow_offset = Vector2(2, 2)
toast_panel.add_theme_stylebox_override("panel", style)
# 设置Toast基本尺寸
var toast_width = 320
toast_panel.size = Vector2(toast_width, 60)
# 2. 创建VBoxContainer
var vbox = VBoxContainer.new()
vbox.add_theme_constant_override("separation", 0)
vbox.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
vbox.alignment = BoxContainer.ALIGNMENT_CENTER
# 3. 创建CenterContainer
var center_container = CenterContainer.new()
center_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
center_container.size_flags_vertical = Control.SIZE_SHRINK_CENTER
# 4. 创建Label文字控件
var text_label = Label.new()
text_label.text = message
text_label.add_theme_color_override("font_color", Color(1, 1, 1, 1))
text_label.add_theme_font_size_override("font_size", 14)
# 平台特定的字体处理
if is_web:
# Web平台使用主题文件
var chinese_theme = load("res://assets/ui/chinese_theme.tres")
if chinese_theme:
text_label.theme = chinese_theme
else:
push_warning("ToastManager: Web平台中文主题加载失败使用默认字体")
else:
# 桌面平台直接加载中文字体
var desktop_chinese_font = load("res://assets/fonts/msyh.ttc")
if desktop_chinese_font:
text_label.add_theme_font_override("font", desktop_chinese_font)
text_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
text_label.custom_minimum_size = Vector2(280, 0)
text_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
text_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
# 组装控件层级
center_container.add_child(text_label)
vbox.add_child(center_container)
toast_panel.add_child(vbox)
# 计算位置
var margin = 20
var start_x = toast_container.get_viewport().get_visible_rect().size.x
var final_x = toast_container.get_viewport().get_visible_rect().size.x - toast_width - margin
# 计算Y位置
var y_position = margin
for existing_toast in active_toasts:
if is_instance_valid(existing_toast):
y_position += existing_toast.size.y + 15
# 设置初始位置
toast_panel.position = Vector2(start_x, y_position)
# 添加到容器
toast_container.add_child(toast_panel)
active_toasts.append(toast_panel)
# 等待一帧让布局系统计算尺寸
await toast_container.get_tree().process_frame
# 让Toast高度自适应内容
var content_size = vbox.get_combined_minimum_size()
var final_height = max(60, content_size.y + 20) # 最小60加20像素边距
toast_panel.size.y = final_height
# 重新排列所有Toast
_rearrange_toasts()
# 开始动画
_animate_toast_in(toast_panel, final_x)
# Toast入场动画
func _animate_toast_in(toast_panel: Panel, final_x: float):
var tween = toast_container.create_tween()
tween.set_ease(Tween.EASE_OUT)
tween.set_trans(Tween.TRANS_BACK)
tween.parallel().tween_property(toast_panel, "position:x", final_x, 0.6)
tween.parallel().tween_property(toast_panel, "modulate:a", 1.0, 0.4)
toast_panel.modulate.a = 0.0
# 等待3秒后开始退场动画
await toast_container.get_tree().create_timer(3.0).timeout
_animate_toast_out(toast_panel)
# Toast退场动画
func _animate_toast_out(toast_panel: Panel):
if not is_instance_valid(toast_panel):
return
var tween = toast_container.create_tween()
tween.set_ease(Tween.EASE_IN)
tween.set_trans(Tween.TRANS_QUART)
var end_x = toast_container.get_viewport().get_visible_rect().size.x + 50
tween.parallel().tween_property(toast_panel, "position:x", end_x, 0.4)
tween.parallel().tween_property(toast_panel, "modulate:a", 0.0, 0.3)
await tween.finished
_cleanup_toast(toast_panel)
# 清理Toast
func _cleanup_toast(toast_panel: Panel):
if not is_instance_valid(toast_panel):
return
active_toasts.erase(toast_panel)
_rearrange_toasts()
toast_panel.queue_free()
# 重新排列Toast位置
func _rearrange_toasts():
var margin = 20
var current_y = margin
for i in range(active_toasts.size()):
var toast = active_toasts[i]
if is_instance_valid(toast):
var tween = toast_container.create_tween()
tween.tween_property(toast, "position:y", current_y, 0.2)
current_y += toast.size.y + 15