Pārlūkot izejas kodu

[Joypads] Add SDL config re-mapping tool.

Most of the code is in the remap folder, but it depends on the gamepad
diagram scene.
It allows remapping of pads to values that godot can understand.
It also comes with some default mapping for the HTML5 platform.
Fabio Alessandrelli 4 gadi atpakaļ
vecāks
revīzija
2a13307276

+ 21 - 2
misc/joypads/joypads.gd

@@ -32,7 +32,7 @@ func _process(_delta):
 	# Display the name of the joypad if we haven't already.
 	if joy_num != cur_joy:
 		cur_joy = joy_num
-		joypad_name.set_text(Input.get_joy_name(joy_num))
+		joypad_name.set_text(Input.get_joy_name(joy_num) + "\n" + Input.get_joy_guid(joy_num))
 
 	# Loop through the axes and show their current values.
 	for axis in range(JOY_AXIS_MAX):
@@ -64,7 +64,10 @@ func _process(_delta):
 # Called whenever a joypad has been connected or disconnected.
 func _on_joy_connection_changed(device_id, connected):
 	if device_id == cur_joy:
-		joypad_name.set_text(Input.get_joy_name(device_id) if connected else "")
+		if connected:
+			joypad_name.set_text(Input.get_joy_name(device_id) + "\n" + Input.get_joy_guid(device_id))
+		else:
+			joypad_name.set_text("")
 
 
 func _on_start_vibration_pressed():
@@ -76,3 +79,19 @@ func _on_start_vibration_pressed():
 
 func _on_stop_vibration_pressed():
 	Input.stop_joy_vibration(cur_joy)
+
+
+func _on_Remap_pressed():
+	$RemapWizard.start(cur_joy)
+
+
+func _on_Clear_pressed():
+	var guid = Input.get_joy_guid(cur_joy)
+	if guid.empty():
+		printerr("No gamepad selected")
+		return
+	Input.remove_joy_mapping(guid)
+
+
+func _on_Show_pressed():
+	$RemapWizard.show_map()

+ 39 - 1
misc/joypads/joypads.tscn

@@ -1,7 +1,8 @@
-[gd_scene load_steps=3 format=2]
+[gd_scene load_steps=4 format=2]
 
 [ext_resource path="res://joypads.gd" type="Script" id=1]
 [ext_resource path="res://joypad_diagram.tscn" type="PackedScene" id=2]
+[ext_resource path="res://remap/remap_wizard.tscn" type="PackedScene" id=3]
 
 [node name="joypads" type="Control"]
 anchor_left = 0.5
@@ -979,5 +980,42 @@ text = "Stop Vibration"
 __meta__ = {
 "_edit_use_anchors_": false
 }
+
+[node name="VBoxContainer" type="HBoxContainer" parent="."]
+anchor_left = 1.0
+anchor_top = 1.0
+anchor_right = 1.0
+anchor_bottom = 1.0
+margin_left = -250.0
+margin_top = -42.0
+margin_bottom = -12.0
+alignment = 1
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Clear" type="Button" parent="VBoxContainer"]
+margin_left = 11.0
+margin_right = 130.0
+margin_bottom = 30.0
+text = "Set Raw Mapping"
+
+[node name="Remap" type="Button" parent="VBoxContainer"]
+margin_left = 134.0
+margin_right = 190.0
+margin_bottom = 30.0
+text = "Remap"
+
+[node name="Show" type="Button" parent="VBoxContainer"]
+margin_left = 194.0
+margin_right = 239.0
+margin_bottom = 30.0
+text = "Show"
+
+[node name="RemapWizard" parent="." instance=ExtResource( 3 )]
+
 [connection signal="pressed" from="Vibration/Buttons/Start" to="." method="_on_start_vibration_pressed"]
 [connection signal="pressed" from="Vibration/Buttons/Stop" to="." method="_on_stop_vibration_pressed"]
+[connection signal="pressed" from="VBoxContainer/Clear" to="." method="_on_Clear_pressed"]
+[connection signal="pressed" from="VBoxContainer/Remap" to="." method="_on_Remap_pressed"]
+[connection signal="pressed" from="VBoxContainer/Show" to="." method="_on_Show_pressed"]

+ 7 - 2
misc/joypads/project.godot

@@ -8,9 +8,14 @@
 
 config_version=4
 
