Bugbot/UI/ControllerInput/controller_input_symbol_wheel.gd
Jamie Greunbaum 3073485e92 Symbol wheel is much less likely to input incorrect characters
Input detection now has a buffer zone, where selections are only detected at
the extreme edges of the joystick range, but are deselected only after moving
through a larger range toward the centre. This creates a buffer zone where
selections are unlikely to happen by accident due to worn-out or limited
joystick ranges around the outside edge, and deselections are unlikely to
happen until the user actually releases the joystick. Phantom inputs are now
seemingly entirely eliminated.
2024-06-23 00:27:26 -04:00

191 lines
6.7 KiB
GDScript

@tool
class_name ControllerInputSymbolWheel
extends Control
@export() var wheel_characters : PackedStringArray = [] : set = __set_wheel_characters
@export_range(0.0, 10.0, 0.01) var unselected_radius : float = 1.5 : set = __set_unselected_radius
@export_range(0.0, 10.0, 0.01) var selected_radius : float = 1.5 : set = __set_selected_radius
@export_range(0.0, 1.0, 0.01) var font_scale : float = 0.45 : set = __set_font_scale
@export var unselected_scale : Vector2 = Vector2(0.5, 0.5) : set = __set_unselected_scale
@export var selected_scale : Vector2 = Vector2(1.0, 1.0) : set = __set_selected_scale
@export_range(0.0, 1.0, 0.01) var tween_speed : float = 0.15
@export_group("Inputs")
@export_subgroup("Joypad", "joypad_")
@export var joypad_symbol_up : InputEventJoypadMotion
@export var joypad_symbol_down : InputEventJoypadMotion
@export var joypad_symbol_left : InputEventJoypadMotion
@export var joypad_symbol_right : InputEventJoypadMotion
signal new_character(char:String)
var __symbol_labels : Array = []
var __selected_label : int = -1
var __selected : bool = false
func _init() -> void:
if not resized.is_connected(__spin_labels_around_centre):
resized.connect(__spin_labels_around_centre)
func _enter_tree() -> void:
__regenerate_labels()
func _exit_tree() -> void:
if resized.is_connected(__spin_labels_around_centre):
resized.disconnect(__spin_labels_around_centre)
func _input(event:InputEvent) -> void:
if not __selected: return
var symbols_count : int = __symbol_labels.size()
var index : int = __selected_label
var right_stick_vector : Vector2 = Input.get_vector(&"select_symbol_left", &"select_symbol_right", &"select_symbol_up", &"select_symbol_down")
if right_stick_vector.length() >= 0.95:
var stick_angle : float = ((Vector2.UP).rotated(right_stick_vector.angle() + (TAU / (symbols_count + symbols_count))).angle() + PI) / TAU
index = clamp(int(floor(stick_angle * symbols_count)), 0, symbols_count - 1)
elif right_stick_vector.length() < 0.8:
index = -1
__new_symbol_selection(index)
func select() -> void:
if __selected: return
__selected = true
__spin_labels_around_centre()
__initialise_input()
func deselect() -> void:
if not __selected: return
__selected = false
if __selected_label >= 0:
__deselect_symbol(__selected_label)
__selected_label = -1
__uninitialise_input()
__spin_labels_around_centre()
func __new_symbol_selection(new_selection:int) -> void:
if __selected_label == new_selection: return
if new_selection < 0:
var send_char : String = wheel_characters[__selected_label]
if not send_char.is_empty():
if send_char == "":
new_character.emit(" ")
else:
new_character.emit(send_char)
if __selected_label >= 0:
__deselect_symbol(__selected_label)
__selected_label = new_selection
if __selected_label >= 0:
__select_symbol(__selected_label)
func __select_symbol(selection:int) -> void:
var label : Label = __symbol_labels[selection]
label.z_index = 1
var label_tween_activate : Tween = get_tree().create_tween()
label_tween_activate.bind_node(label)
label_tween_activate.tween_property(label, "scale", selected_scale, tween_speed)
var position : Vector2 = __calculate_label_position(label, selection, selected_radius)
var label_tween_radius : Tween = get_tree().create_tween()
label_tween_radius.bind_node(label)
label_tween_radius.tween_property(label, "position", position, tween_speed)
func __deselect_symbol(selection:int) -> void:
var label : Label = __symbol_labels[selection]
label.z_index = 0
var label_tween_deactivate : Tween = get_tree().create_tween()
label_tween_deactivate.bind_node(label)
label_tween_deactivate.tween_property(label, "scale", unselected_scale, tween_speed)
var position : Vector2 = __calculate_label_position(label, selection, unselected_radius)
var label_tween_radius : Tween = get_tree().create_tween()
label_tween_radius.bind_node(label)
label_tween_radius.tween_property(label, "position", position, tween_speed)
func __calculate_label_position(label:Label, label_index:int, radius:float) -> Vector2:
var angle_slice : float = PI / float(__symbol_labels.size()) * 2.0
var min_size : int = min(size.x, size.y)
var max_label_size : int = max(label.size.x, label.size.y)
label.pivot_offset = label.size / 2.0
var position : Vector2 = (size / 2.0) - label.size / 2.0
position += (-Vector2.UP * radius).rotated(angle_slice * label_index) * ((min_size - max_label_size) / 3.0)
return position
func __regenerate_labels() -> void:
for i:Label in __symbol_labels:
i.queue_free()
__symbol_labels = []
for char:String in wheel_characters:
var label : Label = Label.new()
add_child(label)
label.text = char
label.horizontal_alignment = HorizontalAlignment.HORIZONTAL_ALIGNMENT_CENTER
label.vertical_alignment = VerticalAlignment.VERTICAL_ALIGNMENT_CENTER
label.size = Vector2(20.0, 20.0)
label.pivot_offset = label.size / 2.0
__symbol_labels.append(label)
__spin_labels_around_centre()
func __spin_labels_around_centre() -> void:
for i:int in range(__symbol_labels.size()):
var label : Label = __symbol_labels[i]
var max_label_size : int = max(label.size.x, label.size.y)
var radius : float = selected_radius if i == __selected_label else unselected_radius
label.scale = selected_scale if i == __selected_label else unselected_scale
label.position = __calculate_label_position(label, i, radius)
func __initialise_input() -> void:
InputMap.add_action(&"select_symbol_up", 0.0)
InputMap.action_add_event(&"select_symbol_up", joypad_symbol_up)
InputMap.add_action(&"select_symbol_down", 0.0)
InputMap.action_add_event(&"select_symbol_down", joypad_symbol_down)
InputMap.add_action(&"select_symbol_left", 0.0)
InputMap.action_add_event(&"select_symbol_left", joypad_symbol_left)
InputMap.add_action(&"select_symbol_right", 0.0)
InputMap.action_add_event(&"select_symbol_right", joypad_symbol_right)
func __uninitialise_input() -> void:
InputMap.action_erase_event(&"select_symbol_right", joypad_symbol_right)
InputMap.erase_action(&"select_symbol_right")
InputMap.action_erase_event(&"select_symbol_left", joypad_symbol_left)
InputMap.erase_action(&"select_symbol_left")
InputMap.action_erase_event(&"select_symbol_down", joypad_symbol_down)
InputMap.erase_action(&"select_symbol_down")
InputMap.action_erase_event(&"select_symbol_up", joypad_symbol_up)
InputMap.erase_action(&"select_symbol_up")
func __set_wheel_characters(characters:PackedStringArray) -> void:
wheel_characters = characters
__regenerate_labels()
func __set_selected_radius(r:float) -> void:
selected_radius = r
func __set_unselected_radius(r:float) -> void:
unselected_radius = r
func __set_font_scale(s:float) -> void:
font_scale = s
func __set_unselected_scale(s:Vector2) -> void:
unselected_scale = s
func __set_selected_scale(s:Vector2) -> void:
selected_scale = s