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