class_name ModalLayerController extends RefCounted # 弹窗层控制器: # - 统一管理 ModalLayer、ToastLabel 和打开/关闭动效。 # - 面板模块只返回 Control,不直接决定层级;这样顶部命中层、农场命中层和弹窗层不会互相抢事件。 # - 每次关闭都会释放旧弹窗节点,避免活动面板频繁打开后常驻大量离屏节点。 const UiMotionScript := preload("res://scripts/ui/common/ui_motion.gd") const I18nScript := preload("res://scripts/core/i18n.gd") var _owner: Control var _panel_size := Vector2(640.0, 960.0) var _style_label: Callable var _modal_layer: Control var _toast_label: Label var _modal_tween: Tween var _toast_tween: Tween func setup(owner: Control, panel_size: Vector2, style_label: Callable) -> void: _owner = owner _panel_size = panel_size _style_label = style_label _setup_modal_layer() func layer() -> Control: return _modal_layer func toast_label() -> Label: return _toast_label func open(panel: Control) -> void: if _modal_layer == null or panel == null: return _modal_layer.visible = true _modal_layer.add_child(panel) # 原版弹窗有轻微弹出感,这里用 scale + alpha 统一复用,避免各面板各自动画。 panel.pivot_offset = _panel_size * 0.5 panel.scale = Vector2(0.88, 0.88) panel.modulate = Color(1.0, 1.0, 1.0, 0.0) if _modal_tween != null: _modal_tween.kill() _modal_tween = _owner.create_tween().set_parallel(true) _modal_tween.tween_property(panel, "scale", Vector2.ONE, 0.2).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT) _modal_tween.tween_property(panel, "modulate:a", 1.0, 0.12) func close(panel: Control, animated := true) -> void: if _modal_layer == null: return if _modal_tween != null: _modal_tween.kill() if panel == null: # panel 为空时通常是强制清理,比如切换功能入口前先关闭旧弹窗。 _clear_children(_modal_layer) _modal_layer.visible = false return if not animated: # 某些面板立即切换时不播放关闭动画,减少视觉拖尾。 _clear_children(_modal_layer) _modal_layer.visible = false return _modal_tween = _owner.create_tween().set_parallel(true) _modal_tween.tween_property(panel, "scale", Vector2(0.94, 0.94), 0.14).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN) _modal_tween.tween_property(panel, "modulate:a", 0.0, 0.12) _modal_tween.chain().tween_callback(func() -> void: _clear_children(_modal_layer) _modal_layer.visible = false ) func show_toast(message: String) -> void: if _toast_label == null: return # Toast 复用同一个 Label 和 Tween,连续提示时 kill 旧动画再开始新动画。 _toast_label.text = I18nScript.t(message) _toast_label.visible = true _toast_label.modulate = Color(1.0, 1.0, 1.0, 0.0) if _toast_tween != null: _toast_tween.kill() _toast_tween = _owner.create_tween() _toast_tween.tween_property(_toast_label, "modulate:a", 1.0, 0.12) _toast_tween.tween_interval(1.35) _toast_tween.tween_property(_toast_label, "modulate:a", 0.0, 0.18) _toast_tween.tween_callback(func() -> void: _toast_label.visible = false) func bring_to_front() -> void: if _owner == null: return # 场景新增 hit layer 或动态节点后调用,确保弹窗和 Toast 始终在最上层。 if _modal_layer != null: _owner.move_child(_modal_layer, _owner.get_child_count() - 1) if _toast_label != null: _owner.move_child(_toast_label, _owner.get_child_count() - 1) func _setup_modal_layer() -> void: if _owner == null: return # ModalLayer 吃掉所有背景点击,用面板内部 Close 或 mask 按钮决定是否关闭。 _modal_layer = Control.new() _modal_layer.name = "ModalLayer" _modal_layer.set_anchors_preset(Control.PRESET_FULL_RECT) _modal_layer.mouse_filter = Control.MOUSE_FILTER_STOP _modal_layer.z_index = 2000 _modal_layer.visible = false _owner.add_child(_modal_layer) _owner.move_child(_modal_layer, _owner.get_child_count() - 1) _toast_label = Label.new() # Toast 不截获输入,层级高于弹窗,防止按钮反馈遮住提示。 _toast_label.name = "ToastLabel" _toast_label.position = Vector2(80.0, 700.0) _toast_label.size = Vector2(480.0, 44.0) _toast_label.visible = false _toast_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER _toast_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER _toast_label.mouse_filter = Control.MOUSE_FILTER_IGNORE _toast_label.z_index = 3000 if _style_label.is_valid(): _style_label.call(_toast_label, 24, Color.WHITE, 3, Color.html("#6a3113")) _owner.add_child(_toast_label) _owner.move_child(_toast_label, _owner.get_child_count() - 1) func _clear_children(parent: Node) -> void: for child in parent.get_children(): parent.remove_child(child) child.queue_free()