class_name ControlsEditor extends PanelContainer var ui: GameUI var settings_path := ControlBindings.PATH var rows := VBoxContainer.new() var notice := Label.new() var pending_action := "" var pending_slot := 0 var candidate: Dictionary = {} var swap_button := Button.new() func _ready() -> void: set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) var background := StyleBoxFlat.new() background.bg_color = Color("101d26") add_theme_stylebox_override("panel", background) var margin := MarginContainer.new() for side: String in ["left", "right", "top", "bottom"]: margin.add_theme_constant_override("margin_" + side, 24) add_child(margin) var column := VBoxContainer.new() column.add_theme_constant_override("separation", 12) margin.add_child(column) var heading := Label.new() heading.text = "STEUERUNG / TASTENBELEGUNG" heading.add_theme_font_size_override("font_size", 28) column.add_child(heading) notice.text = "Belegung anklicken, dann Taste oder Maustaste drücken. Esc bricht die Eingabe ab." notice.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART column.add_child(notice) swap_button.text = "BELEGUNGEN TAUSCHEN" swap_button.hide() swap_button.pressed.connect(func(): finish(true)) column.add_child(swap_button) var scroll := ScrollContainer.new() scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL column.add_child(scroll) rows.size_flags_horizontal = Control.SIZE_EXPAND_FILL scroll.add_child(rows) rebuild() var footer := HBoxContainer.new() column.add_child(footer) var reset := Button.new() reset.text = "STANDARD WIEDERHERSTELLEN" reset.pressed.connect(func(): pending_action = "" swap_button.hide() ControlBindings.defaults() persist() rebuild()) footer.add_child(reset) var back := Button.new() back.text = "← ZURÜCK" back.pressed.connect(ui.settings_menu) footer.add_child(back) func rebuild() -> void: for child in rows.get_children(): rows.remove_child(child) child.queue_free() for index in ControlBindings.ACTIONS.size(): var action: String = ControlBindings.ACTIONS[index] var row := HBoxContainer.new() rows.add_child(row) var label := Label.new() label.text = ControlBindings.LABELS[index] label.custom_minimum_size.x = 300 label.size_flags_horizontal = Control.SIZE_EXPAND_FILL row.add_child(label) for slot in 2: var button := Button.new() button.text = ControlBindings.label(ControlBindings.bindings[action][slot]) button.custom_minimum_size = Vector2(180, 40) button.pressed.connect(func(): pending_action = action pending_slot = slot candidate = {} swap_button.hide() notice.text = "%s: neue Taste / Maustaste drücken … (Esc: abbrechen)" % ControlBindings.LABELS[index]) row.add_child(button) var clear := Button.new() clear.text = "×" clear.tooltip_text = "Diese Belegung entfernen" clear.pressed.connect(func(): pending_action = "" swap_button.hide() if ControlBindings.assign(action, slot, {}): persist() rebuild() else: notice.text = "Für das Menü muss mindestens eine Taste belegt bleiben.") row.add_child(clear) func _input(event: InputEvent) -> void: if pending_action.is_empty(): return if not (event is InputEventKey or event is InputEventMouseButton): return if not event.is_pressed() or event.is_echo(): return # A pending conflict has explicit buttons; don't capture their mouse clicks. if swap_button.visible: if event is InputEventKey and event.keycode == KEY_ESCAPE: pending_action = "" swap_button.hide() notice.text = "Abgebrochen. Die bisherigen Belegungen bleiben bestehen." get_viewport().set_input_as_handled() return get_viewport().set_input_as_handled() if event is InputEventKey and event.keycode == KEY_ESCAPE: pending_action = "" notice.text = "Eingabe abgebrochen." return candidate = ControlBindings.encode(event) if candidate.is_empty(): return var conflicts := ControlBindings.conflicts(pending_action, pending_slot, candidate) if not conflicts.is_empty(): var names := PackedStringArray() for conflict in conflicts: names.append(ControlBindings.LABELS[ControlBindings.ACTIONS.find(conflict.action)]) notice.text = "%s ist bereits belegt: %s. Tauschen oder mit Esc abbrechen." % [ControlBindings.label(candidate), ", ".join(names)] swap_button.show() else: finish(false) func finish(swap: bool) -> void: if ControlBindings.assign(pending_action, pending_slot, candidate, swap): persist() else: notice.text = "Diese Belegung ist nicht möglich: gehaltene Aktionen brauchen eine Taste / Maustaste; das Menü muss erreichbar bleiben." pending_action = "" swap_button.hide() rebuild() func persist() -> void: notice.text = "Gespeichert. Die Steuerung ist sofort aktiv." if ControlBindings.save(settings_path) == OK else "Speichern fehlgeschlagen. Die Änderung ist nur für diese Sitzung aktiv."