MagicNStuff/source/addons/godot_3d_cursor/pie_menu.gd
SchimmelSpreu83 2a60380258 Added Plugins
- ProtoShape
- StairsCharacter (GDExtension)
- TODO Manager
- 3D Cursor
2025-09-21 23:42:15 +02:00

229 lines
8.0 KiB
GDScript

@tool
class_name PieMenu
extends Control
## Emitted when the "3D Cursor to Origin" command is invoked through the [PieMenu]
signal cursor_to_origin_pressed
## Emitted when the "3D Cursor to Selected Object(s)" command is invoked through the [PieMenu]
signal cursor_to_selected_objects_pressed
## Emitted when the "Selected Object to 3D Cursor" command is invoked through the [PieMenu]
signal selected_object_to_cursor_pressed
## Emitted when the "Remove 3D Cursor from Scene" command is invoked through the [PieMenu]
signal remove_cursor_from_scene_pressed
## Emitted when the "Toggle 3D Cursor" command is invoked through the [PieMenu]
signal toggle_cursor_pressed
## The dimmed color for the selection indicator that is used if no button is hovered
const _dimmed_selection_indicator_color: Color = Color("8c8c8cFF")
## The value at which the buttons start to animate/slide if the menu is shown
var slide_start: int = 0
## The position the buttons animate/slide to
var slide_end: int = 100
## The radius of the menu. The buttons are aligned around an invisible circle
## and this is the corresponding radius.
var menu_radius: int = slide_start
## The buttons that are "loaded"
var buttons: Array[Button] = []
var _hovered_button: Button
var _show_menu_echo: bool = false
@onready var selection_indicator: Sprite2D = $SelectionIndicator
@onready var toggle_3d_cursor: Button = $Toggle3DCursor
func _process(delta: float) -> void:
#var viewport_height: int = get_viewport().size.y
#var viewport_width: int = get_viewport().size.x
# If the menu is shown animate the buttons
if visible:
menu_radius = lerp(menu_radius, slide_end, 20 * delta)
# Reset the button positions when the menu is hidden
if not visible:
menu_radius = slide_start
_show_menu_echo = true
_align_buttons()
# Load all children from the pie menu
var children = get_children()
# Get all the children that are buttons if there are no new button return
if children.filter(_is_button) == buttons:
return
# If there are new buttons repopulate the buttons list and display them
buttons.clear()
for button: Button in children.filter(_is_button):
buttons.append(button)
_align_buttons()
func _input(event: InputEvent) -> void:
if not is_visible_in_tree():
return
# If the [PieMenu] is opened the selection indicator will rotate according
# to the mouse position. If the user hovers over a button the indicator
# will change its color to white and a more dimmed color otherwise
if event is InputEventMouseMotion:
# Calculate the angle of the mouse position to the x axis
var rot: float = rad_to_deg(get_local_mouse_position().angle_to(Vector2.RIGHT)) - 45
# Apply the rotation to the indicator
selection_indicator.rotation_degrees = -rot
# If the key used to open the [PieMenu] is held down while selecting hovering
# over a button the user can invoke the buttons action by releasing the
# key (s). This does not work if the key was released prior to hovering
# over a button. This functionality is similar to the one in Blender
if not event is InputEventKey:
return
if not event.keycode == KEY_S:
return
if event.is_released() and _hovered_button == null:
_show_menu_echo = false
return
if _show_menu_echo and event.is_released():
_hovered_button.pressed.emit()
## This method should be used in conjuncton with a [Array.filter] method.
## It checks whether a node inherits from Button
func _is_button(child: Node) -> bool:
return child is Button
## This method aligns the available buttons in a circular menu by using
## some [sin] and [cos] magic
func _align_buttons() -> void:
var button_count: int = len(buttons)
for i in range(button_count):
var button: Button = buttons[i]
var theta: float = (i / float(button_count)) * TAU
var x: float = (menu_radius * cos(theta))
var y: float = (menu_radius * sin(theta)) - button.size.y / 2.0
x = x - button.size.x if x < 0 else x
button.position = Vector2(x, y)
## Connected to the corresponding UI button this method acts as a repeater
## by emitting the corresponding signal classes can listen to via a [PieMenu]
## instance
func _on_3d_cursor_to_origin_pressed() -> void:
hide()
cursor_to_origin_pressed.emit()
## Executes when the "3D Cursor to Origin" button is hovered
func _on_3d_cursor_to_origin_mouse_entered() -> void:
_hovered_button = $"3DCursorToOrigin"
_on_mouse_entered_button()
## Executes when the "3D Cursor to Origin" button is no longer hovered
func _on_3d_cursor_to_origin_mouse_exited() -> void:
_hovered_button = null
_on_mouse_exited_button()
## Connected to the corresponding UI button this method acts as a repeater
## by emitting the corresponding signal classes can listen to via a [PieMenu]
## instance
func _on_3d_cursor_to_selected_objects_pressed() -> void:
hide()
cursor_to_selected_objects_pressed.emit()
## Executes when the "3D Cursor to Selected Object(s)" button is hovered
func _on_3d_cursor_to_selected_objects_mouse_entered() -> void:
_hovered_button = $"3DCursorToSelectedObjects"
_on_mouse_entered_button()
## Executes when the "3D Cursor to Selected Object(s)" button is no longer hovered
func _on_3d_cursor_to_selected_objects_mouse_exited() -> void:
_hovered_button = null
_on_mouse_exited_button()
## Connected to the corresponding UI button this method acts as a repeater
## by emitting the corresponding signal classes can listen to via a [PieMenu]
## instance
func _on_selected_object_to_3d_cursor_pressed() -> void:
hide()
selected_object_to_cursor_pressed.emit()
## Executes when the "Selected Object to 3D Cursor" button is hovered
func _on_selected_object_to_3d_cursor_mouse_entered() -> void:
_hovered_button = $SelectedObjectTo3DCursor
_on_mouse_entered_button()
## Executes when the "Selected Object to 3D Cursor" button is no longer hovered
func _on_selected_object_to_3d_cursor_mouse_exited() -> void:
_hovered_button = null
_on_mouse_exited_button()
## Connected to the corresponding UI button this method acts as a repeater
## by emitting the corresponding signal classes can listen to via a [PieMenu]
## instance
func _on_remove_3d_cursor_from_scene_pressed() -> void:
hide()
remove_cursor_from_scene_pressed.emit()
## Executes when the "Remove 3D Cursor" button is hovered
func _on_remove_3d_cursor_from_scene_mouse_entered() -> void:
_hovered_button = $Remove3DCursorFromScene
_on_mouse_entered_button()
## Executes when the "Remove 3D Cursor" button is no longer hovered
func _on_remove_3d_cursor_from_scene_mouse_exited() -> void:
_hovered_button = null
_on_mouse_exited_button()
## Connected to the corresponding UI button this method acts as a repeater
## by emitting the corresponding signal classes can listen to via a [PieMenu]
## instance
func _on_toggle_3d_cursor_pressed() -> void:
hide()
toggle_cursor_pressed.emit()
## Executes when the "Disable/Enable 3D Cursor" button is hovered
func _on_toggle_3d_cursor_mouse_entered() -> void:
_hovered_button = toggle_3d_cursor
_on_mouse_entered_button()
## Executes when the "Disable/Enable 3D Cursor" button is hovered
func _on_toggle_3d_cursor_mouse_exited() -> void:
_hovered_button = null
_on_mouse_exited_button()
## This method is executed by every button of the [PieMenu]. It brightens
## the color of the selection indicator when a button is hovered.
func _on_mouse_entered_button() -> void:
selection_indicator.modulate = Color.WHITE
## This method is executed by every button of the [PieMenu]. It dims the color
## of the selection indicator when a button is no longer hovered
func _on_mouse_exited_button() -> void:
selection_indicator.modulate = _dimmed_selection_indicator_color
## This method is a little helper that is used to prevent some quirky behaviour
## with the consumption of events. It checks whether the user clicked on a
## button rather than the space around it
func hit_any_button() -> bool:
var mouse_position: Vector2 = get_global_mouse_position()
for button in buttons:
if button.get_global_rect().has_point(mouse_position):
return true
return false
func change_toggle_label(visible: bool) -> void:
toggle_3d_cursor.text = ("Disable" if visible else "Enable") + " 3D Cursor"