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()