-_global_script_classes=[  ]
+_global_script_classes=[ {
+"base": "Reference",
+"class": "JoyMapping",
+"language": "GDScript",
+"path": "res://remap/joy_mapping.gd"
+} ]
 _global_script_class_icons={
-
+"JoyMapping": ""
 }
 
 [application]

+ 137 - 0
misc/joypads/remap/joy_mapping.gd

@@ -0,0 +1,137 @@
+extends Reference
+class_name JoyMapping
+
+
+enum TYPE {NONE, BTN, AXIS}
+enum AXIS {FULL, HALF_PLUS, HALF_MINUS}
+
+const PLATFORMS = {
+	# From gamecontrollerdb
+	"Windows": "Windows",
+	"OSX": "Mac OS X",
+	"X11": "Linux",
+	"Android": "Android",
+	"iOS": "iOS",
+	# Godot customs
+	"HTML5": "Javascript",
+	"UWP": "UWP",
+	# 4.x compat
+	"Linux": "Linux",
+	"FreeBSD": "Linux",
+	"NetBSD": "Linux",
+	"BSD": "Linux",
+	"macOS": "Mac OS X",
+}
+
+const BASE = {
+	# Buttons
+	"a": JOY_XBOX_A,
+	"b": JOY_XBOX_B,
+	"y": JOY_XBOX_Y,
+	"x": JOY_XBOX_X,
+	"start": JOY_START,
+	"back": JOY_SELECT,
+	"leftstick": JOY_BUTTON_8,
+	"rightstick": JOY_BUTTON_9,
+	"leftshoulder": JOY_L,
+	"rightshoulder": JOY_R,
+	"dpup": JOY_DPAD_UP,
+	"dpleft": JOY_DPAD_LEFT,
+	"dpdown": JOY_DPAD_DOWN,
+	"dpright": JOY_DPAD_RIGHT,
+
+	# Axis
+	"leftx": JOY_AXIS_0,
+	"lefty": JOY_AXIS_1,
+	"rightx": JOY_AXIS_2,
+	"righty": JOY_AXIS_3,
+	"lefttrigger": JOY_ANALOG_L2,
+	"righttrigger": JOY_ANALOG_R2,
+}
+
+const XBOX = {
+	"a": "b0",
+	"b": "b1",
+	"y": "b3",
+	"x": "b2",
+	"start": "b7",
+	"guide": "b8",
+	"back": "b6",
+	"leftstick": "b9",
+	"rightstick": "b10",
+	"leftshoulder": "b4",
+	"rightshoulder": "b5",
+	"dpup": "-a7",
+	"dpleft":"-a6",
+	"dpdown": "+a7",
+	"dpright": "+a6",
+	"leftx": "a0",
+	"lefty": "a1",
+	"rightx": "a3",
+	"righty": "a4",
+	"lefttrigger": "a2",
+	"righttrigger": "a5",
+}
+
+const XBOX_OSX = {
+	"a": "b11",
+	"b": "b12",
+	"y": "b14",
+	"x": "b13",
+	"start": "b4",
+	"back": "b5",
+	"leftstick": "b6",
+	"rightstick": "b7",
+	"leftshoulder": "b8",
+	"rightshoulder": "b9",
+	"dpup": "b0",
+	"dpleft": "b2",
+	"dpdown": "b1",
+	"dpright": "b3",
+	"leftx": "a0",
+	"lefty": "a1",
+	"rightx": "a2",
+	"righty": "a3",
+	"lefttrigger": "a4",
+	"righttrigger":"a5",
+}
+
+var type = TYPE.NONE
+var idx = -1
+var axis = AXIS.FULL
+var inverted = false
+
+
+func _init(p_type = TYPE.NONE, p_idx = -1, p_axis = AXIS.FULL):
+	type = p_type
+	idx = p_idx
+	axis = p_axis
+
+
+func _to_string():
+	if type == TYPE.NONE:
+		return ""
+	var ts = "b" if type == TYPE.BTN else "a"
+	var prefix = ""
+	var suffix = "~" if inverted else ""
+	match axis:
+		AXIS.HALF_PLUS:
+			prefix = "+"
+		AXIS.HALF_MINUS:
+			prefix = "-"
+	return "%s%s%d%s" % [prefix, ts, idx, suffix]
+
+
+func to_human_string():
+	if type == TYPE.BTN:
+		return "Button %d" % idx
+	if type == TYPE.AXIS:
+		var prefix = ""
+		match axis:
+			AXIS.HALF_PLUS:
+				prefix = "(+) "
+			AXIS.HALF_MINUS:
+				prefix = "(-) "
+		var suffix = " (inverted)" if inverted else ""
+		return "Axis %s%d%s" % [prefix, idx, suffix]
+	return ""

