addon for remapping inputs
33 files changed, 739 insertions, 568 deletions
diff --git a/.github/workflows/export.yml b/.github/workflows/export.yml deleted file mode 100644 index 0d33f16..0000000 --- a/.github/workflows/export.yml +++ /dev/null @@ -1,68 +0,0 @@ -name: "export" -on: - workflow_dispatch: - push: - paths: - - "**.gd" - - "**.tscn" - - "**.import" - - "**.tres" - - "**.ttf" - - "**.yml" - branches: - - main - -env: - GODOT_VERSION: 3.5 - NAME: ${{ github.event.repository.name }} - -jobs: - export: - runs-on: ubuntu-latest - container: - image: ghcr.io/bend-n/godot-2d:3.5 - name: ${{ matrix.name }} - strategy: - matrix: - include: - - name: Linux export - platform: linux - - steps: - - name: Build (Windows) - if: matrix.platform == 'windows' - uses: bend-n/godot-actions/.github/actions/export-windows@main - - - name: Build (Linux) - if: matrix.platform == 'linux' - uses: bend-n/godot-actions/.github/actions/export-linux@main - - - name: Build (Mac) - if: matrix.platform == 'mac' - uses: bend-n/godot-actions/.github/actions/export-mac@main - - - name: Build (Web) - if: matrix.platform == 'web' - uses: bend-n/godot-actions/.github/actions/export-web@main - - - name: Build (Android) - if: matrix.platform == 'android' - uses: bend-n/godot-actions/.github/actions/export-android@main - with: - android-keystore-base64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} - android-password: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} - - push-itch: - needs: [export] - name: Push to itch.io - runs-on: ubuntu-20.04 - steps: - - name: Check for api key - id: secret - run: echo '::set-output name=secret::${{ secrets.BUTLER_CREDENTIALS }}' - - - name: Push - if: steps.secret.outputs.secret - uses: bend-n/godot-actions/.github/actions/itch-push@main - with: - api-key: ${{ secrets.BUTLER_CREDENTIALS }} @@ -1,4 +1,4 @@ -.import/ +.godot/ logs/ *.sh *.py @@ -1,6 +1,6 @@ # godot cli parser -[](https://godotengine.org "Made with godot") +[](https://godotengine.org "Made with godot") [](https://www.npmjs.com/package/@bendn/remap) <a href='https://ko-fi.com/bendn' title='Buy me a coffee' target='_blank'><img height='28' src='https://storage.ko-fi.com/cdn/brandasset/kofi_button_red.png' alt='Buy me a coffee'> </a> @@ -9,6 +9,7 @@ A utility for parsing command line arguments for godot. ## Usage ```gdscript +const RemapButton = preload("res://addons/remap/RemapButton.gd") var label = RemapButton.new() label.action = "ui_left" label._name = "left" diff --git a/Test.gd b/Test.gd deleted file mode 100644 index 539ff35..0000000 --- a/Test.gd +++ /dev/null @@ -1,8 +0,0 @@ -extends Control - - -func _ready(): - var label = RemapButton.new() - label.action = "ui_left" - label._name = "left" - add_child(label) @@ -1,19 +1,34 @@ -[gd_scene load_steps=3 format=2] +[gd_scene load_steps=3 format=3 uid="uid://bueg0v45altvi"] -[ext_resource path="res://Test.gd" type="Script" id=3] -[ext_resource path="res://addons/remap/ActionLabel.gd" type="Script" id=4] +[ext_resource type="Script" path="res://addons/remap/RemapButton.gd" id="2_dj1bu"] +[ext_resource type="Script" path="res://addons/remap/ActionLabel.gd" id="4"] -[node name="Test" type="Control"] +[node name="Test" type="CenterContainer"] +anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 -script = ExtResource( 3 ) +grow_horizontal = 2 +grow_vertical = 2 -[node name="HBoxContainer" type="HBoxContainer" parent="."] -margin_left = 5.0 -margin_top = 42.0 -margin_right = 316.0 -margin_bottom = 75.0 -script = ExtResource( 4 ) -_name = " lef" +[node name="v" type="VBoxContainer" parent="."] +layout_mode = 2 +offset_left = 576.0 +offset_top = 322.0 +offset_right = 576.0 +offset_bottom = 326.0 + +[node name="actionlabel" type="HBoxContainer" parent="v"] +layout_mode = 2 +script = ExtResource("4") +_name = "left" +action = "ui_left" +continuous_updating = true + +[node name="remapbutton" type="HBoxContainer" parent="v"] +layout_mode = 2 +offset_top = 4.0 +offset_bottom = 4.0 +script = ExtResource("2_dj1bu") +_name = "left" action = "ui_left" -icon_size = Vector2( 10, 10 ) +continuous_updating = true diff --git a/addons/remap/ActionIcons.gd b/addons/remap/ActionIcons.gd deleted file mode 100644 index 8bd3e2d..0000000 --- a/addons/remap/ActionIcons.gd +++ /dev/null @@ -1,27 +0,0 @@ -extends HBoxContainer -class_name ActionIcons - -export(String) var action: String -export(Vector2) var size := Vector2(20, 20) -export(bool) var override_font := true - -const font = preload("./PromptFont.tres") - - -func _update(): - for i in get_children(): - i.queue_free() - var act_list := InputMap.get_action_list(action) - for e in act_list: - var icon = IconMap.get_icon(e) - if icon: - var p: PanelContainer = PanelContainer.new() - var i: Label = Label.new() - if override_font: - i.add_font_override("font", font) - i.text = icon - i.align = Label.ALIGN_CENTER - i.valign = Label.VALIGN_CENTER - p.add_child(i) - add_child(p) - i.rect_min_size = size diff --git a/addons/remap/ActionLabel.gd b/addons/remap/ActionLabel.gd index d4fd8a9..956357f 100644 --- a/addons/remap/ActionLabel.gd +++ b/addons/remap/ActionLabel.gd @@ -1,30 +1,55 @@ +## A action label. Displays the inputevents for a action, with a label. + extends HBoxContainer -class_name ActionLabel +@icon("./icons/action_label.svg") + +const IconMap := preload("./private/IconMap.gd") +const ActionIcons := preload("./private/ActionIcons.gd") +const SaveLoadUtils := preload("./private/SaveLoadUtils.gd") + +## The label text. +@export var _name := "" + +## The action to follow. +@export var action := "" + +## The minimum ActionIcon size. +@export var icon_size := Vector2(20, 20) -export(String) var _name: String -export(String) var action: String -export(Vector2) var icon_size := Vector2(20, 20) -export(bool) var override_font := true +## The font to use. Only change if your font has the [url=https://github.com/Shinmera/promptfont/blob/c27797b49dee560e3ea3eaa40e87f9a7f35e8913/glyphs.json]necessary glyphs[/url]. +@export var font: Font = preload("./PromptFont.ttf") -var icons := ActionIcons.new() +## Wether to update continuously. Usefull if you have RemapButtons on this action. +@export var continuous_updating := false + +## The ActionIcons object to use. This is a required internal node. +var icons: ActionIcons = null + +## The label to display the button text on. This is not a required internal node. var name_label := Label.new() + +## The background for the label to display the button text on. This is not a required internal node. var name_label_bg := PanelContainer.new() func _ready() -> void: - SaveLoadUtils.create_dir(SaveLoadUtils.dir) - SaveLoadUtils.load2inputmap(action) - add_child(name_label_bg) - name_label_bg.add_child(name_label) - name_label.text = _name - name_label.rect_min_size = icon_size - name_label.valign = Label.VALIGN_CENTER - name_label.align = Label.ALIGN_CENTER - var spacer := Control.new() - spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL - add_child(spacer) - add_child(icons) - icons.action = action - icons.size = icon_size - icons.override_font = override_font - icons._update() + set_process(continuous_updating) + DirAccess.make_dir_absolute(SaveLoadUtils.dir) + SaveLoadUtils.load_action_to_inputmap(action) + add_child(name_label_bg) + name_label_bg.add_child(name_label) + name_label.text = _name + name_label.custom_minimum_size = icon_size + name_label.vertical_alignment = ALIGNMENT_CENTER + name_label.horizontal_alignment = ALIGNMENT_CENTER + var spacer := Control.new() + spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL + icons = ActionIcons.new(action, icon_size, font) + add_child(spacer) + add_child(icons) + if not continuous_updating: + icons.update() + + +func _process(_delta: float) -> void: + icons.update()
\ No newline at end of file diff --git a/addons/remap/IconMap.gd b/addons/remap/IconMap.gd deleted file mode 100644 index e8b8923..0000000 --- a/addons/remap/IconMap.gd +++ /dev/null @@ -1,120 +0,0 @@ -extends Reference -class_name IconMap - -const key_map := { - KEY_LEFT: "←", - KEY_RIGHT: "→", - KEY_UP: "↑", - KEY_DOWN: "↓", - KEY_ENTER: "", - KEY_KP_ENTER: "", - KEY_HOME: "", - KEY_CONTROL: "", - KEY_ALT: "", - KEY_SHIFT: "", - KEY_SUPER_L: "", - KEY_SUPER_R: "", - KEY_TAB: "", - KEY_CAPSLOCK: "", - KEY_BACKSPACE: "", - KEY_ESCAPE: "", - KEY_PRINT: "", - KEY_SCROLLLOCK: "", - KEY_PAUSE: "", - KEY_NUMLOCK: "", - KEY_DELETE: "", - KEY_INSERT: "", - KEY_PAGEUP: "", - KEY_PAGEDOWN: "", - KEY_SPACE: "", - KEY_F1: "①", - KEY_F2: "②", - KEY_F3: "③", - KEY_F4: "④", - KEY_F5: "⑤", - KEY_F6: "⑥", - KEY_F7: "⑦", - KEY_F8: "⑧", - KEY_F9: "⑨", - KEY_F10: "⑩", - KEY_F11: "⑪", - KEY_F12: "⑫", - KEY_SEMICOLON: ";", - KEY_QUOTELEFT: "`", - KEY_COMMA: ",", - KEY_PERIOD: ".", - KEY_SLASH: "/", - KEY_BACKSLASH: "\\", - KEY_MINUS: "-", - KEY_EQUAL: "=", - KEY_BRACKETLEFT: "[", - KEY_BRACKETRIGHT: "]", - KEY_BRACELEFT: "{", - KEY_BRACERIGHT: "}", - KEY_APOSTROPHE: "'", - KEY_MENU: "⇻", - KEY_END: "" -} - -# JOY_SONY_CIRCLE and JOY_XBOX_B are the same as JOY_DS_A -# TODO: use a different map for each controller after finding out what kind it is -const gamepad_button_map := { - JOY_XBOX_X: "↤", - JOY_XBOX_A: "↧", - JOY_XBOX_B: "↦", - JOY_XBOX_Y: "↥", - JOY_L: "↘", - JOY_R: "↙", - JOY_L2: "↖", - JOY_R2: "↗", - JOY_DPAD_LEFT: "↞", - JOY_DPAD_RIGHT: "↠", - JOY_DPAD_UP: "↟", - JOY_DPAD_DOWN: "↡", -} - -const joystick_map := { - -1: - { - JOY_ANALOG_LX: "↼", - JOY_ANALOG_LY: "⇈", - JOY_ANALOG_RX: "↽", - JOY_ANALOG_RY: "↿", - }, - 1: - { - JOY_ANALOG_LX: "⇀", - JOY_ANALOG_LY: "⇂", - JOY_ANALOG_RX: "⇁", - JOY_ANALOG_RY: "⇃", - } -} - -const mouse_button_map := { - BUTTON_LEFT: "⟵", BUTTON_RIGHT: "⟶", BUTTON_MIDDLE: "⟷", BUTTON_WHEEL_DOWN: "⟱", BUTTON_WHEEL_UP: "⟰" -} - - -static func get_icon(e: InputEvent) -> String: - if e is InputEventKey: - if key_map.has(e.scancode): - return key_map[e.scancode] - elif OS.get_scancode_string(e.scancode): - return OS.get_scancode_string(e.scancode) - elif OS.keyboard_get_scancode_from_physical(e.physical_scancode): - var scancode = OS.keyboard_get_scancode_from_physical(e.physical_scancode) - if key_map.has(scancode): - return key_map[scancode] - elif OS.get_scancode_string(scancode): - return OS.get_scancode_string(scancode) - - elif e is InputEventJoypadButton: - if gamepad_button_map.has(e.button_index): - return gamepad_button_map[e.button_index] - elif e is InputEventJoypadMotion: - if joystick_map[int(e.axis_value)].has(e.axis): - return joystick_map[int(e.axis_value)][e.axis] - elif e is InputEventMouseButton: - if mouse_button_map.has(e.button_index): - return mouse_button_map[e.button_index] - return "❓" diff --git a/addons/remap/KeyPromptLabel.gd b/addons/remap/KeyPromptLabel.gd deleted file mode 100644 index 47aa19f..0000000 --- a/addons/remap/KeyPromptLabel.gd +++ /dev/null @@ -1,13 +0,0 @@ -extends Label - -var selected: InputEvent - - -func _input(event: InputEvent) -> void: - if !RemapUtilities.is_valid_action(event) or event is InputEventMouseButton: - return - if event is InputEventJoypadMotion: - event.axis_value = sign(event.axis_value) - selected = event - text = IconMap.get_icon(event) - $"%ok".disabled = false diff --git a/addons/remap/KeySelector.gd b/addons/remap/KeySelector.gd deleted file mode 100644 index 13234c2..0000000 --- a/addons/remap/KeySelector.gd +++ /dev/null @@ -1,20 +0,0 @@ -extends Node -class_name KeySelector - -signal confirmed(event) -signal cancelled - - -func _ready(): - $"%ok".connect("pressed", self, "confirmed") - $"%cancel".connect("pressed", self, "cancelled") - - -func confirmed(): - emit_signal("confirmed", $"%KeyPrompter".selected) - queue_free() - - -func cancelled(): - emit_signal("cancelled") - queue_free() diff --git a/addons/remap/KeySelector.tscn b/addons/remap/KeySelector.tscn deleted file mode 100644 index 0dcf4fa..0000000 --- a/addons/remap/KeySelector.tscn +++ /dev/null @@ -1,60 +0,0 @@ -[gd_scene load_steps=5 format=2] - -[ext_resource path="res://addons/remap/KeyPromptLabel.gd" type="Script" id=2] -[ext_resource path="res://addons/remap/PromptFont.ttf" type="DynamicFontData" id=3] -[ext_resource path="res://addons/remap/KeySelector.gd" type="Script" id=4] - -[sub_resource type="DynamicFont" id=1] -size = 40 -font_data = ExtResource( 3 ) - -[node name="KeySelector" type="CanvasLayer"] -script = ExtResource( 4 ) - -[node name="ColorRect" type="ColorRect" parent="."] -anchor_right = 1.0 -anchor_bottom = 1.0 -color = Color( 0.160156, 0.160156, 0.160156, 0.705882 ) - -[node name="Center" type="CenterContainer" parent="ColorRect"] -anchor_right = 1.0 -anchor_bottom = 1.0 - -[node name="V" type="VBoxContainer" parent="ColorRect/Center"] -margin_left = 35.0 -margin_top = 53.0 -margin_right = 285.0 -margin_bottom = 126.0 - -[node name="KeyPrompter" type="Label" parent="ColorRect/Center/V"] -unique_name_in_owner = true -margin_right = 250.0 -margin_bottom = 49.0 -rect_min_size = Vector2( 125, 0 ) -custom_fonts/font = SubResource( 1 ) -text = "press a key" -align = 1 -script = ExtResource( 2 ) - -[node name="H" type="HBoxContainer" parent="ColorRect/Center/V"] -margin_top = 53.0 -margin_right = 250.0 -margin_bottom = 73.0 - -[node name="ok" type="Button" parent="ColorRect/Center/V/H"] -unique_name_in_owner = true -margin_right = 123.0 -margin_bottom = 20.0 -rect_min_size = Vector2( 50, 0 ) -size_flags_horizontal = 3 -disabled = true -text = "ok" - -[node name="cancel" type="Button" parent="ColorRect/Center/V/H"] -unique_name_in_owner = true -margin_left = 127.0 -margin_right = 250.0 -margin_bottom = 20.0 -rect_min_size = Vector2( 100, 0 ) -size_flags_horizontal = 3 -text = "cancel" diff --git a/addons/remap/PromptFont.tres b/addons/remap/PromptFont.tres deleted file mode 100644 index 181c50e..0000000 --- a/addons/remap/PromptFont.tres +++ /dev/null @@ -1,7 +0,0 @@ -[gd_resource type="DynamicFont" load_steps=2 format=2] - -[ext_resource path="res://addons/remap/PromptFont.ttf" type="DynamicFontData" id=1] - -[resource] -size = 15 -font_data = ExtResource( 1 ) diff --git a/addons/remap/PromptFont.ttf.import b/addons/remap/PromptFont.ttf.import new file mode 100644 index 0000000..dd70868 --- /dev/null +++ b/addons/remap/PromptFont.ttf.import @@ -0,0 +1,36 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://ck4uai24afp11" +path="res://.godot/imported/PromptFont.ttf-7aa5ceab00464524ed719deff6b514eb.fontdata" + +[deps] + +source_file="res://addons/remap/PromptFont.ttf" +dest_files=["res://.godot/imported/PromptFont.ttf-7aa5ceab00464524ed719deff6b514eb.fontdata"] + +[params] + +Rendering=null +antialiasing=1 +generate_mipmaps=false +multichannel_signed_distance_field=false +msdf_pixel_range=8 +msdf_size=48 +force_autohinter=false +hinting=2 +subpixel_positioning=1 +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[{ +"chars": [], +"glyphs": [], +"name": "New Configuration" +}] +language_support={} +script_support={} +opentype_features={} diff --git a/addons/remap/README.md b/addons/remap/README.md index 157dafa..c337743 100644 --- a/addons/remap/README.md +++ b/addons/remap/README.md @@ -5,6 +5,7 @@ A utility for parsing command line arguments for godot. ## Usage ```gdscript +const RemapButton = preload("res://addons/remap/RemapButton.gd") var label = RemapButton.new() label.action = "ui_left" label._name = "left" diff --git a/addons/remap/RemapButton.gd b/addons/remap/RemapButton.gd index c1310e8..190262a 100644 --- a/addons/remap/RemapButton.gd +++ b/addons/remap/RemapButton.gd @@ -1,69 +1,111 @@ +## Provides a button that allows remapping [InputEventAction]s. + extends HBoxContainer -class_name RemapButton +@icon("./icons/remap_button.svg") + +const ActionIcons := preload("./private/ActionIcons.gd") +const RemapUtilities := preload("./private/RemapUtilities.gd") +const SaveLoadUtils := preload("./private/SaveLoadUtils.gd") + +## Wether to have a clear button. +@export var clear_button := true + +## The text for said clear button. +@export var clear_text := "✗" + +## The text used to prompt the player to press a key. +@export var prompt_text := "[press any key]" + +## The button text. +@export var _name := "" -# if this is overriden, the new scene must -# - have a confirmed(action: InputEvent) signal -# - have a cancelled() signal -# - free itself when one of them is emitted -export(PackedScene) var popup = preload("./KeySelector.tscn") +## The action to follow. +@export var action := "" +## The minimum ActionIcon size. +@export var icon_size := Vector2(20, 20) -export(bool) var clear_button := true -export(String) var clear_text := "✗" -export(String) var _name: String -export(String) var action: String -export(Vector2) var icon_size := Vector2(20, 20) -export(bool) var override_font := true +## The font to use. +@export var font: Font = preload("./PromptFont.ttf") -var icons := ActionIcons.new() +## Wether to update continuously. Usefull if you have multple RemapButtons following this action. +@export var continuous_updating := false + +## The internal ActionIcons object. This is a required internal node. +var icons: ActionIcons = null + +## The internal [Button] object. This is a required internal node. var button := Button.new() + +## The clear button. var clear: Button func _ready() -> void: - rect_min_size = icon_size - SaveLoadUtils.create_dir(SaveLoadUtils.dir) - SaveLoadUtils.load2inputmap(action) - if clear_button: - clear = Button.new() - clear.text = clear_text - clear.connect("pressed", self, "clear") - clear.size_flags_vertical = SIZE_EXPAND_FILL - clear.rect_min_size = icon_size - add_child(clear) - clear.visible = InputMap.get_action_list(action).size() > 0 - if override_font: - clear.add_font_override("font", preload("./PromptFont.tres")) - button.text = _name - button.rect_min_size = icon_size - button.size_flags_vertical = SIZE_EXPAND_FILL - button.connect("pressed", self, "_pressed") - var spacer := Control.new() - spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL - icons.action = action - icons.size = icon_size - icons.override_font = override_font - icons._update() - add_child(button) - add_child(spacer) - add_child(icons) - - + set_process(continuous_updating) + set_process_input(false) + custom_minimum_size = icon_size + DirAccess.make_dir_absolute(SaveLoadUtils.dir) + SaveLoadUtils.load_action_to_inputmap(action) + if clear_button: + clear = Button.new() + clear.text = clear_text + clear.pressed.connect(clear_mappings) + clear.size_flags_vertical = SIZE_EXPAND_FILL + clear.custom_minimum_size = icon_size + add_child(clear) + clear.visible = InputMap.action_get_events(action).size() > 0 + if font: + clear.add_theme_font_override("font", font) + button.text = _name + button.custom_minimum_size = icon_size + button.size_flags_vertical = SIZE_EXPAND_FILL + button.pressed.connect(_pressed) + var spacer := Control.new() + spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL + icons = ActionIcons.new(action, icon_size, font) + add_child(button) + add_child(spacer) + add_child(icons) + if not continuous_updating: + icons.update() func _pressed(): - var selector = popup.instance() - add_child(selector) - selector.connect("confirmed", self, "_on_key_selected") + button.text = prompt_text + set_process_input(true) +func _input(event: InputEvent) -> void: + if ( + not event.is_pressed() + or event in [InputEventMouseMotion, InputEventScreenDrag] + or ( + (event is InputEventJoypadMotion or event is InputEventJoypadButton) and + Input.get_joy_name(event.device) == "HTIX5288:00 0911:5288 Touchpad" # work around https://github.com/godotengine/godot/issues/69153 + ) + ): + return -func _on_key_selected(event: InputEvent): - RemapUtilities.add_action(action, event) - icons._update() - SaveLoadUtils.inputmap2file(action) - clear.show() + if event is InputEventKey and event.physical_keycode == KEY_ESCAPE or not RemapUtilities.is_valid_action(event): + get_viewport().set_input_as_handled() + button.text = _name + set_process_input(false) + return + if event is InputEventJoypadMotion: + event.axis_value = sign(event.axis_value) + get_viewport().set_input_as_handled() + RemapUtilities.add_action(action, event) + if not continuous_updating: + icons.update() + SaveLoadUtils.action_to_file(action) + clear.show() + button.text = _name + set_process_input(false) +## Clears the mappings for this action. +func clear_mappings(): + RemapUtilities.clear_mappings(action) + icons.update() + SaveLoadUtils.action_to_file(action) + clear.hide() -func clear(): - RemapUtilities.clear_mappings(action) - icons._update() - SaveLoadUtils.inputmap2file(action) - clear.hide() +func _process(_delta: float) -> void: + icons.update() diff --git a/addons/remap/RemapUtilities.gd b/addons/remap/RemapUtilities.gd deleted file mode 100644 index bacadbf..0000000 --- a/addons/remap/RemapUtilities.gd +++ /dev/null @@ -1,19 +0,0 @@ -extends Reference -class_name RemapUtilities - -const invalid_actions = [InputEventMouseMotion, InputEventScreenDrag, InputEventScreenTouch, InputEventMIDI] - - -static func clear_mappings(action: String) -> void: - InputMap.action_erase_events(action) - - -static func add_action(action: String, event: InputEvent) -> void: - InputMap.action_add_event(action, event) - - -static func is_valid_action(e: InputEvent) -> bool: - for i in invalid_actions: - if e is i: - return false - return true diff --git a/addons/remap/SaveLoadUtils.gd b/addons/remap/SaveLoadUtils.gd deleted file mode 100644 index fb92310..0000000 --- a/addons/remap/SaveLoadUtils.gd +++ /dev/null @@ -1,55 +0,0 @@ -#warning-ignore-all: return_value_discarded -extends Node -class_name SaveLoadUtils - -const dir := "user://__inputs__" -const path_template := "user://__inputs__/%s.res" - - -static func save(path: String, data: Dictionary) -> void: - var file := File.new() - file.open(path, File.WRITE) - file.store_string(var2str(data)) - file.close() - - -static func load_file(path: String) -> Dictionary: - var file := File.new() - if file.file_exists(path): - file.open(path, File.READ) - var text := file.get_as_text() - var dict := {} - if text: - dict = str2var(text) - file.close() - return dict - save(path, {}) # create file if it doesn't exist - return {} - - -# mkdir -p -static func create_dir(path: String) -> int: - var dir := Directory.new() - return dir.make_dir_recursive(path) - - -static func inputmap2file(action: String) -> void: - var list := InputMap.get_action_list(action) - var data := {actions = list} - save(path_template % action, data) - - -static func load2inputmap(action: String) -> void: - var data := load_file(path_template % action) - if typeof(data) == TYPE_DICTIONARY and typeof(data.get("actions", false)) == TYPE_ARRAY: - for e in data.actions: # validate - if e is InputEvent and RemapUtilities.is_valid_action(e): - continue - push_error("Invalid action: %s" % e) - inputmap2file(action) # reset if invalid - return - RemapUtilities.clear_mappings(action) - for e in data.actions: - RemapUtilities.add_action(action, e) - return - inputmap2file(action) diff --git a/addons/remap/icons/action_icon.svg b/addons/remap/icons/action_icon.svg new file mode 100644 index 0000000..467602e --- /dev/null +++ b/addons/remap/icons/action_icon.svg @@ -0,0 +1 @@ +<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M8 2a6 6 0 0 0-6 6 6 6 0 0 0 6 6 6 6 0 0 0 6-6 6 6 0 0 0-6-6Zm0 2a4 4 0 0 1 4 4 4 4 0 0 1-4 4 4 4 0 0 1-4-4 4 4 0 0 1 4-4Z" fill="#8eef97"/><path d="M10.008 9.939H8.974l-.268-.784H7.269L7 9.94H5.992l1.433-3.878h1.15zM8.464 8.444l-.477-1.39-.476 1.39Z" aria-label="A" style="font-weight:700;font-size:5.33333px;line-height:1.25;font-family:Verdana;-inkscape-font-specification:'Verdana Bold';letter-spacing:2px;fill:#8eef97;stroke:#8eef97;stroke-width:.2"/></svg>
\ No newline at end of file diff --git a/addons/remap/icons/action_icon.svg.import b/addons/remap/icons/action_icon.svg.import new file mode 100644 index 0000000..7b93f6c --- /dev/null +++ b/addons/remap/icons/action_icon.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://csndvekc7ypb2" +path="res://.godot/imported/action_icon.svg-d3a92058e49a58847b5682749062f7e6.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/remap/icons/action_icon.svg" +dest_files=["res://.godot/imported/action_icon.svg-d3a92058e49a58847b5682749062f7e6.ctex"] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/bptc_ldr=0 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/remap/icons/action_icons.svg b/addons/remap/icons/action_icons.svg new file mode 100644 index 0000000..712cf1e --- /dev/null +++ b/addons/remap/icons/action_icons.svg @@ -0,0 +1 @@ +<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M8 1.444a3.193 3.193 0 0 0-3.193 3.193A3.193 3.193 0 0 0 8 7.83a3.193 3.193 0 0 0 3.193-3.193A3.193 3.193 0 0 0 8 1.444Zm0 1.064a2.129 2.129 0 0 1 2.129 2.129A2.129 2.129 0 0 1 8 6.766a2.129 2.129 0 0 1-2.129-2.129A2.129 2.129 0 0 1 8 2.508Z" fill="#8eef97"/><path d="M10.008 9.939H8.974l-.268-.784H7.269L7 9.94H5.992l1.433-3.878h1.15zM8.464 8.444l-.477-1.39-.476 1.39Z" aria-label="A" style="font-weight:700;font-size:5.33333px;line-height:1.25;font-family:Verdana;-inkscape-font-specification:'Verdana Bold';letter-spacing:2px;fill:#8eef97;stroke:#8eef97;stroke-width:.2" transform="translate(3.743 .38) scale(.53217)"/><path d="M1.325 1.444a3.193 3.193 0 0 0-3.193 3.193A3.193 3.193 0 0 0 1.325 7.83a3.193 3.193 0 0 0 3.193-3.193 3.193 3.193 0 0 0-3.193-3.193Zm0 1.064a2.129 2.129 0 0 1 2.129 2.129 2.129 2.129 0 0 1-2.129 2.129A2.129 2.129 0 0 1-.804 4.637a2.129 2.129 0 0 1 2.13-2.129Z" fill="#8eef97"/><path d="M10.008 9.939H8.974l-.268-.784H7.269L7 9.94H5.992l1.433-3.878h1.15zM8.464 8.444l-.477-1.39-.476 1.39Z" aria-label="A" style="font-weight:700;font-size:5.33333px;line-height:1.25;font-family:Verdana;-inkscape-font-specification:'Verdana Bold';letter-spacing:2px;fill:#8eef97;stroke:#8eef97;stroke-width:.2" transform="translate(-2.932 .38) scale(.53217)"/><path d="M14.675 1.444a3.193 3.193 0 0 0-3.193 3.193 3.193 3.193 0 0 0 3.193 3.193 3.193 3.193 0 0 0 3.193-3.193 3.193 3.193 0 0 0-3.193-3.193Zm0 1.064a2.129 2.129 0 0 1 2.129 2.129 2.129 2.129 0 0 1-2.13 2.129 2.129 2.129 0 0 1-2.128-2.129 2.129 2.129 0 0 1 2.129-2.129Z" fill="#8eef97"/><path d="M10.008 9.939H8.974l-.268-.784H7.269L7 9.94H5.992l1.433-3.878h1.15zM8.464 8.444l-.477-1.39-.476 1.39Z" aria-label="A" style="font-weight:700;font-size:5.33333px;line-height:1.25;font-family:Verdana;-inkscape-font-specification:'Verdana Bold';letter-spacing:2px;fill:#8eef97;stroke:#8eef97;stroke-width:.2" transform="translate(10.418 .38) scale(.53217)"/><path d="M8 8.17a3.193 3.193 0 0 0-3.193 3.193A3.193 3.193 0 0 0 8 14.556a3.193 3.193 0 0 0 3.193-3.193A3.193 3.193 0 0 0 8 8.17Zm0 1.064a2.129 2.129 0 0 1 2.129 2.129A2.129 2.129 0 0 1 8 13.492a2.129 2.129 0 0 1-2.129-2.129A2.129 2.129 0 0 1 8 9.234Z" fill="#8eef97"/><path d="M10.008 9.939H8.974l-.268-.784H7.269L7 9.94H5.992l1.433-3.878h1.15zM8.464 8.444l-.477-1.39-.476 1.39Z" aria-label="A" style="font-weight:700;font-size:5.33333px;line-height:1.25;font-family:Verdana;-inkscape-font-specification:'Verdana Bold';letter-spacing:2px;fill:#8eef97;stroke:#8eef97;stroke-width:.2" transform="translate(3.743 7.106) scale(.53217)"/><path d="M1.325 8.17a3.193 3.193 0 0 0-3.193 3.193 3.193 3.193 0 0 0 3.193 3.193 3.193 3.193 0 0 0 3.193-3.193A3.193 3.193 0 0 0 1.325 8.17Zm0 1.064a2.129 2.129 0 0 1 2.129 2.129 2.129 2.129 0 0 1-2.129 2.129 2.129 2.129 0 0 1-2.129-2.129 2.129 2.129 0 0 1 2.13-2.129Z" fill="#8eef97"/><path d="M10.008 9.939H8.974l-.268-.784H7.269L7 9.94H5.992l1.433-3.878h1.15zM8.464 8.444l-.477-1.39-.476 1.39Z" aria-label="A" style="font-weight:700;font-size:5.33333px;line-height:1.25;font-family:Verdana;-inkscape-font-specification:'Verdana Bold';letter-spacing:2px;fill:#8eef97;stroke:#8eef97;stroke-width:.2" transform="translate(-2.932 7.106) scale(.53217)"/><path d="M14.675 8.17a3.193 3.193 0 0 0-3.193 3.193 3.193 3.193 0 0 0 3.193 3.193 3.193 3.193 0 0 0 3.193-3.193 3.193 3.193 0 0 0-3.193-3.193Zm0 1.064a2.129 2.129 0 0 1 2.129 2.129 2.129 2.129 0 0 1-2.13 2.129 2.129 2.129 0 0 1-2.128-2.129 2.129 2.129 0 0 1 2.129-2.129Z" fill="#8eef97"/><path d="M10.008 9.939H8.974l-.268-.784H7.269L7 9.94H5.992l1.433-3.878h1.15zM8.464 8.444l-.477-1.39-.476 1.39Z" aria-label="A" style="font-weight:700;font-size:5.33333px;line-height:1.25;font-family:Verdana;-inkscape-font-specification:'Verdana Bold';letter-spacing:2px;fill:#8eef97;stroke:#8eef97;stroke-width:.2" transform="translate(10.418 7.106) scale(.53217)"/></svg>
\ No newline at end of file diff --git a/addons/remap/icons/action_icons.svg.import b/addons/remap/icons/action_icons.svg.import new file mode 100644 index 0000000..ad11b85 --- /dev/null +++ b/addons/remap/icons/action_icons.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c2gbd0p5byx4q" +path="res://.godot/imported/action_icons.svg-3932be3a3b1cd8f58e4071f6b1c0b727.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/remap/icons/action_icons.svg" +dest_files=["res://.godot/imported/action_icons.svg-3932be3a3b1cd8f58e4071f6b1c0b727.ctex"] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/bptc_ldr=0 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/remap/icons/action_label.svg b/addons/remap/icons/action_label.svg new file mode 100644 index 0000000..5de9a33 --- /dev/null +++ b/addons/remap/icons/action_label.svg @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + height="16" + viewBox="0 0 16 16" + width="16" + version="1.1" + id="svg4" + sodipodi:docname="action_label.svg" + inkscape:version="1.2.1 (9c6d41e410, 2022-07-14, custom)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs8" /> + <sodipodi:namedview + id="namedview6" + pagecolor="#505050" + bordercolor="#ffffff" + borderopacity="1" + inkscape:showpageshadow="0" + inkscape:pageopacity="0" + inkscape:pagecheckerboard="1" + inkscape:deskcolor="#505050" + showgrid="false" + inkscape:zoom="9.4933989" + inkscape:cx="-18.907875" + inkscape:cy="30.758214" + inkscape:window-width="1920" + inkscape:window-height="1025" + inkscape:window-x="0" + inkscape:window-y="55" + inkscape:window-maximized="1" + inkscape:current-layer="svg4" /> + <path + id="path2" + d="M 6 3 A 1.0001 1.0001 0 0 0 5.2929688 3.2929688 L 1.2929688 7.2929688 A 1.0001 1.0001 0 0 0 1.2929688 8.7070312 L 5.2929688 12.707031 A 1.0001 1.0001 0 0 0 6 13 L 14 13 A 1.0001 1.0001 0 0 0 15 12 L 15 4 A 1.0001 1.0001 0 0 0 14 3 L 6 3 z M 10.902344 5.0742188 A 2.9254353 2.9254353 0 0 1 13.826172 8 A 2.9254353 2.9254353 0 0 1 10.902344 10.925781 A 2.9254353 2.9254353 0 0 1 7.9765625 8 A 2.9254353 2.9254353 0 0 1 10.902344 5.0742188 z M 5 7 A 1 1 0 0 1 6 8 A 1 1 0 0 1 5 9 A 1 1 0 0 1 4 8 A 1 1 0 0 1 5 7 z " + style="fill:#8eef97;fill-opacity:1" /> + <g + id="g443-3" + transform="matrix(0.37008351,0,0,0.36026328,3.3156431,3.6702108)"> + <path + d="m 20.500831,6.0177285 a 6,6 0 0 0 -6,6.0000005 6,6 0 0 0 6,5.999999 6,6 0 0 0 6,-5.999999 6,6 0 0 0 -6,-6.0000005 z m 0,2 a 4,4 0 0 1 4,4.0000005 4,4 0 0 1 -4,3.999999 4,4 0 0 1 -4,-3.999999 4,4 0 0 1 4,-4.0000005 z" + fill="#8eef97" + id="path287-5" /> + <path + d="m 22.508831,13.956729 h -1.034 l -0.268,-0.784001 h -1.437 l -0.269,0.785 h -1.008 l 1.433,-3.877999 h 1.15 z m -1.544,-1.495 -0.477,-1.390001 -0.476,1.390001 z" + aria-label="A" + style="font-weight:700;font-size:5.33333px;line-height:1.25;font-family:Verdana;-inkscape-font-specification:'Verdana Bold';letter-spacing:2px;fill:#8eef97;stroke:#8eef97;stroke-width:0.2" + id="path289-6" /> + </g> +</svg> diff --git a/addons/remap/icons/action_label.svg.import b/addons/remap/icons/action_label.svg.import new file mode 100644 index 0000000..743550b --- /dev/null +++ b/addons/remap/icons/action_label.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dxilcw87tjnni" +path="res://.godot/imported/action_label.svg-95462f1979e6b228dc8e5c5c198a8a1a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/remap/icons/action_label.svg" +dest_files=["res://.godot/imported/action_label.svg-95462f1979e6b228dc8e5c5c198a8a1a.ctex"] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/bptc_ldr=0 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/remap/icons/remap_button.svg b/addons/remap/icons/remap_button.svg new file mode 100644 index 0000000..9034fce --- /dev/null +++ b/addons/remap/icons/remap_button.svg @@ -0,0 +1 @@ +<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M5.5 9.43c-.831 0-1.5.668-1.5 1.5v1.5H2v2h12v-2h-2v-1.5c0-.832-.669-1.5-1.5-1.5zM8 1.57a3.503 3.503 0 0 0-3.503 3.504A3.503 3.503 0 0 0 8 8.578a3.503 3.503 0 0 0 3.503-3.504A3.503 3.503 0 0 0 8 1.571zm0 1.17a2.336 2.336 0 0 1 2.336 2.335A2.336 2.336 0 0 1 8 7.41a2.336 2.336 0 0 1-2.336-2.336A2.336 2.336 0 0 1 8 2.74z" fill="#8eef97"/><path d="M10.008 4.255H8.974l-.268-.784H7.269L7 4.256H5.992L7.425.378h1.15zM8.464 2.76l-.477-1.39-.476 1.39z" aria-label="A" style="font-weight:700;font-size:5.33333px;line-height:1.25;font-family:Verdana;-inkscape-font-specification:'Verdana Bold';letter-spacing:2px;fill:#8eef97;stroke:#8eef97;stroke-width:.2" transform="translate(3.329 3.722) scale(.5839)"/></svg>
\ No newline at end of file diff --git a/addons/remap/icons/remap_button.svg.import b/addons/remap/icons/remap_button.svg.import new file mode 100644 index 0000000..8defbe5 --- /dev/null +++ b/addons/remap/icons/remap_button.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://r5t2tpw6of2r" +path="res://.godot/imported/remap_button.svg-8932cf4f00007d2b2e1f55aeb0105c47.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/remap/icons/remap_button.svg" +dest_files=["res://.godot/imported/remap_button.svg-8932cf4f00007d2b2e1f55aeb0105c47.ctex"] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/bptc_ldr=0 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/remap/package.json b/addons/remap/package.json index 09be8a7..08a8c93 100644 --- a/addons/remap/package.json +++ b/addons/remap/package.json @@ -1,6 +1,6 @@ { "name": "@bendn/remap", - "version": "1.2.2", + "version": "4.0.0", "description": "godot input remapping", "main": "InteractiveActionLabel.gd", "scripts": { diff --git a/addons/remap/private/ActionIcon.gd b/addons/remap/private/ActionIcon.gd new file mode 100644 index 0000000..3f923e7 --- /dev/null +++ b/addons/remap/private/ActionIcon.gd @@ -0,0 +1,16 @@ +## A ActionIcon used by ActionIcons. + +extends PanelContainer +@icon("../icons/action_icon.svg") + +## The inner label. +var label := Label.new() + +func _init(text: String, min_size: Vector2, font: Font) -> void: + label.horizontal_alignment = BoxContainer.ALIGNMENT_CENTER + label.vertical_alignment = BoxContainer.ALIGNMENT_CENTER + add_child(label) + label.custom_minimum_size = min_size + label.text = text + if font: + label.add_theme_font_override("font", font) diff --git a/addons/remap/private/ActionIcons.gd b/addons/remap/private/ActionIcons.gd new file mode 100644 index 0000000..ea94cea --- /dev/null +++ b/addons/remap/private/ActionIcons.gd @@ -0,0 +1,29 @@ +## Displays multiple ActionIcons. + +extends HBoxContainer +@icon("../icons/action_icons.svg") + +const ActionIcon := preload("./ActionIcon.gd") +const IconMap := preload("./IconMap.gd") + +## The action to follow. +var action: String + +## The minimum icon size. +var min_size := Vector2(20, 20) + +## The font to use +var font: Font + +func _init(p_action: String, p_min_size: Vector2, p_font: Font) -> void: + action = p_action + min_size = p_min_size + font = p_font + +## Updates the icons to the latest inputs. +func update(): + for i in get_children(): + i.queue_free() + for e in InputMap.action_get_events(action): + var icon := IconMap.get_icon(e) + add_child(ActionIcon.new(icon, min_size, font)) diff --git a/addons/remap/private/IconMap.gd b/addons/remap/private/IconMap.gd new file mode 100644 index 0000000..27e8922 --- /dev/null +++ b/addons/remap/private/IconMap.gd @@ -0,0 +1,195 @@ +## Maps inputevents to a [url=https://shinmera.github.io/promptfont/]prompt font[/url] icon. + +extends RefCounted + +## The map for keys. +const KEY_MAP := { + KEY_LEFT: "←", + KEY_RIGHT: "→", + KEY_UP: "↑", + KEY_DOWN: "↓", + KEY_ENTER: "", + KEY_KP_ENTER: "", + KEY_HOME: "", + KEY_CTRL: "", + KEY_ALT: "", + KEY_SHIFT: "", + KEY_SUPER_L: "", + KEY_SUPER_R: "", + KEY_TAB: "", + KEY_CAPSLOCK: "", + KEY_BACKSPACE: "", + KEY_ESCAPE: "", + KEY_PRINT: "", + KEY_SCROLLLOCK: "", + KEY_PAUSE: "", + KEY_NUMLOCK: "", + KEY_DELETE: "", + KEY_INSERT: "", + KEY_PAGEUP: "", + KEY_PAGEDOWN: "", + KEY_SPACE: "", + KEY_F1: "①", + KEY_F2: "②", + KEY_F3: "③", + KEY_F4: "④", + KEY_F5: "⑤", + KEY_F6: "⑥", + KEY_F7: "⑦", + KEY_F8: "⑧", + KEY_F9: "⑨", + KEY_F10: "⑩", + KEY_F11: "⑪", + KEY_F12: "⑫", + KEY_SEMICOLON: ";", + KEY_QUOTELEFT: "`", + KEY_COMMA: ",", + KEY_PERIOD: ".", + KEY_SLASH: "/", + KEY_BACKSLASH: "\\", + KEY_MINUS: "-", + KEY_EQUAL: "=", + KEY_BRACKETLEFT: "[", + KEY_BRACKETRIGHT: "]", + KEY_BRACELEFT: "{", + KEY_BRACERIGHT: "}", + KEY_APOSTROPHE: "'", + KEY_MENU: "⇻", + KEY_END: "" +} + +## Pad enum. Used to decide which button set to use. +enum PADS { XBOX, PLAYSTATION, NINTENDO, GENERIC } + +## Generic joypad button mappings. +const JOYPAD_BUTTON_MAP := { + JOY_BUTTON_LEFT_SHOULDER: "↘", + JOY_BUTTON_RIGHT_SHOULDER: "↙", + JOY_BUTTON_DPAD_LEFT: "↞", + JOY_BUTTON_DPAD_RIGHT: "↠", + JOY_BUTTON_DPAD_UP: "↟", + JOY_BUTTON_DPAD_DOWN: "↡", +} + +## Xbox button map. +const XBOX_BUTTON_MAP := { + JOY_BUTTON_A: "↧", + JOY_BUTTON_B: "↦", + JOY_BUTTON_X: "↤", + JOY_BUTTON_Y: "↥", + JOY_BUTTON_BACK: "⇺", + JOY_BUTTON_GUIDE: "", + JOY_BUTTON_START: "⇻", +} + +## PS button map. +const PLAYSTATION_BUTTON_MAP := { + JOY_BUTTON_A: "⇣", + JOY_BUTTON_B: "⇢", + JOY_BUTTON_X: "⇠", + JOY_BUTTON_Y: "⇡", + JOY_BUTTON_BACK: "⇦", + JOY_BUTTON_GUIDE: "", + JOY_BUTTON_START: "⇨", +} + +## Nintendo switch button map. +const NINTENDO_BUTTON_MAP := { + JOY_BUTTON_A: "↥", + JOY_BUTTON_B: "↧", + JOY_BUTTON_X: "↥", + JOY_BUTTON_Y: "↤", + JOY_BUTTON_BACK: "⇽", + JOY_BUTTON_GUIDE: "❓", # there is no joy_button_guide on switch + JOY_BUTTON_START: "⇾", +} + +## Joystick axis map. +## Index by joystick index (left or right) first. +const JOYSTICK_MAP := { + -1: + { + JOY_AXIS_LEFT_X: "↼", + JOY_AXIS_LEFT_Y: "⇈", + JOY_AXIS_RIGHT_X: "↽", + JOY_AXIS_RIGHT_Y: "↿", + }, + 1: + { + JOY_AXIS_LEFT_X: "⇀", + JOY_AXIS_LEFT_Y: "⇂", + JOY_AXIS_RIGHT_X: "⇁", + JOY_AXIS_RIGHT_Y: "⇃", + } +} + +## Joypad trigger map. +const trigger_map := {JOY_AXIS_TRIGGER_LEFT: "↲", JOY_AXIS_TRIGGER_RIGHT: "↳"} + +## Mouse button map. +const mouse_button_map := { + MOUSE_BUTTON_LEFT: "⟵", + MOUSE_BUTTON_RIGHT: "⟶", + MOUSE_BUTTON_MIDDLE: "⟷", + MOUSE_BUTTON_WHEEL_DOWN: "⟱", + MOUSE_BUTTON_WHEEL_UP: "⟰" +} + + +## Tries to find the icon for a physical key [param scancode]. +static func check_scn(scancode: int, __r := false) -> String: + if KEY_MAP.has(scancode): + return KEY_MAP[scancode] + var kcs := OS.get_keycode_string(scancode) + if kcs: + return kcs + if !__r: + var fall := check_scn(DisplayServer.keyboard_get_keycode_from_physical(scancode), true) + if fall: + return fall + return "" + + +## Partially lifted from [url=https://github.com/nathanhoad/godot_input_helper/blob/a6140576d3dc48e0296615c4f486456b331f2332/addons/input_helper/input_helper.gd#L48-L60]nathanhoad/godot_input_helper[/url]. +## Naively tries to get the device, returning a PAD enum. +static func get_device(raw_name: String) -> int: + raw_name = raw_name.to_lower() + match raw_name: + "xinput gamepad", "xbox series controller", "xbox 360 controller": + return PADS.XBOX + "sony dualsense", "ps5 controller", "ps4 controller": + return PADS.PLAYSTATION + "switch": + return PADS.NINTENDO + _: + return PADS.GENERIC + + +## Gets the icon for a input[param e]vent. Returns [code]?[/code] on failure. +static func get_icon(e: InputEvent) -> String: + if e is InputEventKey: + var res := check_scn(e.physical_keycode) + if res: + return res + elif e is InputEventJoypadButton: + if e.button_index < 7: + match get_device(Input.get_joy_name(e.device)): + PADS.XBOX: + return XBOX_BUTTON_MAP[e.button_index] + PADS.PLAYSTATION: + return PLAYSTATION_BUTTON_MAP[e.button_index] + PADS.NINTENDO: + return NINTENDO_BUTTON_MAP[e.button_index] + PADS.GENERIC: + return XBOX_BUTTON_MAP[e.button_index] # fallback to xbox + elif JOYPAD_BUTTON_MAP.has(e.button_index): + return JOYPAD_BUTTON_MAP[e.button_index] + elif e is InputEventJoypadMotion: + if JOYSTICK_MAP[int(e.axis_value)].has(e.axis): + return JOYSTICK_MAP[int(e.axis_value)][e.axis] + if trigger_map.has(e.axis): + return trigger_map[e.axis] + elif e is InputEventMouseButton: + if mouse_button_map.has(e.button_index): + return mouse_button_map[e.button_index] + return "❓" diff --git a/addons/remap/private/RemapUtilities.gd b/addons/remap/private/RemapUtilities.gd new file mode 100644 index 0000000..81e77e9 --- /dev/null +++ b/addons/remap/private/RemapUtilities.gd @@ -0,0 +1,22 @@ +## Utilities for remapping. + +extends RefCounted + + +## Clear the mappings for a action. +static func clear_mappings(action: String) -> void: + InputMap.action_erase_events(action) + + +## Adds a input to a action. +static func add_action(action: String, event: InputEvent) -> void: + InputMap.action_add_event(action, event) + + +## Checks for action validity. +static func is_valid_action(e: InputEvent) -> bool: + var good_events := [InputEventKey, InputEventMouseButton, InputEventJoypadButton, InputEventJoypadMotion] + for g_e in good_events: + if e is g_e: + return true + return false diff --git a/addons/remap/private/SaveLoadUtils.gd b/addons/remap/private/SaveLoadUtils.gd new file mode 100644 index 0000000..541a2bc --- /dev/null +++ b/addons/remap/private/SaveLoadUtils.gd @@ -0,0 +1,54 @@ +## Saveing and loading utilities. +## Provides functions to save [InputEventAction]s. + +extends RefCounted +const RemapUtilities := preload("./RemapUtilities.gd") + +## The directory to put the inputs in. +const dir := "user://__inputs__/" + +## The complete path template. +const path_template := dir + "%s.res" + + +## Saves a basic dictionary to a path. +static func save(path: String, data: Dictionary) -> void: + var file := FileAccess.open(path, FileAccess.WRITE) + file.store_string(var_to_str(data)) + + +## Loads a basic dictionary out of a file. +static func load_file(path: String) -> Dictionary: + if FileAccess.file_exists(path): + var file := FileAccess.open(path, FileAccess.READ) + var text := file.get_as_text() + var dict := {} + if text: + dict = str_to_var(text) + return dict + save(path, {}) # create file if it doesn't exist + return {} + + +## Saves a [InputEventAction] to a file. +static func action_to_file(action: String) -> void: + var list := InputMap.action_get_events(action) + var data := {actions = list} + save(path_template % action, data) + + +## Loads a [InputEventAction] from a file into the [InputMap]. +static func load_action_to_inputmap(action: String) -> void: + var data := load_file(path_template % action) + if typeof(data) == TYPE_DICTIONARY and typeof(data.get("actions", false)) == TYPE_ARRAY: + for e in data.actions: # validate + if RemapUtilities.is_valid_action(e): + continue + push_error("Invalid action: %s" % e) + action_to_file(action) # reset if invalid + return + RemapUtilities.clear_mappings(action) + for e in data.actions: + RemapUtilities.add_action(action, e) + return + action_to_file(action) # reset if invalid diff --git a/export_presets.cfg b/export_presets.cfg deleted file mode 100644 index fb88f7e..0000000 --- a/export_presets.cfg +++ /dev/null @@ -1,24 +0,0 @@ -[preset.0] - -name="Linux" -platform="Linux/X11" -runnable=true -custom_features="" -export_filter="all_resources" -include_filter="" -exclude_filter="" -export_path="" -script_export_mode=1 -script_encryption_key="" - -[preset.0.options] - -custom_template/debug="" -custom_template/release="" -binary_format/64_bits=true -binary_format/embed_pck=true -texture_format/bptc=false -texture_format/s3tc=true -texture_format/etc=false -texture_format/etc2=false -texture_format/no_bptc_fallbacks=true diff --git a/project.godot b/project.godot index a758d14..99e5261 100644 --- a/project.godot +++ b/project.godot @@ -6,53 +6,7 @@ ; [section] ; section goes between [] ; param=value ; assign values to parameters -config_version=4 - -_global_script_classes=[ { -"base": "HBoxContainer", -"class": "ActionIcons", -"language": "GDScript", -"path": "res://addons/remap/ActionIcons.gd" -}, { -"base": "HBoxContainer", -"class": "ActionLabel", -"language": "GDScript", -"path": "res://addons/remap/ActionLabel.gd" -}, { -"base": "Reference", -"class": "IconMap", -"language": "GDScript", -"path": "res://addons/remap/IconMap.gd" -}, { -"base": "Node", -"class": "KeySelector", -"language": "GDScript", -"path": "res://addons/remap/KeySelector.gd" -}, { -"base": "HBoxContainer", -"class": "RemapButton", -"language": "GDScript", -"path": "res://addons/remap/RemapButton.gd" -}, { -"base": "Reference", -"class": "RemapUtilities", -"language": "GDScript", -"path": "res://addons/remap/RemapUtilities.gd" -}, { -"base": "Node", -"class": "SaveLoadUtils", -"language": "GDScript", -"path": "res://addons/remap/SaveLoadUtils.gd" -} ] -_global_script_class_icons={ -"ActionIcons": "", -"ActionLabel": "", -"IconMap": "", -"KeySelector": "", -"RemapButton": "", -"RemapUtilities": "", -"SaveLoadUtils": "" -} +config_version=5 [application] @@ -60,6 +14,7 @@ config/name="test" run/main_scene="res://Test.tscn" config/use_custom_user_dir=true config/custom_user_dir_name="remap" +config/features=PackedStringArray("4.0") [debug] @@ -67,24 +22,22 @@ gdscript/warnings/return_value_discarded=false [display] +window/stretch/mode="2d" +window/stretch/aspect="expand" window/size/width=320 window/size/height=180 window/size/test_width=1280 window/size/test_height=720 -window/dpi/allow_hidpi=true -window/stretch/mode="2d" -window/stretch/aspect="expand" [input] ui_left={ "deadzone": 0.5, -"events": [ Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":-1.0,"script":null) -, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":65,"physical_scancode":0,"unicode":0,"echo":false,"script":null) +"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":3,"axis_value":-1.0,"script":null) , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":0,"pressure":0.0,"pressed":false,"script":null) -, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":0,"physical_scancode":16777229,"unicode":0,"echo":false,"script":null) -, Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"button_mask":0,"position":Vector2( 0, 0 ),"global_position":Vector2( 0, 0 ),"factor":1.0,"button_index":1,"pressed":false,"doubleclick":false,"script":null) - ] +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"unicode":0,"echo":false,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":66,"physical_keycode":0,"unicode":0,"echo":false,"script":null) +] } [logging] |