316 lines
9.2 KiB
GDScript3
316 lines
9.2 KiB
GDScript3
|
|
extends CanvasLayer
|
||
|
|
|
||
|
|
@export var jet_path: NodePath
|
||
|
|
@export var config_path: String = "user://hotas_mapping.cfg"
|
||
|
|
@export var axis_capture_threshold: float = 0.35
|
||
|
|
@export var toggle_action: String = "toggle_hotas_mapper"
|
||
|
|
|
||
|
|
@export var panel_position: Vector2 = Vector2(12, 12)
|
||
|
|
@export var panel_size: Vector2 = Vector2(520, 360)
|
||
|
|
|
||
|
|
const AXIS_CONFIG = [
|
||
|
|
{
|
||
|
|
"name": "Roll",
|
||
|
|
"axis_prop": "roll_axis",
|
||
|
|
"invert_prop": "roll_invert",
|
||
|
|
"device_prop": "roll_device_id",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"name": "Pitch",
|
||
|
|
"axis_prop": "pitch_axis",
|
||
|
|
"invert_prop": "pitch_invert",
|
||
|
|
"device_prop": "pitch_device_id",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"name": "Yaw",
|
||
|
|
"axis_prop": "yaw_axis",
|
||
|
|
"invert_prop": "yaw_invert",
|
||
|
|
"device_prop": "yaw_device_id",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"name": "Throttle",
|
||
|
|
"axis_prop": "throttle_axis",
|
||
|
|
"invert_prop": "throttle_invert",
|
||
|
|
"signed_prop": "throttle_signed",
|
||
|
|
"device_prop": "throttle_device_id",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"name": "Strafe",
|
||
|
|
"axis_prop": "strafe_axis",
|
||
|
|
"invert_prop": "strafe_invert",
|
||
|
|
"device_prop": "strafe_device_id",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"name": "Lift",
|
||
|
|
"axis_prop": "lift_axis",
|
||
|
|
"invert_prop": "lift_invert",
|
||
|
|
"device_prop": "lift_device_id",
|
||
|
|
},
|
||
|
|
]
|
||
|
|
|
||
|
|
var _jet: Node
|
||
|
|
var _panel: PanelContainer
|
||
|
|
var _status_label: Label
|
||
|
|
var _device_label: Label
|
||
|
|
var _rows := {}
|
||
|
|
var _listening_axis: String = ""
|
||
|
|
|
||
|
|
func _ready() -> void:
|
||
|
|
_jet = get_node_or_null(jet_path)
|
||
|
|
_build_ui()
|
||
|
|
_load_config()
|
||
|
|
_refresh_labels()
|
||
|
|
|
||
|
|
func _unhandled_input(event: InputEvent) -> void:
|
||
|
|
if _is_toggle_event(event):
|
||
|
|
visible = not visible
|
||
|
|
if not visible:
|
||
|
|
_listening_axis = ""
|
||
|
|
_refresh_labels()
|
||
|
|
get_viewport().set_input_as_handled()
|
||
|
|
|
||
|
|
func _input(event: InputEvent) -> void:
|
||
|
|
if not visible:
|
||
|
|
return
|
||
|
|
if _listening_axis.is_empty():
|
||
|
|
return
|
||
|
|
if event is InputEventJoypadMotion:
|
||
|
|
if abs(event.axis_value) < axis_capture_threshold:
|
||
|
|
return
|
||
|
|
_apply_axis_mapping(_listening_axis, event.axis, event.device)
|
||
|
|
_listening_axis = ""
|
||
|
|
_status_label.text = "Axis captured."
|
||
|
|
_refresh_labels()
|
||
|
|
|
||
|
|
func _build_ui() -> void:
|
||
|
|
_panel = PanelContainer.new()
|
||
|
|
_panel.name = "HotasMapper"
|
||
|
|
_panel.anchor_left = 0.0
|
||
|
|
_panel.anchor_top = 0.0
|
||
|
|
_panel.anchor_right = 0.0
|
||
|
|
_panel.anchor_bottom = 0.0
|
||
|
|
_panel.offset_left = panel_position.x
|
||
|
|
_panel.offset_top = panel_position.y
|
||
|
|
_panel.offset_right = panel_position.x + panel_size.x
|
||
|
|
_panel.offset_bottom = panel_position.y + panel_size.y
|
||
|
|
add_child(_panel)
|
||
|
|
|
||
|
|
var root := VBoxContainer.new()
|
||
|
|
root.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||
|
|
root.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
|
|
root.add_theme_constant_override("separation", 8)
|
||
|
|
_panel.add_child(root)
|
||
|
|
|
||
|
|
var header := HBoxContainer.new()
|
||
|
|
header.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
|
|
root.add_child(header)
|
||
|
|
|
||
|
|
var title := Label.new()
|
||
|
|
title.text = "HOTAS Axis Mapper"
|
||
|
|
header.add_child(title)
|
||
|
|
|
||
|
|
var spacer := Control.new()
|
||
|
|
spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
|
|
header.add_child(spacer)
|
||
|
|
|
||
|
|
var close_button := Button.new()
|
||
|
|
close_button.text = "Close"
|
||
|
|
close_button.pressed.connect(_on_close_pressed)
|
||
|
|
header.add_child(close_button)
|
||
|
|
|
||
|
|
_device_label = Label.new()
|
||
|
|
_device_label.text = _get_device_text()
|
||
|
|
root.add_child(_device_label)
|
||
|
|
|
||
|
|
_status_label = Label.new()
|
||
|
|
_status_label.text = "Click Map, then move the desired axis. Toggle: %s" % _get_toggle_hint()
|
||
|
|
root.add_child(_status_label)
|
||
|
|
|
||
|
|
for config in AXIS_CONFIG:
|
||
|
|
root.add_child(_build_axis_row(config))
|
||
|
|
|
||
|
|
func _build_axis_row(config: Dictionary) -> Control:
|
||
|
|
var row := HBoxContainer.new()
|
||
|
|
row.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
|
|
row.add_theme_constant_override("separation", 8)
|
||
|
|
|
||
|
|
var name_label := Label.new()
|
||
|
|
name_label.text = config.name
|
||
|
|
name_label.custom_minimum_size = Vector2(120, 0)
|
||
|
|
row.add_child(name_label)
|
||
|
|
|
||
|
|
var map_button := Button.new()
|
||
|
|
map_button.text = "Map"
|
||
|
|
map_button.pressed.connect(_on_map_pressed.bind(config.axis_prop))
|
||
|
|
row.add_child(map_button)
|
||
|
|
|
||
|
|
var axis_label := Label.new()
|
||
|
|
axis_label.text = "Axis: ?"
|
||
|
|
axis_label.custom_minimum_size = Vector2(120, 0)
|
||
|
|
row.add_child(axis_label)
|
||
|
|
|
||
|
|
var device_label := Label.new()
|
||
|
|
device_label.text = "Dev: ?"
|
||
|
|
device_label.custom_minimum_size = Vector2(160, 0)
|
||
|
|
row.add_child(device_label)
|
||
|
|
|
||
|
|
var invert_check := CheckBox.new()
|
||
|
|
invert_check.text = "Invert"
|
||
|
|
invert_check.toggled.connect(_on_invert_toggled.bind(config.invert_prop))
|
||
|
|
row.add_child(invert_check)
|
||
|
|
|
||
|
|
var signed_check: CheckBox = null
|
||
|
|
if config.has("signed_prop"):
|
||
|
|
signed_check = CheckBox.new()
|
||
|
|
signed_check.text = "Signed"
|
||
|
|
signed_check.toggled.connect(_on_signed_toggled.bind(config.signed_prop))
|
||
|
|
row.add_child(signed_check)
|
||
|
|
|
||
|
|
_rows[config.axis_prop] = {
|
||
|
|
"axis_label": axis_label,
|
||
|
|
"device_label": device_label,
|
||
|
|
"map_button": map_button,
|
||
|
|
"invert_check": invert_check,
|
||
|
|
"signed_check": signed_check,
|
||
|
|
}
|
||
|
|
return row
|
||
|
|
|
||
|
|
func _on_map_pressed(axis_prop: String) -> void:
|
||
|
|
if _listening_axis == axis_prop:
|
||
|
|
_listening_axis = ""
|
||
|
|
_status_label.text = "Mapping cancelled."
|
||
|
|
else:
|
||
|
|
_listening_axis = axis_prop
|
||
|
|
_status_label.text = "Listening for %s axis..." % axis_prop
|
||
|
|
_refresh_labels()
|
||
|
|
|
||
|
|
func _on_close_pressed() -> void:
|
||
|
|
_listening_axis = ""
|
||
|
|
visible = false
|
||
|
|
|
||
|
|
func _on_invert_toggled(pressed: bool, invert_prop: String) -> void:
|
||
|
|
if _jet == null:
|
||
|
|
return
|
||
|
|
_jet.set(invert_prop, pressed)
|
||
|
|
_save_config()
|
||
|
|
|
||
|
|
func _on_signed_toggled(pressed: bool, signed_prop: String) -> void:
|
||
|
|
if _jet == null:
|
||
|
|
return
|
||
|
|
_jet.set(signed_prop, pressed)
|
||
|
|
_save_config()
|
||
|
|
|
||
|
|
func _apply_axis_mapping(axis_prop: String, axis_index: int, device_id: int) -> void:
|
||
|
|
if _jet == null:
|
||
|
|
return
|
||
|
|
_jet.set(axis_prop, axis_index)
|
||
|
|
if device_id >= 0:
|
||
|
|
var device_prop = _get_device_prop(axis_prop)
|
||
|
|
if not device_prop.is_empty():
|
||
|
|
_jet.set(device_prop, device_id)
|
||
|
|
_jet.set("joy_device_id", device_id)
|
||
|
|
_save_config()
|
||
|
|
_device_label.text = _get_device_text()
|
||
|
|
|
||
|
|
func _refresh_labels() -> void:
|
||
|
|
if _jet == null:
|
||
|
|
return
|
||
|
|
_device_label.text = _get_device_text()
|
||
|
|
for config in AXIS_CONFIG:
|
||
|
|
var axis_prop: String = config.axis_prop
|
||
|
|
var row = _rows.get(axis_prop, null)
|
||
|
|
if row == null:
|
||
|
|
continue
|
||
|
|
row["axis_label"].text = "Axis: %d" % int(_jet.get(axis_prop))
|
||
|
|
var device_prop: String = config.device_prop
|
||
|
|
var device_id: int = int(_jet.get(device_prop))
|
||
|
|
row["device_label"].text = _get_device_label(device_id)
|
||
|
|
var invert_prop: String = config.invert_prop
|
||
|
|
row["invert_check"].set_pressed_no_signal(bool(_jet.get(invert_prop)))
|
||
|
|
if config.has("signed_prop") and row["signed_check"] != null:
|
||
|
|
row["signed_check"].set_pressed_no_signal(bool(_jet.get(config.signed_prop)))
|
||
|
|
var map_button: Button = row["map_button"]
|
||
|
|
if _listening_axis == axis_prop:
|
||
|
|
map_button.text = "Listening..."
|
||
|
|
else:
|
||
|
|
map_button.text = "Map"
|
||
|
|
|
||
|
|
func _load_config() -> void:
|
||
|
|
if _jet == null:
|
||
|
|
return
|
||
|
|
var config := ConfigFile.new()
|
||
|
|
var err = config.load(config_path)
|
||
|
|
if err != OK:
|
||
|
|
return
|
||
|
|
for entry in AXIS_CONFIG:
|
||
|
|
var axis_prop: String = entry.axis_prop
|
||
|
|
if config.has_section_key("axes", axis_prop):
|
||
|
|
_jet.set(axis_prop, int(config.get_value("axes", axis_prop)))
|
||
|
|
var invert_prop: String = entry.invert_prop
|
||
|
|
if config.has_section_key("invert", invert_prop):
|
||
|
|
_jet.set(invert_prop, bool(config.get_value("invert", invert_prop)))
|
||
|
|
var device_prop: String = entry.device_prop
|
||
|
|
if config.has_section_key("devices", device_prop):
|
||
|
|
_jet.set(device_prop, int(config.get_value("devices", device_prop)))
|
||
|
|
if entry.has("signed_prop"):
|
||
|
|
var signed_prop: String = entry.signed_prop
|
||
|
|
if config.has_section_key("flags", signed_prop):
|
||
|
|
_jet.set(signed_prop, bool(config.get_value("flags", signed_prop)))
|
||
|
|
if config.has_section_key("device", "joy_device_id"):
|
||
|
|
_jet.set("joy_device_id", int(config.get_value("device", "joy_device_id")))
|
||
|
|
|
||
|
|
func _save_config() -> void:
|
||
|
|
if _jet == null:
|
||
|
|
return
|
||
|
|
var config := ConfigFile.new()
|
||
|
|
for entry in AXIS_CONFIG:
|
||
|
|
var axis_prop: String = entry.axis_prop
|
||
|
|
config.set_value("axes", axis_prop, int(_jet.get(axis_prop)))
|
||
|
|
var invert_prop: String = entry.invert_prop
|
||
|
|
config.set_value("invert", invert_prop, bool(_jet.get(invert_prop)))
|
||
|
|
var device_prop: String = entry.device_prop
|
||
|
|
config.set_value("devices", device_prop, int(_jet.get(device_prop)))
|
||
|
|
if entry.has("signed_prop"):
|
||
|
|
var signed_prop: String = entry.signed_prop
|
||
|
|
config.set_value("flags", signed_prop, bool(_jet.get(signed_prop)))
|
||
|
|
config.set_value("device", "joy_device_id", int(_jet.get("joy_device_id")))
|
||
|
|
config.save(config_path)
|
||
|
|
|
||
|
|
func _get_device_text() -> String:
|
||
|
|
if _jet == null:
|
||
|
|
return "Device: none"
|
||
|
|
var device_id: int = int(_jet.get("joy_device_id"))
|
||
|
|
if device_id < 0:
|
||
|
|
var pads = Input.get_connected_joypads()
|
||
|
|
if pads.is_empty():
|
||
|
|
return "Device: none"
|
||
|
|
device_id = pads[0]
|
||
|
|
var name = Input.get_joy_name(device_id)
|
||
|
|
return "Device: %s (id %d)" % [name, device_id]
|
||
|
|
|
||
|
|
func _get_device_label(device_id: int) -> String:
|
||
|
|
if device_id < 0:
|
||
|
|
return "Dev: auto"
|
||
|
|
var name = Input.get_joy_name(device_id)
|
||
|
|
if name.is_empty():
|
||
|
|
return "Dev: %d" % device_id
|
||
|
|
return "Dev: %s (%d)" % [name, device_id]
|
||
|
|
|
||
|
|
func _get_device_prop(axis_prop: String) -> String:
|
||
|
|
for entry in AXIS_CONFIG:
|
||
|
|
if entry.axis_prop == axis_prop:
|
||
|
|
return entry.device_prop
|
||
|
|
return ""
|
||
|
|
|
||
|
|
func _is_toggle_event(event: InputEvent) -> bool:
|
||
|
|
if InputMap.has_action(toggle_action):
|
||
|
|
return event.is_action_pressed(toggle_action)
|
||
|
|
if event is InputEventKey and event.pressed and not event.echo:
|
||
|
|
return event.keycode == KEY_F1
|
||
|
|
return false
|
||
|
|
|
||
|
|
func _get_toggle_hint() -> String:
|
||
|
|
if InputMap.has_action(toggle_action):
|
||
|
|
return toggle_action
|
||
|
|
return "F1"
|