extends Node2D signal selected(id : int) @export var actions : Array[Texture] = [] @export_subgroup("Topology") @export var icons_scale : float = 1. @export var radius : float = 128. @export var thickness : float = 32. @export var angle_offset : float = PI/4. @export var background_color : Color = Color(1., 1., 1., 0.5) @export var selection_color : Color = Color.WHITE @export var always_display : bool = true @export_subgroup("Unfold", "unfold_") @export var unfold_time : float = 0.1 @export var unfold_delay : float = 0.03 @export var unfold_ease_type : Tween.EaseType = Tween.EASE_IN_OUT @export var unfold_transition_type : Tween.TransitionType = Tween.TRANS_BOUNCE @export_subgroup("Fold", "fold_") @export var fold_time : float = 0.1 @export var fold_delay : float = 0.01 @export var fold_ease_type : Tween.EaseType = Tween.EASE_IN_OUT @export var fold_transition_type : Tween.TransitionType = Tween.TRANS_BOUNCE @export_subgroup("Selection") @export var shake_amplitude : float = PI / 4. @export var shake_speed : float = 0.1 @onready var icons : Array[Sprite2D] = [] @onready var active_icon : int = -1 @onready var time : float = 0. @onready var next_shake : float func _ready() -> void: for i in range(actions.size()): var texture : Texture = actions[i] var s : Sprite2D = Sprite2D.new() s.name = "action_%d" % (i) s.scale = Vector2.ZERO s.position = Vector2.ZERO s.texture = texture icons.append(s) add_child(s) visible = false func set_active(pos : Vector2) -> void: if not visible: return var found : bool = false for i in range(icons.size()): var s : Sprite2D = icons[i] var r : Rect2 = s.get_rect() var c : Vector2 = r.get_center() var ns : Vector2 = r.size * s.scale var np : Vector2 = c - (ns / 2.) r.position = np r.size = ns r.position += position + s.position if r.has_point(pos): if active_icon != -1 and active_icon != i: icons[active_icon].rotation = 0. if active_icon != i: next_shake = time - (shake_speed * 2.) queue_redraw() active_icon = i found = true break if not (found or active_icon == -1): icons[active_icon].rotation = 0. active_icon = -1 queue_redraw() func _draw() -> void: if always_display or active_icon != -1: draw_circle(Vector2.ZERO, radius, background_color, false, thickness, true) if active_icon != -1: var angle_step : float = (2. * PI) / actions.size() var start_angle : float = angle_offset + (active_icon * angle_step) - angle_step / 2. var end_angle : float = start_angle + angle_step draw_arc(Vector2.ZERO, radius, start_angle, end_angle, 16, selection_color, thickness, true) func _process(delta : float) -> void: time += delta if visible and active_icon != -1 and time > next_shake: var s : Sprite2D = icons[active_icon] while next_shake < time: next_shake += shake_speed var rot : float = randf() * shake_amplitude * (-1. if sign(s.rotation) > 0. else 1.) s.rotation = rot func unfold() -> void: if not (icons.is_empty() or visible): visible = true var final_scale : Vector2 = Vector2.ONE * icons_scale var angle_step : float = (2. * PI) / actions.size() var tween : Tween = get_tree().create_tween() for i in range(icons.size()): var s_delay : float = i * unfold_delay var angle : float = angle_offset + i * angle_step var final_position : Vector2 = Vector2(cos(angle), sin(angle)) * radius var sprite : Sprite2D = icons[i] tween.parallel().tween_property(sprite, "position", final_position, unfold_time).set_delay(s_delay).set_ease(unfold_ease_type).set_trans(unfold_transition_type) tween.parallel().tween_property(sprite, "scale", final_scale, unfold_time).set_delay(s_delay).set_ease(unfold_ease_type).set_trans(unfold_transition_type) tween.tween_callback(queue_redraw) func fold() -> void: var selected_icon : int = active_icon active_icon = -1 queue_redraw() if (not icons.is_empty()) and visible: var tween : Tween = get_tree().create_tween().parallel() var s_delay : float = 0. for s in icons: tween.parallel().tween_property(s, "position", Vector2.ZERO, fold_time).set_delay(s_delay).set_ease(fold_ease_type).set_trans(fold_transition_type) tween.parallel().tween_property(s, "scale", Vector2.ZERO, fold_time).set_delay(s_delay).set_ease(fold_ease_type).set_trans(fold_transition_type) s_delay += fold_delay tween.tween_callback(set_visible.bind(false)) if selected_icon != -1: tween.tween_callback(selected.emit.bind(selected_icon))
or share this direct link: