@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