@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 = -1 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_squared() - 0.01 > 0.0: 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)) if index != __selected_wheel: var old_selected_wheel : int = __selected_wheel __selected_wheel = index if old_selected_wheel >= 0: __deselect_wheel(old_selected_wheel) if __selected_wheel >= 0: __select_wheel(__selected_wheel) 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 __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)