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.
360 lines
14 KiB
GDScript
360 lines
14 KiB
GDScript
@tool
|
|
class_name ControllerInputSelectWheel
|
|
extends Control
|
|
|
|
@export var wheel_data_pack : ControllerInputWheelDataPack : set = __set_wheel_data_pack
|
|
|
|
@export_range(0.0, 2.0, 0.01) var unselected_radius : float = 1.5 : set = __set_unselected_radius
|
|
@export_range(0.0, 2.0, 0.01) var selected_radius : float = 1.5 : set = __set_selected_radius
|
|
|
|
@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_range(0.0, 1.0, 0.01) var caret_move_delay : float = 0.25
|
|
@export_range(0.0, 1.0, 0.01) var caret_move_repeat_delay : float = 0.1
|
|
|
|
@export_group("Inputs")
|
|
@export_subgroup("Joypad", "joypad_")
|
|
@export var joypad_wheel_up : InputEventJoypadMotion
|
|
@export var joypad_wheel_down : InputEventJoypadMotion
|
|
@export var joypad_wheel_left : InputEventJoypadMotion
|
|
@export var joypad_wheel_right : InputEventJoypadMotion
|
|
|
|
@export var joypad_caret_up : InputEventJoypadButton
|
|
@export var joypad_caret_down : InputEventJoypadButton
|
|
@export var joypad_caret_left : InputEventJoypadButton
|
|
@export var joypad_caret_right : InputEventJoypadButton
|
|
|
|
@export var joypad_shift : InputEventJoypadButton
|
|
@export var joypad_backspace : InputEventJoypadButton
|
|
@export var joypad_delete : InputEventJoypadButton
|
|
|
|
@onready var caret_horizontal_move_timer : Timer = $CaretHorizontalMoveTimer
|
|
@onready var caret_vertical_move_timer : Timer = $CaretVerticalMoveTimer
|
|
@onready var caret_remove_text_timer : Timer = $CaretRemoveTextTimer
|
|
|
|
signal new_character(char:String)
|
|
signal move_caret_up
|
|
signal move_caret_down
|
|
signal move_caret_left
|
|
signal move_caret_right
|
|
signal backspace
|
|
signal delete
|
|
|
|
var __wheel_data : ControllerInputWheelData = null
|
|
var __wheels : Array = []
|
|
var __selected_wheel : int = -1
|
|
|
|
var __symbol_wheel : PackedScene = preload("res://addons/Bugbot/UI/ControllerInput/controller_input_symbol_wheel.tscn")
|
|
|
|
|
|
func _init() -> void:
|
|
resized.connect(__spin_wheels_around_centre)
|
|
|
|
func _enter_tree() -> void:
|
|
__initialise_input()
|
|
__regenerate_wheels()
|
|
|
|
func _exit_tree() -> void:
|
|
__uninitialise_input()
|
|
|
|
func _input(event:InputEvent) -> void:
|
|
if event.is_action_pressed(&"select_wheel_shift"):
|
|
__wheel_data = wheel_data_pack.uppercase
|
|
__regenerate_wheels()
|
|
elif event.is_action_released(&"select_wheel_shift"):
|
|
__wheel_data = wheel_data_pack.lowercase
|
|
__regenerate_wheels()
|
|
|
|
__move_caret(event)
|
|
|
|
__backspace_caret(event)
|
|
__delete_caret(event)
|
|
|
|
var wheels_count : int = __wheels.size()
|
|
|
|
var index : int = __selected_wheel
|
|
var left_stick_vector : Vector2 = Input.get_vector(&"select_wheel_left", &"select_wheel_right", &"select_wheel_up", &"select_wheel_down", 0.95)
|
|
if left_stick_vector.length() >= 0.95:
|
|
var stick_angle : float = ((Vector2.UP).rotated(left_stick_vector.angle() + (TAU / wheels_count / 2.0)).angle() + PI) / TAU
|
|
index = int(floor(stick_angle * wheels_count))
|
|
elif left_stick_vector.length() < 0.8:
|
|
index = -1
|
|
|
|
__new_wheel_selection(index)
|
|
|
|
|
|
func __select_wheel(selection:int) -> void:
|
|
var wheel : ControllerInputSymbolWheel = __wheels[selection]
|
|
|
|
wheel.z_index = 1
|
|
wheel.new_character.connect(__print_character)
|
|
|
|
var wheel_tween_activate : Tween = get_tree().create_tween()
|
|
wheel_tween_activate.bind_node(wheel)
|
|
wheel_tween_activate.tween_property(wheel, "scale", __calculate_wheel_scale(selection), tween_speed)
|
|
|
|
var position : Vector2 = __calculate_wheel_position(wheel, selection, selected_radius)
|
|
var wheel_tween_radius : Tween = get_tree().create_tween()
|
|
wheel_tween_radius.bind_node(wheel)
|
|
wheel_tween_radius.tween_property(wheel, "position", position, tween_speed)
|
|
|
|
wheel.select()
|
|
|
|
func __deselect_wheel(deselection:int) -> void:
|
|
var wheel : ControllerInputSymbolWheel = __wheels[deselection]
|
|
|
|
wheel.z_index = 0
|
|
wheel.new_character.disconnect(__print_character)
|
|
|
|
var wheel_tween_deactivate : Tween = get_tree().create_tween()
|
|
wheel_tween_deactivate.bind_node(wheel)
|
|
wheel_tween_deactivate.tween_property(wheel, "scale", __calculate_wheel_scale(deselection), tween_speed)
|
|
|
|
var position : Vector2 = __calculate_wheel_position(wheel, deselection, unselected_radius)
|
|
var wheel_tween_radius : Tween = get_tree().create_tween()
|
|
wheel_tween_radius.bind_node(wheel)
|
|
wheel_tween_radius.tween_property(wheel, "position", position, tween_speed)
|
|
|
|
wheel.deselect()
|
|
|
|
|
|
func __new_wheel_selection(new_selection:int) -> void:
|
|
if __selected_wheel == new_selection: return
|
|
|
|
var old_selected_wheel : int = __selected_wheel
|
|
__selected_wheel = new_selection
|
|
|
|
if old_selected_wheel >= 0:
|
|
__deselect_wheel(old_selected_wheel)
|
|
|
|
if __selected_wheel >= 0:
|
|
__select_wheel(__selected_wheel)
|
|
|
|
func __print_character(char:String) -> void:
|
|
new_character.emit(char)
|
|
|
|
|
|
func __regenerate_wheels() -> void:
|
|
for i:ControllerInputSymbolWheel in __wheels:
|
|
i.deselect()
|
|
i.queue_free()
|
|
__wheels.clear()
|
|
|
|
if not __wheel_data:
|
|
if not wheel_data_pack.lowercase: return
|
|
__wheel_data = wheel_data_pack.lowercase
|
|
|
|
for character_group:PackedStringArray in __wheel_data.characters:
|
|
var wheel : ControllerInputSymbolWheel = __symbol_wheel.instantiate()
|
|
wheel.wheel_characters = character_group
|
|
if __selected_wheel == __wheels.size():
|
|
wheel.select()
|
|
wheel.new_character.connect(__print_character)
|
|
add_child(wheel)
|
|
__wheels.append(wheel)
|
|
|
|
__spin_wheels_around_centre()
|
|
|
|
func __spin_wheels_around_centre() -> void:
|
|
if not is_inside_tree(): return
|
|
|
|
for i:int in range(__wheels.size()):
|
|
var wheel : ControllerInputSymbolWheel = __wheels[i]
|
|
var radius : float = selected_radius if __selected_wheel == i else unselected_radius
|
|
wheel.scale = __calculate_wheel_scale(i)
|
|
wheel.position = __calculate_wheel_position(wheel, i, radius)
|
|
|
|
func __calculate_wheel_position(wheel:ControllerInputSymbolWheel, wheel_index:int, radius:float) -> Vector2:
|
|
var angle_slice : float = PI / float(__wheels.size()) * 2.0
|
|
var min_size : int = min(size.x, size.y)
|
|
var max_wheel_size : int = max(wheel.size.x, wheel.size.y)
|
|
var position : Vector2 = (size / 2.0) - wheel.size / 2.0
|
|
position += (Vector2.UP * radius).rotated(angle_slice * wheel_index) * ((min_size - max_wheel_size) / 3.0)
|
|
return position
|
|
|
|
func __calculate_wheel_scale(wheel_index:int) -> Vector2:
|
|
return (selected_scale if wheel_index == __selected_wheel else unselected_scale) * __get_proportional_scale()
|
|
|
|
func __refresh_scales() -> void:
|
|
for i:int in range(__wheels.size()):
|
|
var wheel : ControllerInputSymbolWheel = __wheels[i]
|
|
wheel.scale = __calculate_wheel_scale(i)
|
|
|
|
|
|
func __initialise_input() -> void:
|
|
InputMap.add_action(&"select_wheel_up", 0.75)
|
|
InputMap.action_add_event(&"select_wheel_up", joypad_wheel_up)
|
|
InputMap.add_action(&"select_wheel_down", 0.75)
|
|
InputMap.action_add_event(&"select_wheel_down", joypad_wheel_down)
|
|
InputMap.add_action(&"select_wheel_left", 0.75)
|
|
InputMap.action_add_event(&"select_wheel_left", joypad_wheel_left)
|
|
InputMap.add_action(&"select_wheel_right", 0.75)
|
|
InputMap.action_add_event(&"select_wheel_right", joypad_wheel_right)
|
|
|
|
InputMap.add_action(&"select_wheel_caret_up", 0.75)
|
|
InputMap.action_add_event(&"select_wheel_caret_up", joypad_caret_up)
|
|
InputMap.add_action(&"select_wheel_caret_down", 0.75)
|
|
InputMap.action_add_event(&"select_wheel_caret_down", joypad_caret_down)
|
|
InputMap.add_action(&"select_wheel_caret_left", 0.75)
|
|
InputMap.action_add_event(&"select_wheel_caret_left", joypad_caret_left)
|
|
InputMap.add_action(&"select_wheel_caret_right", 0.75)
|
|
InputMap.action_add_event(&"select_wheel_caret_right", joypad_caret_right)
|
|
|
|
InputMap.add_action(&"select_wheel_shift", 0.5)
|
|
InputMap.action_add_event(&"select_wheel_shift", joypad_shift)
|
|
|
|
InputMap.add_action(&"select_wheel_backspace", 0.5)
|
|
InputMap.action_add_event(&"select_wheel_backspace", joypad_backspace)
|
|
|
|
InputMap.add_action(&"select_wheel_delete", 0.5)
|
|
InputMap.action_add_event(&"select_wheel_delete", joypad_delete)
|
|
|
|
func __uninitialise_input() -> void:
|
|
InputMap.action_erase_event(&"select_wheel_delete", joypad_delete)
|
|
InputMap.erase_action(&"select_wheel_delete")
|
|
|
|
InputMap.action_erase_event(&"select_wheel_backspace", joypad_backspace)
|
|
InputMap.erase_action(&"select_wheel_backspace")
|
|
|
|
InputMap.action_erase_event(&"select_wheel_shift", joypad_shift)
|
|
InputMap.erase_action(&"select_wheel_shift")
|
|
|
|
InputMap.action_erase_event(&"select_wheel_caret_up", joypad_caret_up)
|
|
InputMap.erase_action(&"select_wheel_caret_up")
|
|
InputMap.action_erase_event(&"select_wheel_caret_down", joypad_caret_down)
|
|
InputMap.erase_action(&"select_wheel_caret_down")
|
|
InputMap.action_erase_event(&"select_wheel_caret_left", joypad_caret_left)
|
|
InputMap.erase_action(&"select_wheel_caret_left")
|
|
InputMap.action_erase_event(&"select_wheel_caret_right", joypad_caret_right)
|
|
InputMap.erase_action(&"select_wheel_caret_right")
|
|
|
|
InputMap.action_erase_event(&"select_wheel_right", joypad_wheel_right)
|
|
InputMap.erase_action(&"select_wheel_right")
|
|
InputMap.action_erase_event(&"select_wheel_left", joypad_wheel_left)
|
|
InputMap.erase_action(&"select_wheel_left")
|
|
InputMap.action_erase_event(&"select_wheel_down", joypad_wheel_down)
|
|
InputMap.erase_action(&"select_wheel_down")
|
|
InputMap.action_erase_event(&"select_wheel_up", joypad_wheel_up)
|
|
InputMap.erase_action(&"select_wheel_up")
|
|
|
|
func __move_caret(event:InputEvent) -> void:
|
|
if event.is_action_pressed(&"select_wheel_caret_up"):
|
|
__clear_vertical_move_caret_timeouts()
|
|
move_caret_up.emit()
|
|
caret_vertical_move_timer.timeout.connect(__move_caret_up_timeout)
|
|
caret_vertical_move_timer.start(caret_move_delay)
|
|
elif event.is_action_released(&"select_wheel_caret_up"):
|
|
__clear_vertical_move_caret_timeouts()
|
|
|
|
if event.is_action_pressed(&"select_wheel_caret_down"):
|
|
__clear_vertical_move_caret_timeouts()
|
|
move_caret_down.emit()
|
|
caret_vertical_move_timer.timeout.connect(__move_caret_down_timeout)
|
|
caret_vertical_move_timer.start(caret_move_delay)
|
|
elif event.is_action_released(&"select_wheel_caret_down"):
|
|
__clear_vertical_move_caret_timeouts()
|
|
|
|
if event.is_action_pressed(&"select_wheel_caret_left"):
|
|
__clear_horizontal_move_caret_timeouts()
|
|
move_caret_left.emit()
|
|
caret_horizontal_move_timer.timeout.connect(__move_caret_left_timeout)
|
|
caret_horizontal_move_timer.start(caret_move_delay)
|
|
elif event.is_action_released(&"select_wheel_caret_left"):
|
|
__clear_horizontal_move_caret_timeouts()
|
|
|
|
if event.is_action_pressed(&"select_wheel_caret_right"):
|
|
__clear_horizontal_move_caret_timeouts()
|
|
move_caret_right.emit()
|
|
caret_horizontal_move_timer.timeout.connect(__move_caret_right_timeout)
|
|
caret_horizontal_move_timer.start(caret_move_delay)
|
|
elif event.is_action_released(&"select_wheel_caret_right"):
|
|
__clear_horizontal_move_caret_timeouts()
|
|
|
|
func __move_caret_up_timeout() -> void:
|
|
move_caret_up.emit()
|
|
caret_vertical_move_timer.start(caret_move_repeat_delay)
|
|
func __move_caret_down_timeout() -> void:
|
|
move_caret_down.emit()
|
|
caret_vertical_move_timer.start(caret_move_repeat_delay)
|
|
func __move_caret_left_timeout() -> void:
|
|
move_caret_left.emit()
|
|
caret_horizontal_move_timer.start(caret_move_repeat_delay)
|
|
func __move_caret_right_timeout() -> void:
|
|
move_caret_right.emit()
|
|
caret_horizontal_move_timer.start(caret_move_repeat_delay)
|
|
|
|
func __clear_horizontal_move_caret_timeouts() -> void:
|
|
caret_horizontal_move_timer.stop()
|
|
var connections : Array = caret_horizontal_move_timer.timeout.get_connections()
|
|
for c:Dictionary in connections:
|
|
caret_horizontal_move_timer.timeout.disconnect(c["callable"])
|
|
func __clear_vertical_move_caret_timeouts() -> void:
|
|
caret_vertical_move_timer.stop()
|
|
var connections : Array = caret_vertical_move_timer.timeout.get_connections()
|
|
for c:Dictionary in connections:
|
|
caret_vertical_move_timer.timeout.disconnect(c["callable"])
|
|
|
|
|
|
func __backspace_caret(event:InputEvent) -> void:
|
|
if event.is_action_pressed(&"select_wheel_backspace"):
|
|
__clear_backspace_caret_timeouts()
|
|
backspace.emit()
|
|
caret_remove_text_timer.timeout.connect(__backspace_caret_timeout)
|
|
caret_remove_text_timer.start(caret_move_delay)
|
|
elif event.is_action_released(&"select_wheel_backspace"):
|
|
__clear_backspace_caret_timeouts()
|
|
func __backspace_caret_timeout() -> void:
|
|
backspace.emit()
|
|
caret_remove_text_timer.start(caret_move_repeat_delay)
|
|
|
|
func __clear_backspace_caret_timeouts() -> void:
|
|
caret_remove_text_timer.stop()
|
|
var connections : Array = caret_remove_text_timer.timeout.get_connections()
|
|
for c:Dictionary in connections:
|
|
caret_remove_text_timer.timeout.disconnect(c["callable"])
|
|
|
|
|
|
func __delete_caret(event:InputEvent) -> void:
|
|
if event.is_action_pressed(&"select_wheel_delete"):
|
|
__clear_backspace_caret_timeouts()
|
|
delete.emit()
|
|
caret_remove_text_timer.timeout.connect(__delete_caret_timeout)
|
|
caret_remove_text_timer.start(caret_move_delay)
|
|
elif event.is_action_released(&"select_wheel_delete"):
|
|
__clear_backspace_caret_timeouts()
|
|
func __delete_caret_timeout() -> void:
|
|
delete.emit()
|
|
caret_remove_text_timer.start(caret_move_repeat_delay)
|
|
|
|
|
|
func __set_wheel_data_pack(data:ControllerInputWheelDataPack) -> void:
|
|
wheel_data_pack = data
|
|
if __wheel_data == wheel_data_pack.lowercase:
|
|
__regenerate_wheels()
|
|
|
|
func __set_unselected_radius(r:float) -> void:
|
|
unselected_radius = r
|
|
__spin_wheels_around_centre()
|
|
|
|
func __set_selected_radius(r:float) -> void:
|
|
selected_radius = r
|
|
__spin_wheels_around_centre()
|
|
|
|
func __set_unselected_scale(s:Vector2) -> void:
|
|
unselected_scale = s
|
|
__refresh_scales()
|
|
|
|
func __set_selected_scale(s:Vector2) -> void:
|
|
selected_scale = s
|
|
__refresh_scales()
|
|
|
|
|
|
func __get_proportional_scale() -> float:
|
|
var viewport_rect : Vector2 = get_viewport_rect().size
|
|
if Engine.is_editor_hint():
|
|
viewport_rect.x = ProjectSettings.get_setting("display/window/size/viewport_width")
|
|
viewport_rect.y = ProjectSettings.get_setting("display/window/size/viewport_height")
|
|
return min(size.x, size.y) / min(viewport_rect.x, viewport_rect.y)
|