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 print("ToastManager 初始化完成") # ============ 公共方法 ============ # 显示Toast消息 # # 参数: # message: String - 消息内容 # is_success: bool - 是否为成功消息(影响颜色) func show_toast(message: String, is_success: bool = true): if toast_container == null: print("错误: toast_container 节点不存在") return print("显示Toast消息: ", message, " 成功: ", is_success) _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: print("Web平台Toast字体处理") # Web平台使用主题文件 var chinese_theme = load("res://assets/ui/chinese_theme.tres") if chinese_theme: text_label.theme = chinese_theme print("Web平台应用中文主题") else: print("Web平台中文主题加载失败") else: print("桌面平台Toast字体处理") # 桌面平台直接加载中文字体 var desktop_chinese_font = load("res://assets/fonts/msyh.ttc") if desktop_chinese_font: text_label.add_theme_font_override("font", desktop_chinese_font) print("桌面平台使用中文字体") 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