+ 189 - 0
misc/joypads/remap/remap_wizard.gd

@@ -0,0 +1,189 @@
+extends Node
+
+
+const DEADZONE = 0.3
+
+var joy_guid = ""
+var joy_name = ""
+
+var steps = JoyMapping.BASE.keys()
+var cur_step = -1
+var cur_mapping = {}
+var last_mapping = ""
+
+onready var joy_buttons = $Mapping/Margin/VBox/ViewportContainer/Viewport/JoypadDiagram/Buttons
+onready var joy_axes = $Mapping/Margin/VBox/ViewportContainer/Viewport/JoypadDiagram/Axes
+onready var joy_mapping_text = $Mapping/Margin/VBox/Info/Text/Value
+onready var joy_mapping_full_axis = $Mapping/Margin/VBox/Info/Extra/FullAxis
+onready var joy_mapping_axis_invert = $Mapping/Margin/VBox/Info/Extra/InvertAxis
+
+
+func _input(event):
+	if cur_step == -1:
+		return
+	if event is InputEventJoypadMotion:
+		get_tree().set_input_as_handled()
+		var motion = event as InputEventJoypadMotion
+		if abs(motion.axis_value) > DEADZONE:
+			var idx = motion.axis
+			var map = JoyMapping.new(JoyMapping.TYPE.AXIS, idx)
+			map.inverted = joy_mapping_axis_invert.pressed
+			if joy_mapping_full_axis.pressed:
+				map.axis = JoyMapping.AXIS.FULL
+			else:
+				var plus = motion.axis_value > 0
+				map.axis = JoyMapping.AXIS.HALF_PLUS if plus else JoyMapping.AXIS.HALF_MINUS
+			joy_mapping_text.text = map.to_human_string()
+			cur_mapping[steps[cur_step]] = map
+	elif event is InputEventJoypadButton and event.pressed:
+		get_tree().set_input_as_handled()
+		var btn = event as InputEventJoypadButton
+		var map = JoyMapping.new(JoyMapping.TYPE.BTN, btn.button_index)
+		joy_mapping_text.text = map.to_human_string()
+		cur_mapping[steps[cur_step]] = map
+
+
+func create_mapping_string(mapping):
+	var string = "%s,%s," % [joy_guid, joy_name]
+	for k in mapping:
+		var m = mapping[k]
+		if typeof(m) == TYPE_OBJECT and m.type == JoyMapping.TYPE.NONE:
+			continue
+		string += "%s:%s," % [k, str(m)]
+	var platform = "Unknown"
+	if JoyMapping.PLATFORMS.keys().has(OS.get_name()):
+		platform = JoyMapping.PLATFORMS[OS.get_name()]
+	return string + "platform:" + platform
+
+
+func start(idx):
+	joy_guid = Input.get_joy_guid(idx)
+	joy_name = Input.get_joy_name(idx)
+	if joy_guid.empty():
+		printerr("Unable to find controller")
+		return
+	if OS.get_name() == "HTML5":
+		# Propose trying known mapping on HTML5.
+		$Start.window_title = "%s - %s" % [joy_guid, joy_name]
+		$Start.popup_centered()
+	else:
+		# Run wizard directly.
+		_on_Wizard_pressed()
+
+
+func remap_and_close(mapping):
+	last_mapping = create_mapping_string(mapping)
+	Input.add_joy_mapping(last_mapping, true)
+	reset()
+	show_map()
+
+
+func reset():
+	$Start.hide()
+	$Mapping.hide()
+	joy_guid = ""
+	joy_name = ""
+	cur_mapping = {}
+	cur_step = -1
+
+
+func step_next():
+	$Mapping.window_title = "Step: %d/%d" % [cur_step + 1, steps.size()]
+	joy_mapping_text.text = ""
+	if cur_step >= steps.size():
+		remap_and_close(cur_mapping)
+	else:
+		_update_step()
+
+
+func show_map():
+	if OS.get_name() == "HTML5":
+		JavaScript.eval("window.prompt('This is the resulting remap string', '" + last_mapping + "')")
+	else:
+		$MapWindow/Margin/VBoxContainer/TextEdit.text = last_mapping
+		$MapWindow.popup_centered()
+
+
+func _update_step():
+	$Mapping/Margin/VBox/Info/Buttons/Next.grab_focus()
+	for btn in joy_buttons.get_children():
+		btn.hide()
+	for axis in joy_axes.get_children():
+		axis.hide()
+	var key = steps[cur_step]
+	var idx = JoyMapping.BASE[key]
+	if key in ["leftx", "lefty", "rightx", "righty"]:
+		joy_axes.get_node(str(idx) + "+").show()
+		joy_axes.get_node(str(idx) + "-").show()
+	else:
+		joy_buttons.get_node(str(idx)).show()
+
+	joy_mapping_full_axis.pressed = key in ["leftx", "lefty", "rightx", "righty", "righttrigger", "lefttrigger"]
+	joy_mapping_axis_invert.pressed = false
+	if cur_mapping.has(key):
+		var cur = cur_mapping[steps[cur_step]]
+		joy_mapping_text.text = cur.to_human_string()
+		if cur.type == JoyMapping.TYPE.AXIS:
+			joy_mapping_full_axis.pressed = cur.axis == JoyMapping.AXIS.FULL
+			joy_mapping_axis_invert.pressed = cur.inverted
+
+
+func _on_Wizard_pressed():
+	Input.remove_joy_mapping(joy_guid)
+	$Start.hide()
+	$Mapping.popup_centered()
+	cur_step = 0
+	step_next()
+
+
+func _on_Cancel_pressed():
+	reset()
+
+
+func _on_xbox_pressed():
+	remap_and_close(JoyMapping.XBOX)
+
+
+func _on_xboxosx_pressed():
+	remap_and_close(JoyMapping.XBOX_OSX)
+
+
+func _on_Mapping_popup_hide():
+	reset()
+
+
+func _on_Next_pressed():
+	cur_step += 1
+	step_next()
+
+
+func _on_Prev_pressed():
+	if cur_step > 0:
+		cur_step -= 1
+		step_next()
+
+
+func _on_Skip_pressed():
+	var key = steps[cur_step]
+	if cur_mapping.has(key):
+		cur_mapping.erase(key)
+	cur_step += 1
+	step_next()
+
+
+func _on_FullAxis_toggled(button_pressed):
+	if cur_step == -1 or not button_pressed:
+		return
+	var key = steps[cur_step]
+	if cur_mapping.has(key) and cur_mapping[key].type == JoyMapping.TYPE.AXIS:
+		cur_mapping[key].axis = JoyMapping.AXIS.FULL
+		joy_mapping_text.text = cur_mapping[key].to_human_string()
+
+
+func _on_InvertAxis_toggled(button_pressed):
+	if cur_step == -1:
+		return
+	var key = steps[cur_step]
+	if cur_mapping.has(key) and cur_mapping[key].type == JoyMapping.TYPE.AXIS:
+		cur_mapping[key].inverted = button_pressed
+		joy_mapping_text.text = cur_mapping[key].to_human_string()

