hy-farm/godot/FarmGodot/scripts/ui/common/modal_layer_controller.gd
2026-05-29 19:54:56 +08:00

134 lines
4.6 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 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()