generated from mstar/godot-template
159 lines
4 KiB
GDScript
159 lines
4 KiB
GDScript
@tool
|
|
extends Control
|
|
|
|
signal item_selected(index)
|
|
signal item_focused(index)
|
|
signal item_cancelled()
|
|
|
|
const button_margin = 6
|
|
|
|
@export var items := [] : set = set_items
|
|
@export var selected_index = -1 : set = set_selected_index
|
|
@export var radius = 100.0 : set = set_radius
|
|
|
|
var buttons = []
|
|
var pie_menus = []
|
|
|
|
var focused_index = -1
|
|
var theme_source_node = self : set = set_theme_source_node
|
|
var grow_with_max_button_width = false
|
|
|
|
|
|
func _ready():
|
|
set_items(items)
|
|
set_selected_index(selected_index)
|
|
set_radius(radius)
|
|
hide()
|
|
connect("visibility_changed", _on_visiblity_changed)
|
|
|
|
func _input(event):
|
|
if visible:
|
|
if event is InputEventKey:
|
|
if event.pressed:
|
|
match event.keycode:
|
|
KEY_ESCAPE:
|
|
cancel()
|
|
if event is InputEventMouseMotion:
|
|
focus_item()
|
|
get_viewport().set_input_as_handled()
|
|
if event is InputEventMouseButton:
|
|
if event.pressed:
|
|
match event.button_index:
|
|
MOUSE_BUTTON_LEFT:
|
|
select_item(focused_index)
|
|
get_viewport().set_input_as_handled()
|
|
MOUSE_BUTTON_RIGHT:
|
|
cancel()
|
|
get_viewport().set_input_as_handled()
|
|
|
|
func _on_visiblity_changed():
|
|
if not visible:
|
|
if selected_index != focused_index: # Cancellation
|
|
focused_index = selected_index
|
|
|
|
func cancel():
|
|
hide()
|
|
get_viewport().set_input_as_handled()
|
|
emit_signal("item_cancelled")
|
|
|
|
func select_item(index):
|
|
set_button_style(selected_index, "normal", "normal")
|
|
selected_index = index
|
|
focused_index = selected_index
|
|
hide()
|
|
emit_signal("item_selected", selected_index)
|
|
|
|
func focus_item():
|
|
queue_redraw()
|
|
var pos = get_global_mouse_position()
|
|
var count = max(buttons.size(), 1)
|
|
var angle_offset = 2 * PI / count
|
|
var angle = pos.angle_to_point(global_position) + PI / 2 # -90 deg initial offset
|
|
if angle < 0:
|
|
angle += 2 * PI
|
|
|
|
var index = (angle / angle_offset)
|
|
var decimal = index - floor(index)
|
|
index = floor(index)
|
|
if decimal >= 0.5:
|
|
index += 1
|
|
if index > buttons.size()-1:
|
|
index = 0
|
|
|
|
set_button_style(focused_index, "normal", "normal")
|
|
focused_index = index
|
|
set_button_style(focused_index, "normal", "hover")
|
|
set_button_style(selected_index, "normal", "focus")
|
|
emit_signal("item_focused", focused_index)
|
|
|
|
func popup(pos):
|
|
global_position = pos
|
|
show()
|
|
|
|
func populate_menu():
|
|
clear_menu()
|
|
buttons = []
|
|
for i in items.size():
|
|
var item = items[i]
|
|
var is_array = item is Array
|
|
var name = item if not is_array else item[0]
|
|
var value = null if not is_array else item[1]
|
|
var button = Button.new()
|
|
button.grow_horizontal = Control.GROW_DIRECTION_BOTH
|
|
button.text = name
|
|
if value != null:
|
|
button.set_meta("value", value)
|
|
buttons.append(button)
|
|
set_button_style(i, "hover", "hover")
|
|
set_button_style(i, "pressed", "pressed")
|
|
set_button_style(i, "focus", "focus")
|
|
set_button_style(i, "disabled", "disabled")
|
|
set_button_style(i, "normal", "normal")
|
|
add_child(button)
|
|
align()
|
|
|
|
set_button_style(selected_index, "normal", "focus")
|
|
|
|
func align():
|
|
var final_radius = radius
|
|
if grow_with_max_button_width:
|
|
var max_button_width = 0.0
|
|
for button in buttons:
|
|
max_button_width = max(max_button_width, button.size.x)
|
|
final_radius = max(radius, max_button_width)
|
|
var count = max(buttons.size(), 1)
|
|
var angle_offset = 2 * PI / count
|
|
var angle = PI / 2 # 90 deg initial offset
|
|
for button in buttons:
|
|
button.position = Vector2(final_radius, 0.0).rotated(angle) - (button.size / 2.0)
|
|
angle += angle_offset
|
|
|
|
func clear_menu():
|
|
for button in buttons:
|
|
button.queue_free()
|
|
|
|
func set_button_style(index, name, source):
|
|
if index < 0 or index > buttons.size() - 1:
|
|
return
|
|
|
|
buttons[index].set("theme_override_styles/%s" % name, get_theme_stylebox(source, "Button"))
|
|
|
|
func set_items(v):
|
|
items = v
|
|
if is_inside_tree():
|
|
populate_menu()
|
|
|
|
func set_selected_index(v):
|
|
set_button_style(selected_index, "normal", "normal")
|
|
selected_index = v
|
|
set_button_style(selected_index, "normal", "focus")
|
|
|
|
func set_radius(v):
|
|
radius = v
|
|
align()
|
|
|
|
func set_theme_source_node(v):
|
|
theme_source_node = v
|
|
for pie_menu in pie_menus:
|
|
if pie_menu:
|
|
pie_menu.theme_source_node = theme_source_node
|