diff --git a/misc/joypads/joypads.gd b/misc/joypads/joypads.gd index 3011af22..3dc7eb03 100644 --- a/misc/joypads/joypads.gd +++ b/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() diff --git a/misc/joypads/joypads.tscn b/misc/joypads/joypads.tscn index 51589f35..339c26f9 100644 --- a/misc/joypads/joypads.tscn +++ b/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"] diff --git a/misc/joypads/project.godot b/misc/joypads/project.godot index 90eec91d..24645499 100644 --- a/misc/joypads/project.godot +++ b/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] diff --git a/misc/joypads/remap/joy_mapping.gd b/misc/joypads/remap/joy_mapping.gd new file mode 100644 index 00000000..a410b54b --- /dev/null +++ b/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 "" diff --git a/misc/joypads/remap/remap_wizard.gd b/misc/joypads/remap/remap_wizard.gd new file mode 100644 index 00000000..3c3866a5 --- /dev/null +++ b/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() diff --git a/misc/joypads/remap/remap_wizard.tscn b/misc/joypads/remap/remap_wizard.tscn new file mode 100644 index 00000000..017b55a2 --- /dev/null +++ b/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"]