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

198 lines
5.9 KiB
GDScript

class_name LandActionMenu
extends RefCounted
const UiMotionScript := preload("res://scripts/ui/common/ui_motion.gd")
const DIRECT_HIT_GROW := 8.0
var _owner: Control
var _layer: Control
var _dismiss_button: Button
var _get_land_node: Callable
var _on_action: Callable
var _on_dismiss: Callable
var _textures: Dictionary = {}
var _button_size := Vector2(92.0, 88.0)
var _origin := Vector2.ZERO
var _hide_tween: Tween
func setup(options: Dictionary) -> void:
_owner = options.get("owner") as Control
_get_land_node = options.get("get_land_node", Callable())
_on_action = options.get("on_action", Callable())
_on_dismiss = options.get("on_dismiss", Callable())
_textures = options.get("textures", {})
_button_size = options.get("button_size", _button_size)
_create_layer()
func show(position: int, actions: Array[String]) -> void:
if _layer == null:
return
var land_node := _get_land_node_for(position)
if land_node == null:
return
_clear_children(_layer)
if actions.is_empty():
_layer.visible = false
return
_layer.add_child(_dismiss_button)
var center := _land_anchor_in_layer(land_node)
_origin = center
var count := actions.size()
var angle_start := -90.0
if center.x < 150.0 and count > 2:
angle_start = 0.0
elif center.x > 490.0 and count > 2:
angle_start = 180.0
var angle_step := 50.0
var radius := 110.0
if count >= 2:
if count == 2:
angle_step = 55.0
elif count == 3:
angle_step = 72.0
else:
if count == 4:
radius = 95.0
angle_step = 360.0 / float(count)
if center.x < 150.0 or center.x > 490.0:
angle_step = 270.0 / float(count)
if count == 5:
radius = 125.0
angle_start -= float(count - 1) * angle_step * 0.5
else:
radius = 60.0
for index in range(count):
var action := actions[index]
var button := TextureButton.new()
button.name = "Menu%s" % action.capitalize()
button.texture_normal = _textures.get(action, _textures.get("plant"))
button.size = _button_size
button.pivot_offset = _button_size * 0.5
button.focus_mode = Control.FOCUS_NONE
button.set_meta("action", action)
button.pressed.connect(func() -> void: _handle_action(action))
button.button_down.connect(func() -> void: UiMotionScript.play_press_scale(_owner, button))
button.button_up.connect(func() -> void: UiMotionScript.play_release_scale(_owner, button))
var radians := deg_to_rad(angle_start + float(index) * angle_step)
var button_center := center + Vector2(cos(radians), sin(radians)) * radius
var final_position := button_center - _button_size * 0.5
button.position = center - _button_size * 0.5
button.scale = Vector2(0.18, 0.18)
button.modulate = Color(1.0, 1.0, 1.0, 0.0)
_layer.add_child(button)
var tween := _owner.create_tween().set_parallel(true)
var delay := float(index) * 0.035
tween.tween_property(button, "position", final_position, 0.2).set_delay(delay).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT)
tween.tween_property(button, "scale", Vector2.ONE, 0.2).set_delay(delay).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT)
tween.tween_property(button, "modulate:a", 1.0, 0.12).set_delay(delay)
_layer.visible = true
func hide(animated := false) -> void:
if _layer == null or not _layer.visible:
return
if _hide_tween != null:
_hide_tween.kill()
if animated:
for child in _layer.get_children():
if child is TextureButton:
child.disabled = true
var tween := _owner.create_tween().set_parallel(true)
tween.tween_property(child, "position", _origin - _button_size * 0.5, 0.18).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_IN)
tween.tween_property(child, "scale", Vector2(0.18, 0.18), 0.18).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_IN)
tween.tween_property(child, "modulate:a", 0.0, 0.12)
_hide_tween = _owner.create_tween()
_hide_tween.tween_interval(0.19)
_hide_tween.tween_callback(_finish_hide)
return
_finish_hide()
func is_visible() -> bool:
return _layer != null and _layer.visible
func handle_direct_hit(screen_position: Vector2) -> bool:
if _layer == null or not _layer.visible:
return false
for child in _layer.get_children():
var button := child as TextureButton
if button == null or button.disabled or not button.is_visible_in_tree():
continue
if not button.get_global_rect().grow(DIRECT_HIT_GROW).has_point(screen_position):
continue
UiMotionScript.play_click_pulse(_owner, button)
_handle_action(str(button.get_meta("action", "")))
return true
_handle_dismiss()
return true
func layer() -> Control:
return _layer
func _create_layer() -> void:
if _owner == null:
return
_layer = Control.new()
_layer.name = "CoolMenuLayer"
_layer.mouse_filter = Control.MOUSE_FILTER_STOP
_layer.set_anchors_preset(Control.PRESET_FULL_RECT)
_layer.visible = false
_owner.add_child(_layer)
_dismiss_button = Button.new()
_dismiss_button.name = "MenuDismissHit"
_dismiss_button.set_anchors_preset(Control.PRESET_FULL_RECT)
_dismiss_button.focus_mode = Control.FOCUS_NONE
_dismiss_button.text = ""
UiMotionScript.apply_empty_button_style(_dismiss_button)
_dismiss_button.pressed.connect(_handle_dismiss)
func _handle_action(action: String) -> void:
if _on_action.is_valid():
_on_action.call(action)
func _handle_dismiss() -> void:
if _on_dismiss.is_valid():
_on_dismiss.call()
func _finish_hide() -> void:
if _layer != null:
_layer.visible = false
_clear_children(_layer)
func _get_land_node_for(position: int) -> Control:
if not _get_land_node.is_valid():
return null
return _get_land_node.call(position) as Control
func _land_anchor_in_layer(land_node: Control) -> Vector2:
var global_rect := land_node.get_global_rect()
var global_anchor := global_rect.position + global_rect.size * 0.5 + Vector2(0.0, -25.0)
return _layer.get_global_transform().affine_inverse() * global_anchor
func _clear_children(parent: Node) -> void:
if parent == null:
return
for child in parent.get_children():
if child == _dismiss_button:
parent.remove_child(child)
continue
parent.remove_child(child)
child.queue_free()