mirror of
				https://github.com/godotengine/godot.git
				synced 2025-10-31 13:41:03 +00:00 
			
		
		
		
	
		
			
	
	
		
			701 lines
		
	
	
	
		
			25 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
		
		
			
		
	
	
			701 lines
		
	
	
	
		
			25 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
|   | /*************************************************************************/ | ||
|  | /*  input_event_configuration_dialog.cpp                                 */ | ||
|  | /*************************************************************************/ | ||
|  | /*                       This file is part of:                           */ | ||
|  | /*                           GODOT ENGINE                                */ | ||
|  | /*                      https://godotengine.org                          */ | ||
|  | /*************************************************************************/ | ||
|  | /* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */ | ||
|  | /* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */ | ||
|  | /*                                                                       */ | ||
|  | /* Permission is hereby granted, free of charge, to any person obtaining */ | ||
|  | /* a copy of this software and associated documentation files (the       */ | ||
|  | /* "Software"), to deal in the Software without restriction, including   */ | ||
|  | /* without limitation the rights to use, copy, modify, merge, publish,   */ | ||
|  | /* distribute, sublicense, and/or sell copies of the Software, and to    */ | ||
|  | /* permit persons to whom the Software is furnished to do so, subject to */ | ||
|  | /* the following conditions:                                             */ | ||
|  | /*                                                                       */ | ||
|  | /* The above copyright notice and this permission notice shall be        */ | ||
|  | /* included in all copies or substantial portions of the Software.       */ | ||
|  | /*                                                                       */ | ||
|  | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */ | ||
|  | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */ | ||
|  | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ | ||
|  | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */ | ||
|  | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */ | ||
|  | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */ | ||
|  | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */ | ||
|  | /*************************************************************************/ | ||
|  | 
 | ||
|  | #include "editor/input_event_configuration_dialog.h"
 | ||
|  | #include "core/input/input_map.h"
 | ||
|  | #include "editor/editor_scale.h"
 | ||
|  | #include "editor/event_listener_line_edit.h"
 | ||
|  | #include "scene/gui/check_box.h"
 | ||
|  | #include "scene/gui/line_edit.h"
 | ||
|  | #include "scene/gui/option_button.h"
 | ||
|  | #include "scene/gui/separator.h"
 | ||
|  | #include "scene/gui/tree.h"
 | ||
|  | 
 | ||
|  | // Maps to 2*axis if value is neg, or 2*axis+1 if value is pos.
 | ||
|  | static const char *_joy_axis_descriptions[(size_t)JoyAxis::MAX * 2] = { | ||
|  | 	TTRC("Left Stick Left, Joystick 0 Left"), | ||
|  | 	TTRC("Left Stick Right, Joystick 0 Right"), | ||
|  | 	TTRC("Left Stick Up, Joystick 0 Up"), | ||
|  | 	TTRC("Left Stick Down, Joystick 0 Down"), | ||
|  | 	TTRC("Right Stick Left, Joystick 1 Left"), | ||
|  | 	TTRC("Right Stick Right, Joystick 1 Right"), | ||
|  | 	TTRC("Right Stick Up, Joystick 1 Up"), | ||
|  | 	TTRC("Right Stick Down, Joystick 1 Down"), | ||
|  | 	TTRC("Joystick 2 Left"), | ||
|  | 	TTRC("Left Trigger, Sony L2, Xbox LT, Joystick 2 Right"), | ||
|  | 	TTRC("Joystick 2 Up"), | ||
|  | 	TTRC("Right Trigger, Sony R2, Xbox RT, Joystick 2 Down"), | ||
|  | 	TTRC("Joystick 3 Left"), | ||
|  | 	TTRC("Joystick 3 Right"), | ||
|  | 	TTRC("Joystick 3 Up"), | ||
|  | 	TTRC("Joystick 3 Down"), | ||
|  | 	TTRC("Joystick 4 Left"), | ||
|  | 	TTRC("Joystick 4 Right"), | ||
|  | 	TTRC("Joystick 4 Up"), | ||
|  | 	TTRC("Joystick 4 Down"), | ||
|  | }; | ||
|  | 
 | ||