+ 277 - 0
misc/joypads/remap/remap_wizard.tscn

@@ -0,0 +1,277 @@
+[gd_scene load_steps=3 format=2]
+
+[ext_resource path="res://remap/remap_wizard.gd" type="Script" id=1]
+[ext_resource path="res://joypad_diagram.tscn" type="PackedScene" id=2]
+
+[node name="RemapWizard" type="Node"]
+script = ExtResource( 1 )
+
+[node name="Start" type="WindowDialog" parent="."]
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+margin_left = -128.0
+margin_top = -96.0
+margin_right = 128.0
+margin_bottom = 96.0
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Margin" type="MarginContainer" parent="Start"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+custom_constants/margin_right = 12
+custom_constants/margin_top = 12
+custom_constants/margin_left = 12
+custom_constants/margin_bottom = 12
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Layout" type="VBoxContainer" parent="Start/Margin"]
+margin_left = 12.0
+margin_top = 12.0
+margin_right = 244.0
+margin_bottom = 180.0
+custom_constants/separation = 20
+alignment = 1
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="HTML5" type="VBoxContainer" parent="Start/Margin/Layout"]
+margin_top = 28.0
+margin_right = 232.0
+margin_bottom = 66.0
+
+[node name="Label" type="Label" parent="Start/Margin/Layout/HTML5"]
+margin_right = 232.0
+margin_bottom = 14.0
+text = "Try a common mapping:"
+
+[node name="known" type="HBoxContainer" parent="Start/Margin/Layout/HTML5"]
+margin_top = 18.0
+margin_right = 232.0
+margin_bottom = 38.0
+custom_constants/separation = 20
+alignment = 1
+
+[node name="Xbox" type="Button" parent="Start/Margin/Layout/HTML5/known"]
+margin_left = 44.0
+margin_right = 87.0
+margin_bottom = 20.0
+text = "Xbox"
+
+[node name="XboxOSX" type="Button" parent="Start/Margin/Layout/HTML5/known"]
+margin_left = 107.0
+margin_right = 188.0
+margin_bottom = 20.0
+text = "Xbox (OSX)"
+
+[node name="Label" type="Label" parent="Start/Margin/Layout"]
+margin_top = 86.0
+margin_right = 232.0
+margin_bottom = 100.0
+text = "Or start the wizard"
+
+[node name="Buttons" type="HBoxContainer" parent="Start/Margin/Layout"]
+margin_top = 120.0
+margin_right = 232.0
+margin_bottom = 140.0
+
+[node name="Cancel" type="Button" parent="Start/Margin/Layout/Buttons"]
+margin_right = 54.0
+margin_bottom = 20.0
+text = "Cancel"
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Control" type="Control" parent="Start/Margin/Layout/Buttons"]
+margin_left = 58.0
+margin_right = 172.0
+margin_bottom = 20.0
+size_flags_horizontal = 3
+
+[node name="Wizard" type="Button" parent="Start/Margin/Layout/Buttons"]
+margin_left = 176.0
+margin_right = 232.0
+margin_bottom = 20.0
+text = "Wizard"
+
+[node name="Mapping" type="WindowDialog" parent="."]
+margin_right = 305.0
+margin_bottom = 437.0
+rect_min_size = Vector2( 330, 440 )
+popup_exclusive = true
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Margin" type="MarginContainer" parent="Mapping"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+custom_constants/margin_right = 12
+custom_constants/margin_top = 12
+custom_constants/margin_left = 12
+custom_constants/margin_bottom = 12
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="VBox" type="VBoxContainer" parent="Mapping/Margin"]
+margin_left = 12.0
+margin_top = 12.0
+margin_right = 318.0
+margin_bottom = 428.0
+custom_constants/separation = 5
+
+[node name="ViewportContainer" type="ViewportContainer" parent="Mapping/Margin/VBox"]
+margin_right = 306.0
+margin_bottom = 305.0
+rect_min_size = Vector2( 305, 305 )
+stretch = true
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Viewport" type="Viewport" parent="Mapping/Margin/VBox/ViewportContainer"]
+size = Vector2( 306, 305 )
+handle_input_locally = false
+render_target_update_mode = 0
+
+[node name="JoypadDiagram" parent="Mapping/Margin/VBox/ViewportContainer/Viewport" instance=ExtResource( 2 )]
+position = Vector2( 0, 0 )
+
+[node name="Camera2D" type="Camera2D" parent="Mapping/Margin/VBox/ViewportContainer/Viewport"]
+current = true
+
+[node name="Info" type="VBoxContainer" parent="Mapping/Margin/VBox"]
+margin_top = 310.0
+margin_right = 306.0
+margin_bottom = 408.0
+custom_constants/separation = 20
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Text" type="HBoxContainer" parent="Mapping/Margin/VBox/Info"]
+margin_right = 306.0
+margin_bottom = 14.0
+
+[node name="Text" type="Label" parent="Mapping/Margin/VBox/Info/Text"]
+margin_right = 125.0
+margin_bottom = 14.0
+text = "Currently selected: "
+
+[node name="Value" type="Label" parent="Mapping/Margin/VBox/Info/Text"]
+margin_left = 129.0
+margin_right = 129.0
+margin_bottom = 14.0
+
+[node name="Extra" type="HBoxContainer" parent="Mapping/Margin/VBox/Info"]
+margin_top = 34.0
+margin_right = 306.0
+margin_bottom = 58.0
+
+[node name="FullAxis" type="CheckBox" parent="Mapping/Margin/VBox/Info/Extra"]
+margin_right = 80.0
+margin_bottom = 24.0
+hint_tooltip = "Check this if the sign of the axis keep changing from + to - when pressing and releasing.
+Do not check it otherwise"
+text = "Full axis"
+
+[node name="InvertAxis" type="CheckBox" parent="Mapping/Margin/VBox/Info/Extra"]
+margin_left = 84.0
+margin_right = 181.0
+margin_bottom = 24.0
+hint_tooltip = "Check this if you think the axis should be inverted"
+text = "Invert Axis"
+
+[node name="Buttons" type="HBoxContainer" parent="Mapping/Margin/VBox/Info"]
+margin_top = 78.0
+margin_right = 306.0
+margin_bottom = 98.0
+
+[node name="Prev" type="Button" parent="Mapping/Margin/VBox/Info/Buttons"]
+margin_right = 67.0
+margin_bottom = 20.0
+text = "Previous"
+
+[node name="Control" type="Control" parent="Mapping/Margin/VBox/Info/Buttons"]
+margin_left = 71.0
+margin_right = 142.0
+margin_bottom = 20.0
+size_flags_horizontal = 3
+
+[node name="Skip" type="Button" parent="Mapping/Margin/VBox/Info/Buttons"]
+margin_left = 146.0
+margin_right = 184.0
+margin_bottom = 20.0
+text = "Skip"
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Control2" type="Control" parent="Mapping/Margin/VBox/Info/Buttons"]
+margin_left = 188.0
+margin_right = 260.0
+margin_bottom = 20.0
+size_flags_horizontal = 3
+
+[node name="Next" type="Button" parent="Mapping/Margin/VBox/Info/Buttons"]
+margin_left = 264.0
+margin_right = 306.0
+margin_bottom = 20.0
+text = "Next"
+
+[node name="MapWindow" type="WindowDialog" parent="."]
+margin_right = 400.0
+margin_bottom = 200.0
+popup_exclusive = true
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Margin" type="MarginContainer" parent="MapWindow"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+custom_constants/margin_right = 12
+custom_constants/margin_top = 12
+custom_constants/margin_left = 12
+custom_constants/margin_bottom = 12
+
+[node name="VBoxContainer" type="VBoxContainer" parent="MapWindow/Margin"]
+margin_left = 12.0
+margin_top = 12.0
+margin_right = 388.0
+margin_bottom = 188.0
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Label" type="Label" parent="MapWindow/Margin/VBoxContainer"]
+margin_right = 376.0
+margin_bottom = 14.0
+text = "This is the resulting remap string:"
+
+[node name="TextEdit" type="TextEdit" parent="MapWindow/Margin/VBoxContainer"]
+margin_top = 18.0
+margin_right = 376.0
+margin_bottom = 176.0
+size_flags_vertical = 3
+readonly = true
+wrap_enabled = true
+
+[connection signal="pressed" from="Start/Margin/Layout/HTML5/known/Xbox" to="." method="_on_xbox_pressed"]
+[connection signal="pressed" from="Start/Margin/Layout/HTML5/known/XboxOSX" to="." method="_on_xboxosx_pressed"]
+[connection signal="pressed" from="Start/Margin/Layout/Buttons/Cancel" to="." method="_on_Cancel_pressed"]
+[connection signal="pressed" from="Start/Margin/Layout/Buttons/Wizard" to="." method="_on_Wizard_pressed"]
+[connection signal="popup_hide" from="Mapping" to="." method="_on_Mapping_popup_hide"]
+[connection signal="toggled" from="Mapping/Margin/VBox/Info/Extra/FullAxis" to="." method="_on_FullAxis_toggled"]
+[connection signal="toggled" from="Mapping/Margin/VBox/Info/Extra/InvertAxis" to="." method="_on_InvertAxis_toggled"]
+[connection signal="pressed" from="Mapping/Margin/VBox/Info/Buttons/Prev" to="." method="_on_Prev_pressed"]
+[connection signal="pressed" from="Mapping/Margin/VBox/Info/Buttons/Skip" to="." method="_on_Skip_pressed"]
+[connection signal="pressed" from="Mapping/Margin/VBox/Info/Buttons/Next" to="." method="_on_Next_pressed"]