|  | String InputEventConfigurationDialog::get_event_text(const Ref<InputEvent> &p_event, bool p_include_device) const { | ||
|  | 	ERR_FAIL_COND_V_MSG(p_event.is_null(), String(), "Provided event is not a valid instance of InputEvent"); | ||
|  | 
 | ||
|  | 	String text = p_event->as_text(); | ||
|  | 
 | ||
|  | 	Ref<InputEventKey> key = p_event; | ||
|  | 	if (key.is_valid() && key->is_command_or_control_autoremap()) { | ||
|  | #ifdef MACOS_ENABLED
 | ||
|  | 		text = text.replace("Command", "Command/Ctrl"); | ||
|  | #else
 | ||
|  | 		text = text.replace("Ctrl", "Command/Ctrl"); | ||
|  | #endif
 | ||
|  | 	} | ||
|  | 	Ref<InputEventMouse> mouse = p_event; | ||
|  | 	Ref<InputEventJoypadMotion> jp_motion = p_event; | ||
|  | 	Ref<InputEventJoypadButton> jp_button = p_event; | ||
|  | 	if (jp_motion.is_valid()) { | ||
|  | 		// Joypad motion events will display slightly differently than what the event->as_text() provides. See #43660.
 | ||
|  | 		String desc = TTR("Unknown Joypad Axis"); | ||
|  | 		if (jp_motion->get_axis() < JoyAxis::MAX) { | ||
|  | 			desc = RTR(_joy_axis_descriptions[2 * (size_t)jp_motion->get_axis() + (jp_motion->get_axis_value() < 0 ? 0 : 1)]); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		text = vformat("Joypad Axis %s %s (%s)", itos((int64_t)jp_motion->get_axis()), jp_motion->get_axis_value() < 0 ? "-" : "+", desc); | ||
|  | 	} | ||
|  | 	if (p_include_device && (mouse.is_valid() || jp_button.is_valid() || jp_motion.is_valid())) { | ||
|  | 		String device_string = _get_device_string(p_event->get_device()); | ||
|  | 		text += vformat(" - %s", device_string); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	return text; | ||
|  | } | ||
|  | 
 | ||
|  | void InputEventConfigurationDialog::_set_event(const Ref<InputEvent> &p_event, bool p_update_input_list_selection) { | ||
|  | 	if (p_event.is_valid()) { | ||
|  | 		event = p_event; | ||
|  | 
 | ||
|  | 		// If the event is changed to something which is not the same as the listener,
 | ||
|  | 		// clear out the event from the listener text box to avoid confusion.
 | ||
|  | 		const Ref<InputEvent> listener_event = event_listener->get_event(); | ||
|  | 		if (listener_event.is_valid() && !listener_event->is_match(p_event)) { | ||
|  | 			event_listener->clear_event(); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// Update Label
 | ||
|  | 		event_as_text->set_text(get_event_text(event, true)); | ||
|  | 
 | ||
|  | 		Ref<InputEventKey> k = p_event; | ||
|  | 		Ref<InputEventMouseButton> mb = p_event; | ||
|  | 		Ref<InputEventJoypadButton> joyb = p_event; | ||
|  | 		Ref<InputEventJoypadMotion> joym = p_event; | ||
|  | 		Ref<InputEventWithModifiers> mod = p_event; | ||
|  | 
 | ||
|  | 		// Update option values and visibility
 | ||
|  | 		bool show_mods = false; | ||
|  | 		bool show_device = false; | ||
|  | 		bool show_phys_key = false; | ||
|  | 
 | ||
|  | 		if (mod.is_valid()) { | ||
|  | 			show_mods = true; | ||
|  | 			mod_checkboxes[MOD_ALT]->set_pressed(mod->is_alt_pressed()); | ||
|  | 			mod_checkboxes[MOD_SHIFT]->set_pressed(mod->is_shift_pressed()); | ||
|  | 			mod_checkboxes[MOD_CTRL]->set_pressed(mod->is_ctrl_pressed()); | ||
|  | 			mod_checkboxes[MOD_META]->set_pressed(mod->is_meta_pressed()); | ||
|  | 
 | ||
|  | 			autoremap_command_or_control_checkbox->set_pressed(mod->is_command_or_control_autoremap()); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		if (k.is_valid()) { | ||
|  | 			show_phys_key = true; | ||
|  | 			physical_key_checkbox->set_pressed(k->get_physical_keycode() != Key::NONE && k->get_keycode() == Key::NONE); | ||
|  | 
 | ||
|  | 		} else if (joyb.is_valid() || joym.is_valid() || mb.is_valid()) { | ||
|  | 			show_device = true; | ||
|  | 			_set_current_device(event->get_device()); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		mod_container->set_visible(show_mods); | ||
|  | 		device_container->set_visible(show_device); | ||
|  | 		physical_key_checkbox->set_visible(show_phys_key); | ||
|  | 		additional_options_container->show(); | ||
|  | 
 | ||
|  | 		// Update selected item in input list.
 | ||
|  | 		if (p_update_input_list_selection && (k.is_valid() || joyb.is_valid() || joym.is_valid() || mb.is_valid())) { | ||
|  | 			TreeItem *category = input_list_tree->get_root()->get_first_child(); | ||
|  | 			while (category) { | ||
|  | 				TreeItem *input_item = category->get_first_child(); | ||
|  | 
 | ||
|  | 				if (input_item != nullptr) { | ||
|  | 					// input_type should always be > 0, unless the tree structure has been misconfigured.
 | ||
|  | 					int input_type = input_item->get_parent()->get_meta("__type", 0); | ||
|  | 					if (input_type == 0) { | ||
|  | 						return; | ||
|  | 					} | ||
|  | 
 | ||
|  | 					// If event type matches input types of this category.
 | ||
|  | 					if ((k.is_valid() && input_type == INPUT_KEY) || (joyb.is_valid() && input_type == INPUT_JOY_BUTTON) || (joym.is_valid() && input_type == INPUT_JOY_MOTION) || (mb.is_valid() && input_type == INPUT_MOUSE_BUTTON)) { | ||
|  | 						// Loop through all items of this category until one matches.
 | ||
|  | 						while (input_item) { | ||
|  | 							bool key_match = k.is_valid() && (Variant(k->get_keycode()) == input_item->get_meta("__keycode") || Variant(k->get_physical_keycode()) == input_item->get_meta("__keycode")); | ||
|  | 							bool joyb_match = joyb.is_valid() && Variant(joyb->get_button_index()) == input_item->get_meta("__index"); | ||
|  | 							bool joym_match = joym.is_valid() && Variant(joym->get_axis()) == input_item->get_meta("__axis") && joym->get_axis_value() == (float)input_item->get_meta("__value"); | ||
|  | 							bool mb_match = mb.is_valid() && Variant(mb->get_button_index()) == input_item->get_meta("__index"); | ||
|  | 							if (key_match || joyb_match || joym_match || mb_match) { | ||
|  | 								category->set_collapsed(false); | ||
|  | 								input_item->select(0); | ||
|  | 								input_list_tree->ensure_cursor_is_visible(); | ||
|  | 								return; | ||
|  | 							} | ||
|  | 							input_item = input_item->get_next(); | ||
|  | 						} | ||
|  | 					} | ||
|  | 				} | ||
|  | 
 | ||
|  | 				category->set_collapsed(true); // Event not in this category, so collapse;
 | ||
|  | 				category = category->get_next(); | ||
|  | 			} | ||
|  | 		} | ||
|  | 	} else { | ||
|  | 		// Event is not valid, reset dialog
 | ||
|  | 		event = p_event; | ||
|  | 		event_listener->clear_event(); | ||
|  | 		event_as_text->set_text(TTR("No Event Configured")); | ||
|  | 
 | ||
|  | 		additional_options_container->hide(); | ||
|  | 		input_list_tree->deselect_all(); | ||
|  | 		_update_input_list(); | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | void InputEventConfigurationDialog::_on_listen_input_changed(const Ref<InputEvent> &p_event) { | ||
|  | 	// Ignore if invalid, echo or not pressed
 | ||
|  | 	if (p_event.is_null() || p_event->is_echo() || !p_event->is_pressed()) { | ||
|  | 		return; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// Create an editable reference
 | ||
|  | 	Ref<InputEvent> received_event = p_event; | ||
|  | 	// Check what the type is and if it is allowed.
 | ||
|  | 	Ref<InputEventKey> k = received_event; | ||
|  | 	Ref<InputEventJoypadButton> joyb = received_event; | ||
|  | 	Ref<InputEventJoypadMotion> joym = received_event; | ||
|  | 	Ref<InputEventMouseButton> mb = received_event; | ||
|  | 
 | ||
|  | 	int type = 0; | ||
|  | 	if (k.is_valid()) { | ||
|  | 		type = INPUT_KEY; | ||
|  | 	} else if (joyb.is_valid()) { | ||
|  | 		type = INPUT_JOY_BUTTON; | ||
|  | 	} else if (joym.is_valid()) { | ||
|  | 		type = INPUT_JOY_MOTION; | ||
|  | 	} else if (mb.is_valid()) { | ||
|  | 		type = INPUT_MOUSE_BUTTON; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if (!(allowed_input_types & type)) { | ||
|  | 		return; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if (joym.is_valid()) { | ||
|  | 		float axis_value = joym->get_axis_value(); | ||
|  | 		if (ABS(axis_value) < 0.9) { | ||
|  | 			// Ignore motion below 0.9 magnitude to avoid accidental touches
 | ||
|  | 			return; | ||
|  | 		} else { | ||
|  | 			// Always make the value 1 or -1 for display consistency
 | ||
|  | 			joym->set_axis_value(SIGN(axis_value)); | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if (k.is_valid()) { | ||
|  | 		k->set_pressed(false); // To avoid serialisation of 'pressed' property - doesn't matter for actions anyway.
 | ||
|  | 		// Maintain physical keycode option state
 | ||
|  | 		if (physical_key_checkbox->is_pressed()) { | ||
|  | 			k->set_keycode(Key::NONE); | ||
|  | 		} else { | ||
|  | 			k->set_physical_keycode(Key::NONE); | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	Ref<InputEventWithModifiers> mod = received_event; | ||
|  | 	if (mod.is_valid()) { | ||
|  | 		mod->set_window_id(0); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// Maintain device selection.
 | ||
|  | 	received_event->set_device(_get_current_device()); | ||
|  | 
 | ||
|  | 	_set_event(received_event); | ||
|  | } | ||
|  | 
 | ||
|  | void InputEventConfigurationDialog::_on_listen_focus_changed() { | ||
|  | 	if (event_listener->has_focus()) { | ||
|  | 		set_close_on_escape(false); | ||
|  | 	} else { | ||
|  | 		set_close_on_escape(true); | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | void InputEventConfigurationDialog::_search_term_updated(const String &) { | ||
|  | 	_update_input_list(); | ||
|  | } | ||
|  | 
 | ||
|  | void InputEventConfigurationDialog::_update_input_list() { | ||
|  | 	input_list_tree->clear(); | ||
|  | 
 | ||
|  | 	TreeItem *root = input_list_tree->create_item(); | ||
|  | 	String search_term = input_list_search->get_text(); | ||
|  | 
 | ||
|  | 	bool collapse = input_list_search->get_text().is_empty(); | ||
|  | 
 | ||
|  | 	if (allowed_input_types & INPUT_KEY) { | ||
|  | 		TreeItem *kb_root = input_list_tree->create_item(root); | ||
|  | 		kb_root->set_text(0, TTR("Keyboard Keys")); | ||
|  | 		kb_root->set_icon(0, icon_cache.keyboard); | ||
|  | 		kb_root->set_collapsed(collapse); | ||
|  | 		kb_root->set_meta("__type", INPUT_KEY); | ||
|  | 
 | ||
|  | 		for (int i = 0; i < keycode_get_count(); i++) { | ||
|  | 			String name = keycode_get_name_by_index(i); | ||
|  | 
 | ||
|  | 			if (!search_term.is_empty() && name.findn(search_term) == -1) { | ||
|  | 				continue; | ||
|  | 			} | ||
|  | 
 | ||
|  | 			TreeItem *item = input_list_tree->create_item(kb_root); | ||
|  | 			item->set_text(0, name); | ||
|  | 			item->set_meta("__keycode", keycode_get_value_by_index(i)); | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if (allowed_input_types & INPUT_MOUSE_BUTTON) { | ||
|  | 		TreeItem *mouse_root = input_list_tree->create_item(root); | ||
|  | 		mouse_root->set_text(0, TTR("Mouse Buttons")); | ||
|  | 		mouse_root->set_icon(0, icon_cache.mouse); | ||
|  | 		mouse_root->set_collapsed(collapse); | ||
|  | 		mouse_root->set_meta("__type", INPUT_MOUSE_BUTTON); | ||
|  | 
 | ||
|  | 		MouseButton mouse_buttons[9] = { MouseButton::LEFT, MouseButton::RIGHT, MouseButton::MIDDLE, MouseButton::WHEEL_UP, MouseButton::WHEEL_DOWN, MouseButton::WHEEL_LEFT, MouseButton::WHEEL_RIGHT, MouseButton::MB_XBUTTON1, MouseButton::MB_XBUTTON2 }; | ||
|  | 		for (int i = 0; i < 9; i++) { | ||
|  | 			Ref<InputEventMouseButton> mb; | ||
|  | 			mb.instantiate(); | ||
|  | 			mb->set_button_index(mouse_buttons[i]); | ||
|  | 			String desc = get_event_text(mb, false); | ||
|  | 
 | ||
|  | 			if (!search_term.is_empty() && desc.findn(search_term) == -1) { | ||
|  | 				continue; | ||
|  | 			} | ||
|  | 
 | ||
|  | 			TreeItem *item = input_list_tree->create_item(mouse_root); | ||
|  | 			item->set_text(0, desc); | ||
|  | 			item->set_meta("__index", mouse_buttons[i]); | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if (allowed_input_types & INPUT_JOY_BUTTON) { | ||
|  | 		TreeItem *joyb_root = input_list_tree->create_item(root); | ||
|  | 		joyb_root->set_text(0, TTR("Joypad Buttons")); | ||
|  | 		joyb_root->set_icon(0, icon_cache.joypad_button); | ||
|  | 		joyb_root->set_collapsed(collapse); | ||
|  | 		joyb_root->set_meta("__type", INPUT_JOY_BUTTON); | ||
|  | 
 | ||
|  | 		for (int i = 0; i < (int)JoyButton::MAX; i++) { | ||
|  | 			Ref<InputEventJoypadButton> joyb; | ||
|  | 			joyb.instantiate(); | ||
|  | 			joyb->set_button_index((JoyButton)i); | ||
|  | 			String desc = get_event_text(joyb, false); | ||
|  | 
 | ||
|  | 			if (!search_term.is_empty() && desc.findn(search_term) == -1) { | ||
|  | 				continue; | ||
|  | 			} | ||
|  | 
 | ||
|  | 			TreeItem *item = input_list_tree->create_item(joyb_root); | ||
|  | 			item->set_text(0, desc); | ||
|  | 			item->set_meta("__index", i); | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if (allowed_input_types & INPUT_JOY_MOTION) { | ||
|  | 		TreeItem *joya_root = input_list_tree->create_item(root); | ||
|  | 		joya_root->set_text(0, TTR("Joypad Axes")); | ||
|  | 		joya_root->set_icon(0, icon_cache.joypad_axis); | ||
|  | 		joya_root->set_collapsed(collapse); | ||
|  | 		joya_root->set_meta("__type", INPUT_JOY_MOTION); | ||
|  | 
 | ||
|  | 		for (int i = 0; i < (int)JoyAxis::MAX * 2; i++) { | ||
|  | 			int axis = i / 2; | ||
|  | 			int direction = (i & 1) ? 1 : -1; | ||
|  | 			Ref<InputEventJoypadMotion> joym; | ||
|  | 			joym.instantiate(); | ||
|  | 			joym->set_axis((JoyAxis)axis); | ||
|  | 			joym->set_axis_value(direction); | ||
|  | 			String desc = get_event_text(joym, false); | ||
|  | 
 | ||
|  | 			if (!search_term.is_empty() && desc.findn(search_term) == -1) { | ||
|  | 				continue; | ||
|  | 			} | ||
|  | 
 | ||
|  | 			TreeItem *item = input_list_tree->create_item(joya_root); | ||
|  | 			item->set_text(0, desc); | ||
|  | 			item->set_meta("__axis", i >> 1); | ||
|  | 			item->set_meta("__value", (i & 1) ? 1 : -1); | ||
|  | 		} | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | void InputEventConfigurationDialog::_mod_toggled(bool p_checked, int p_index) { | ||
|  | 	Ref<InputEventWithModifiers> ie = event; | ||
|  | 
 | ||
|  | 	// Not event with modifiers
 | ||
|  | 	if (ie.is_null()) { | ||
|  | 		return; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if (p_index == 0) { | ||
|  | 		ie->set_alt_pressed(p_checked); | ||
|  | 	} else if (p_index == 1) { | ||
|  | 		ie->set_shift_pressed(p_checked); | ||
|  | 	} else if (p_index == 2) { | ||
|  | 		if (!autoremap_command_or_control_checkbox->is_pressed()) { | ||
|  | 			ie->set_ctrl_pressed(p_checked); | ||
|  | 		} | ||
|  | 	} else if (p_index == 3) { | ||
|  | 		if (!autoremap_command_or_control_checkbox->is_pressed()) { | ||
|  | 			ie->set_meta_pressed(p_checked); | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	_set_event(ie); | ||
|  | } | ||
|  | 
 | ||
|  | void InputEventConfigurationDialog::_autoremap_command_or_control_toggled(bool p_checked) { | ||
|  | 	Ref<InputEventWithModifiers> ie = event; | ||
|  | 	if (ie.is_valid()) { | ||
|  | 		ie->set_command_or_control_autoremap(p_checked); | ||
|  | 		_set_event(ie); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if (p_checked) { | ||
|  | 		mod_checkboxes[MOD_META]->hide(); | ||
|  | 		mod_checkboxes[MOD_CTRL]->hide(); | ||
|  | 	} else { | ||
|  | 		mod_checkboxes[MOD_META]->show(); | ||
|  | 		mod_checkboxes[MOD_CTRL]->show(); | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | void InputEventConfigurationDialog::_physical_keycode_toggled(bool p_checked) { | ||
|  | 	Ref<InputEventKey> k = event; | ||
|  | 
 | ||
|  | 	if (k.is_null()) { | ||
|  | 		return; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if (p_checked) { | ||
|  | 		k->set_physical_keycode(k->get_keycode()); | ||
|  | 		k->set_keycode(Key::NONE); | ||
|  | 	} else { | ||
|  | 		k->set_keycode((Key)k->get_physical_keycode()); | ||
|  | 		k->set_physical_keycode(Key::NONE); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	_set_event(k); | ||
|  | } | ||
|  | 
 | ||
|  | void InputEventConfigurationDialog::_input_list_item_selected() { | ||
|  | 	TreeItem *selected = input_list_tree->get_selected(); | ||
|  | 
 | ||
|  | 	// Invalid tree selection - type only exists on the "category" items, which are not a valid selection.
 | ||
|  | 	if (selected->has_meta("__type")) { | ||
|  | 		return; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	InputType input_type = (InputType)(int)selected->get_parent()->get_meta("__type"); | ||
|  | 
 | ||
|  | 	switch (input_type) { | ||
|  | 		case INPUT_KEY: { | ||
|  | 			Key keycode = (Key)(int)selected->get_meta("__keycode"); | ||
|  | 			Ref<InputEventKey> k; | ||
|  | 			k.instantiate(); | ||
|  | 
 | ||
|  | 			if (physical_key_checkbox->is_pressed()) { | ||
|  | 				k->set_physical_keycode(keycode); | ||
|  | 				k->set_keycode(Key::NONE); | ||
|  | 			} else { | ||
|  | 				k->set_physical_keycode(Key::NONE); | ||
|  | 				k->set_keycode(keycode); | ||
|  | 			} | ||
|  | 
 | ||
|  | 			// Maintain modifier state from checkboxes
 | ||
|  | 			k->set_alt_pressed(mod_checkboxes[MOD_ALT]->is_pressed()); | ||
|  | 			k->set_shift_pressed(mod_checkboxes[MOD_SHIFT]->is_pressed()); | ||
|  | 			if (autoremap_command_or_control_checkbox->is_pressed()) { | ||
|  | 				k->set_command_or_control_autoremap(true); | ||
|  | 			} else { | ||
|  | 				k->set_ctrl_pressed(mod_checkboxes[MOD_CTRL]->is_pressed()); | ||
|  | 				k->set_meta_pressed(mod_checkboxes[MOD_META]->is_pressed()); | ||
|  | 			} | ||
|  | 
 | ||
|  | 			_set_event(k, false); | ||
|  | 		} break; | ||
|  | 		case INPUT_MOUSE_BUTTON: { | ||
|  | 			MouseButton idx = (MouseButton)(int)selected->get_meta("__index"); | ||
|  | 			Ref<InputEventMouseButton> mb; | ||
|  | 			mb.instantiate(); | ||
|  | 			mb->set_button_index(idx); | ||
|  | 			// Maintain modifier state from checkboxes
 | ||
|  | 			mb->set_alt_pressed(mod_checkboxes[MOD_ALT]->is_pressed()); | ||
|  | 			mb->set_shift_pressed(mod_checkboxes[MOD_SHIFT]->is_pressed()); | ||
|  | 			if (autoremap_command_or_control_checkbox->is_pressed()) { | ||
|  | 				mb->set_command_or_control_autoremap(true); | ||
|  | 			} else { | ||
|  | 				mb->set_ctrl_pressed(mod_checkboxes[MOD_CTRL]->is_pressed()); | ||
|  | 				mb->set_meta_pressed(mod_checkboxes[MOD_META]->is_pressed()); | ||
|  | 			} | ||
|  | 
 | ||
|  | 			// Maintain selected device
 | ||
|  | 			mb->set_device(_get_current_device()); | ||
|  | 
 | ||
|  | 			_set_event(mb, false); | ||
|  | 		} break; | ||
|  | 		case INPUT_JOY_BUTTON: { | ||
|  | 			JoyButton idx = (JoyButton)(int)selected->get_meta("__index"); | ||
|  | 			Ref<InputEventJoypadButton> jb = InputEventJoypadButton::create_reference(idx); | ||
|  | 
 | ||
|  | 			// Maintain selected device
 | ||
|  | 			jb->set_device(_get_current_device()); | ||
|  | 
 | ||
|  | 			_set_event(jb, false); | ||
|  | 		} break; | ||
|  | 		case INPUT_JOY_MOTION: { | ||
|  | 			JoyAxis axis = (JoyAxis)(int)selected->get_meta("__axis"); | ||
|  | 			int value = selected->get_meta("__value"); | ||
|  | 
 | ||
|  | 			Ref<InputEventJoypadMotion> jm; | ||
|  | 			jm.instantiate(); | ||
|  | 			jm->set_axis(axis); | ||
|  | 			jm->set_axis_value(value); | ||
|  | 
 | ||
|  | 			// Maintain selected device
 | ||
|  | 			jm->set_device(_get_current_device()); | ||
|  | 
 | ||
|  | 			_set_event(jm, false); | ||
|  | 		} break; | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | void InputEventConfigurationDialog::_device_selection_changed(int p_option_button_index) { | ||
|  | 	// Subtract 1 as option index 0 corresponds to "All Devices" (value of -1)
 | ||
|  | 	// and option index 1 corresponds to device 0, etc...
 | ||
|  | 	event->set_device(p_option_button_index - 1); | ||
|  | 	event_as_text->set_text(get_event_text(event, true)); | ||
|  | } | ||
|  | 
 | ||
|  | void InputEventConfigurationDialog::_set_current_device(int p_device) { | ||
|  | 	device_id_option->select(p_device + 1); | ||
|  | } | ||
|  | 
 | ||
|  | int InputEventConfigurationDialog::_get_current_device() const { | ||
|  | 	return device_id_option->get_selected() - 1; | ||
|  | } | ||
|  | 
 | ||
|  | String InputEventConfigurationDialog::_get_device_string(int p_device) const { | ||
|  | 	if (p_device == InputMap::ALL_DEVICES) { | ||
|  | 		return TTR("All Devices"); | ||
|  | 	} | ||
|  | 	return TTR("Device") + " " + itos(p_device); | ||
|  | } | ||
|  | 
 | ||
|  | void InputEventConfigurationDialog::_notification(int p_what) { | ||
|  | 	switch (p_what) { | ||
|  | 		case NOTIFICATION_ENTER_TREE: | ||
|  | 		case NOTIFICATION_THEME_CHANGED: { | ||
|  | 			input_list_search->set_right_icon(input_list_search->get_theme_icon(SNAME("Search"), SNAME("EditorIcons"))); | ||
|  | 
 | ||
|  | 			physical_key_checkbox->set_icon(get_theme_icon(SNAME("KeyboardPhysical"), SNAME("EditorIcons"))); | ||
|  | 
 | ||
|  | 			icon_cache.keyboard = get_theme_icon(SNAME("Keyboard"), SNAME("EditorIcons")); | ||
|  | 			icon_cache.mouse = get_theme_icon(SNAME("Mouse"), SNAME("EditorIcons")); | ||
|  | 			icon_cache.joypad_button = get_theme_icon(SNAME("JoyButton"), SNAME("EditorIcons")); | ||
|  | 			icon_cache.joypad_axis = get_theme_icon(SNAME("JoyAxis"), SNAME("EditorIcons")); | ||
|  | 
 | ||
|  | 			_update_input_list(); | ||
|  | 		} break; | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | void InputEventConfigurationDialog::popup_and_configure(const Ref<InputEvent> &p_event) { | ||
|  | 	if (p_event.is_valid()) { | ||
|  | 		_set_event(p_event); | ||
|  | 	} else { | ||
|  | 		// Clear Event
 | ||
|  | 		_set_event(p_event); | ||
|  | 
 | ||
|  | 		// Clear Checkbox Values
 | ||
|  | 		for (int i = 0; i < MOD_MAX; i++) { | ||
|  | 			mod_checkboxes[i]->set_pressed(false); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// Enable the Physical Key checkbox by default to encourage its use.
 | ||
|  | 		// Physical Key should be used for most game inputs as it allows keys to work
 | ||
|  | 		// on non-QWERTY layouts out of the box.
 | ||
|  | 		// This is especially important for WASD movement layouts.
 | ||
|  | 		physical_key_checkbox->set_pressed(true); | ||
|  | 
 | ||
|  | 		autoremap_command_or_control_checkbox->set_pressed(false); | ||
|  | 
 | ||
|  | 		// Select "All Devices" by default.
 | ||
|  | 		device_id_option->select(0); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	popup_centered(Size2(0, 400) * EDSCALE); | ||
|  | } | ||
|  | 
 | ||
|  | Ref<InputEvent> InputEventConfigurationDialog::get_event() const { | ||
|  | 	return event; | ||
|  | } | ||
|  | 
 | ||
|  | void InputEventConfigurationDialog::set_allowed_input_types(int p_type_masks) { | ||
|  | 	allowed_input_types = p_type_masks; | ||
|  | } | ||
|  | 
 | ||
|  | InputEventConfigurationDialog::InputEventConfigurationDialog() { | ||
|  | 	allowed_input_types = INPUT_KEY | INPUT_MOUSE_BUTTON | INPUT_JOY_BUTTON | INPUT_JOY_MOTION; | ||
|  | 
 | ||
|  | 	set_title(TTR("Event Configuration")); | ||
|  | 	set_min_size(Size2i(550 * EDSCALE, 0)); // Min width
 | ||
|  | 
 | ||
|  | 	VBoxContainer *main_vbox = memnew(VBoxContainer); | ||
|  | 	add_child(main_vbox); | ||
|  | 
 | ||
|  | 	event_as_text = memnew(Label); | ||
|  | 	event_as_text->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART); | ||
|  | 	event_as_text->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); | ||
|  | 	event_as_text->add_theme_font_override("font", get_theme_font(SNAME("bold"), SNAME("EditorFonts"))); | ||
|  | 	event_as_text->add_theme_font_size_override("font_size", 18 * EDSCALE); | ||
|  | 	main_vbox->add_child(event_as_text); | ||
|  | 
 | ||
|  | 	event_listener = memnew(EventListenerLineEdit); | ||
|  | 	event_listener->set_h_size_flags(Control::SIZE_EXPAND_FILL); | ||
|  | 	event_listener->set_stretch_ratio(0.75); | ||
|  | 	event_listener->connect("event_changed", callable_mp(this, &InputEventConfigurationDialog::_on_listen_input_changed)); | ||
|  | 	event_listener->connect("focus_entered", callable_mp(this, &InputEventConfigurationDialog::_on_listen_focus_changed)); | ||
|  | 	event_listener->connect("focus_exited", callable_mp(this, &InputEventConfigurationDialog::_on_listen_focus_changed)); | ||
|  | 	main_vbox->add_child(event_listener); | ||
|  | 
 | ||
|  | 	main_vbox->add_child(memnew(HSeparator)); | ||
|  | 
 | ||
|  | 	// List of all input options to manually select from.
 | ||
|  | 	VBoxContainer *manual_vbox = memnew(VBoxContainer); | ||
|  | 	manual_vbox->set_name(TTR("Manual Selection")); | ||
|  | 	manual_vbox->set_v_size_flags(Control::SIZE_EXPAND_FILL); | ||
|  | 	main_vbox->add_child(manual_vbox); | ||
|  | 
 | ||
|  | 	input_list_search = memnew(LineEdit); | ||
|  | 	input_list_search->set_h_size_flags(Control::SIZE_EXPAND_FILL); | ||
|  | 	input_list_search->set_placeholder(TTR("Filter Inputs")); | ||
|  | 	input_list_search->set_clear_button_enabled(true); | ||
|  | 	input_list_search->connect("text_changed", callable_mp(this, &InputEventConfigurationDialog::_search_term_updated)); | ||
|  | 	manual_vbox->add_child(input_list_search); | ||
|  | 
 | ||
|  | 	input_list_tree = memnew(Tree); | ||
|  | 	input_list_tree->set_custom_minimum_size(Size2(0, 100 * EDSCALE)); // Min height for tree
 | ||
|  | 	input_list_tree->connect("item_selected", callable_mp(this, &InputEventConfigurationDialog::_input_list_item_selected)); | ||
|  | 	input_list_tree->set_v_size_flags(Control::SIZE_EXPAND_FILL); | ||
|  | 	manual_vbox->add_child(input_list_tree); | ||
|  | 
 | ||
|  | 	input_list_tree->set_hide_root(true); | ||
|  | 	input_list_tree->set_columns(1); | ||
|  | 
 | ||
|  | 	_update_input_list(); | ||
|  | 
 | ||
|  | 	// Additional Options
 | ||
|  | 	additional_options_container = memnew(VBoxContainer); | ||
|  | 	additional_options_container->hide(); | ||
|  | 
 | ||
|  | 	Label *opts_label = memnew(Label); | ||
|  | 	opts_label->set_theme_type_variation("HeaderSmall"); | ||
|  | 	opts_label->set_text(TTR("Additional Options")); | ||
|  | 	additional_options_container->add_child(opts_label); | ||
|  | 
 | ||
|  | 	// Device Selection
 | ||
|  | 	device_container = memnew(HBoxContainer); | ||
|  | 	device_container->set_h_size_flags(Control::SIZE_EXPAND_FILL); | ||
|  | 
 | ||
|  | 	Label *device_label = memnew(Label); | ||
|  | 	device_label->set_theme_type_variation("HeaderSmall"); | ||
|  | 	device_label->set_text(TTR("Device:")); | ||
|  | 	device_container->add_child(device_label); | ||
|  | 
 | ||
|  | 	device_id_option = memnew(OptionButton); | ||
|  | 	device_id_option->set_h_size_flags(Control::SIZE_EXPAND_FILL); | ||
|  | 	for (int i = -1; i < 8; i++) { | ||
|  | 		device_id_option->add_item(_get_device_string(i)); | ||
|  | 	} | ||
|  | 	device_id_option->connect("item_selected", callable_mp(this, &InputEventConfigurationDialog::_device_selection_changed)); | ||
|  | 	_set_current_device(InputMap::ALL_DEVICES); | ||
|  | 	device_container->add_child(device_id_option); | ||
|  | 
 | ||
|  | 	device_container->hide(); | ||
|  | 	additional_options_container->add_child(device_container); | ||
|  | 
 | ||
|  | 	// Modifier Selection
 | ||
|  | 	mod_container = memnew(HBoxContainer); | ||
|  | 	for (int i = 0; i < MOD_MAX; i++) { | ||
|  | 		String name = mods[i]; | ||
|  | 		mod_checkboxes[i] = memnew(CheckBox); | ||
|  | 		mod_checkboxes[i]->connect("toggled", callable_mp(this, &InputEventConfigurationDialog::_mod_toggled).bind(i)); | ||
|  | 		mod_checkboxes[i]->set_text(name); | ||
|  | 		mod_checkboxes[i]->set_tooltip_text(TTR(mods_tip[i])); | ||
|  | 		mod_container->add_child(mod_checkboxes[i]); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	mod_container->add_child(memnew(VSeparator)); | ||
|  | 
 | ||
|  | 	autoremap_command_or_control_checkbox = memnew(CheckBox); | ||
|  | 	autoremap_command_or_control_checkbox->connect("toggled", callable_mp(this, &InputEventConfigurationDialog::_autoremap_command_or_control_toggled)); | ||
|  | 	autoremap_command_or_control_checkbox->set_pressed(false); | ||
|  | 	autoremap_command_or_control_checkbox->set_text(TTR("Command / Control (auto)")); | ||
|  | 	autoremap_command_or_control_checkbox->set_tooltip_text(TTR("Automatically remaps between 'Meta' ('Command') and 'Control' depending on current platform.")); | ||
|  | 	mod_container->add_child(autoremap_command_or_control_checkbox); | ||
|  | 
 | ||
|  | 	mod_container->hide(); | ||
|  | 	additional_options_container->add_child(mod_container); | ||
|  | 
 | ||
|  | 	// Physical Key Checkbox
 | ||
|  | 
 | ||
|  | 	physical_key_checkbox = memnew(CheckBox); | ||
|  | 	physical_key_checkbox->set_text(TTR("Use Physical Keycode")); | ||
|  | 	physical_key_checkbox->set_tooltip_text(TTR("Stores the physical position of the key on the keyboard rather than the key's value. Used for compatibility with non-latin layouts.\nThis should generally be enabled for most game shortcuts, but not in non-game applications.")); | ||
|  | 	physical_key_checkbox->connect("toggled", callable_mp(this, &InputEventConfigurationDialog::_physical_keycode_toggled)); | ||
|  | 	physical_key_checkbox->hide(); | ||
|  | 	additional_options_container->add_child(physical_key_checkbox); | ||
|  | 
 | ||
|  | 	main_vbox->add_child(additional_options_container); | ||
|  | } |