| 1 | /**************************************************************************/ |
| 2 | /* input_event_configuration_dialog.cpp */ |
| 3 | /**************************************************************************/ |
| 4 | /* This file is part of: */ |
| 5 | /* GODOT ENGINE */ |
| 6 | /* https://godotengine.org */ |
| 7 | /**************************************************************************/ |
| 8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
| 9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
| 10 | /* */ |
| 11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
| 12 | /* a copy of this software and associated documentation files (the */ |
| 13 | /* "Software"), to deal in the Software without restriction, including */ |
| 14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
| 15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
| 16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
| 17 | /* the following conditions: */ |
| 18 | /* */ |
| 19 | /* The above copyright notice and this permission notice shall be */ |
| 20 | /* included in all copies or substantial portions of the Software. */ |
| 21 | /* */ |
| 22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
| 23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
| 24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
| 25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
| 26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
| 27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
| 28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
| 29 | /**************************************************************************/ |
| 30 | |
| 31 | #include "editor/input_event_configuration_dialog.h" |
| 32 | #include "core/input/input_map.h" |
| 33 | #include "editor/editor_scale.h" |
| 34 | #include "editor/editor_string_names.h" |
| 35 | #include "editor/event_listener_line_edit.h" |
| 36 | #include "scene/gui/check_box.h" |
| 37 | #include "scene/gui/line_edit.h" |
| 38 | #include "scene/gui/option_button.h" |
| 39 | #include "scene/gui/separator.h" |
| 40 | #include "scene/gui/tree.h" |
| 41 | |
| 42 | void InputEventConfigurationDialog::_set_event(const Ref<InputEvent> &p_event, const Ref<InputEvent> &p_original_event, bool p_update_input_list_selection) { |
| 43 | if (p_event.is_valid()) { |
| 44 | event = p_event; |
| 45 | original_event = p_original_event; |
| 46 | |
| 47 | // If the event is changed to something which is not the same as the listener, |
| 48 | // clear out the event from the listener text box to avoid confusion. |
| 49 | const Ref<InputEvent> listener_event = event_listener->get_event(); |
| 50 | if (listener_event.is_valid() && !listener_event->is_match(p_event)) { |
| 51 | event_listener->clear_event(); |
| 52 | } |
| 53 | |
| 54 | // Update Label |
| 55 | event_as_text->set_text(EventListenerLineEdit::get_event_text(event, true)); |
| 56 | |
| 57 | Ref<InputEventKey> k = p_event; |
| 58 | Ref<InputEventMouseButton> mb = p_event; |
| 59 | Ref<InputEventJoypadButton> joyb = p_event; |
| 60 | Ref<InputEventJoypadMotion> joym = p_event; |
| 61 | Ref<InputEventWithModifiers> mod = p_event; |
| 62 | |
| 63 | // Update option values and visibility |
| 64 | bool show_mods = false; |
| 65 | bool show_device = false; |
| 66 | bool show_key = false; |
| 67 | |
| 68 | if (mod.is_valid()) { |
| 69 | show_mods = true; |
| 70 | mod_checkboxes[MOD_ALT]->set_pressed(mod->is_alt_pressed()); |
| 71 | mod_checkboxes[MOD_SHIFT]->set_pressed(mod->is_shift_pressed()); |
| 72 | mod_checkboxes[MOD_CTRL]->set_pressed(mod->is_ctrl_pressed()); |
| 73 | mod_checkboxes[MOD_META]->set_pressed(mod->is_meta_pressed()); |
| 74 | |
| 75 | autoremap_command_or_control_checkbox->set_pressed(mod->is_command_or_control_autoremap()); |
| 76 | } |
| 77 | |
| 78 | if (k.is_valid()) { |
| 79 | show_key = true; |
| 80 | if (k->get_keycode() == Key::NONE && k->get_physical_keycode() == Key::NONE && k->get_key_label() != Key::NONE) { |
| 81 | key_mode->select(KEYMODE_UNICODE); |
| 82 | } else if (k->get_keycode() != Key::NONE) { |
| 83 | key_mode->select(KEYMODE_KEYCODE); |
| 84 | } else if (k->get_physical_keycode() != Key::NONE) { |
| 85 | key_mode->select(KEYMODE_PHY_KEYCODE); |
| 86 | } else { |
| 87 | // Invalid key. |
| 88 | event = Ref<InputEvent>(); |
| 89 | original_event = Ref<InputEvent>(); |
| 90 | event_listener->clear_event(); |
| 91 | event_as_text->set_text(TTR("No Event Configured" )); |
| 92 | |
| 93 | additional_options_container->hide(); |
| 94 | input_list_tree->deselect_all(); |
| 95 | _update_input_list(); |
| 96 | return; |
| 97 | } |
| 98 | } else if (joyb.is_valid() || joym.is_valid() || mb.is_valid()) { |
| 99 | show_device = true; |
| 100 | _set_current_device(event->get_device()); |
| 101 | } |
| 102 | |
| 103 | mod_container->set_visible(show_mods); |
| 104 | device_container->set_visible(show_device); |
| 105 | key_mode->set_visible(show_key); |
| 106 | additional_options_container->show(); |
| 107 | |
| 108 | // Update mode selector based on original key event. |
| 109 | Ref<InputEventKey> ko = p_original_event; |
| 110 | if (ko.is_valid()) { |
| 111 | if (ko->get_keycode() == Key::NONE) { |
| 112 | if (ko->get_physical_keycode() != Key::NONE) { |
| 113 | ko->set_keycode(ko->get_physical_keycode()); |
| 114 | } |
| 115 | if (ko->get_key_label() != Key::NONE) { |
| 116 | ko->set_keycode(fix_keycode((char32_t)ko->get_key_label(), Key::NONE)); |
| 117 | } |
| 118 | } |
| 119 | |
| 120 | if (ko->get_physical_keycode() == Key::NONE) { |
| 121 | if (ko->get_keycode() != Key::NONE) { |
| 122 | ko->set_physical_keycode(ko->get_keycode()); |
| 123 | } |
| 124 | if (ko->get_key_label() != Key::NONE) { |
| 125 | ko->set_physical_keycode(fix_keycode((char32_t)ko->get_key_label(), Key::NONE)); |
| 126 | } |
| 127 | } |
| 128 | |
| 129 | if (ko->get_key_label() == Key::NONE) { |
| 130 | if (ko->get_keycode() != Key::NONE) { |
| 131 | ko->set_key_label(fix_key_label((char32_t)ko->get_keycode(), Key::NONE)); |
| 132 | } |
| 133 | if (ko->get_physical_keycode() != Key::NONE) { |
| 134 | ko->set_key_label(fix_key_label((char32_t)ko->get_physical_keycode(), Key::NONE)); |
| 135 | } |
| 136 | } |
| 137 | |
| 138 | key_mode->set_item_disabled(KEYMODE_KEYCODE, ko->get_keycode() == Key::NONE); |
| 139 | key_mode->set_item_disabled(KEYMODE_PHY_KEYCODE, ko->get_physical_keycode() == Key::NONE); |
| 140 | key_mode->set_item_disabled(KEYMODE_UNICODE, ko->get_key_label() == Key::NONE); |
| 141 | } |
| 142 | |
| 143 | // Update selected item in input list. |
| 144 | if (p_update_input_list_selection && (k.is_valid() || joyb.is_valid() || joym.is_valid() || mb.is_valid())) { |
| 145 | in_tree_update = true; |
| 146 | TreeItem *category = input_list_tree->get_root()->get_first_child(); |
| 147 | while (category) { |
| 148 | TreeItem *input_item = category->get_first_child(); |
| 149 | |
| 150 | if (input_item != nullptr) { |
| 151 | // input_type should always be > 0, unless the tree structure has been misconfigured. |
| 152 | int input_type = input_item->get_parent()->get_meta("__type" , 0); |
| 153 | if (input_type == 0) { |
| 154 | in_tree_update = false; |
| 155 | return; |
| 156 | } |
| 157 | |
| 158 | // If event type matches input types of this category. |
| 159 | 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)) { |
| 160 | // Loop through all items of this category until one matches. |
| 161 | while (input_item) { |
| 162 | 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" )); |
| 163 | bool joyb_match = joyb.is_valid() && Variant(joyb->get_button_index()) == input_item->get_meta("__index" ); |
| 164 | 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" ); |
| 165 | bool mb_match = mb.is_valid() && Variant(mb->get_button_index()) == input_item->get_meta("__index" ); |
| 166 | if (key_match || joyb_match || joym_match || mb_match) { |
| 167 | category->set_collapsed(false); |
| 168 | input_item->select(0); |
| 169 | input_list_tree->ensure_cursor_is_visible(); |
| 170 | in_tree_update = false; |
| 171 | return; |
| 172 | } |
| 173 | input_item = input_item->get_next(); |
| 174 | } |
| 175 | } |
| 176 | } |
| 177 | |
| 178 | category->set_collapsed(true); // Event not in this category, so collapse; |
| 179 | category = category->get_next(); |
| 180 | } |
| 181 | in_tree_update = false; |
| 182 | } |
| 183 | } else { |
| 184 | // Event is not valid, reset dialog |
| 185 | event = Ref<InputEvent>(); |
| 186 | original_event = Ref<InputEvent>(); |
| 187 | event_listener->clear_event(); |
| 188 | event_as_text->set_text(TTR("No Event Configured" )); |
| 189 | |
| 190 | additional_options_container->hide(); |
| 191 | input_list_tree->deselect_all(); |
| 192 | _update_input_list(); |
| 193 | } |
| 194 | } |
| 195 | |
| 196 | void InputEventConfigurationDialog::_on_listen_input_changed(const Ref<InputEvent> &p_event) { |
| 197 | // Ignore if invalid, echo or not pressed |
| 198 | if (p_event.is_null() || p_event->is_echo() || !p_event->is_pressed()) { |
| 199 | return; |
| 200 | } |
| 201 | |
| 202 | // Create an editable reference and a copy of full event. |
| 203 | Ref<InputEvent> received_event = p_event; |
| 204 | Ref<InputEvent> received_original_event = received_event->duplicate(); |
| 205 | |
| 206 | // Check what the type is and if it is allowed. |
| 207 | Ref<InputEventKey> k = received_event; |
| 208 | Ref<InputEventJoypadButton> joyb = received_event; |
| 209 | Ref<InputEventJoypadMotion> joym = received_event; |
| 210 | Ref<InputEventMouseButton> mb = received_event; |
| 211 | |
| 212 | int type = 0; |
| 213 | if (k.is_valid()) { |
| 214 | type = INPUT_KEY; |
| 215 | } else if (joyb.is_valid()) { |
| 216 | type = INPUT_JOY_BUTTON; |
| 217 | } else if (joym.is_valid()) { |
| 218 | type = INPUT_JOY_MOTION; |
| 219 | } else if (mb.is_valid()) { |
| 220 | type = INPUT_MOUSE_BUTTON; |
| 221 | } |
| 222 | |
| 223 | if (!(allowed_input_types & type)) { |
| 224 | return; |
| 225 | } |
| 226 | |
| 227 | if (joym.is_valid()) { |
| 228 | joym->set_axis_value(SIGN(joym->get_axis_value())); |
| 229 | } |
| 230 | |
| 231 | if (k.is_valid()) { |
| 232 | k->set_pressed(false); // To avoid serialization of 'pressed' property - doesn't matter for actions anyway. |
| 233 | if (key_mode->get_selected_id() == KEYMODE_KEYCODE) { |
| 234 | k->set_physical_keycode(Key::NONE); |
| 235 | k->set_key_label(Key::NONE); |
| 236 | } else if (key_mode->get_selected_id() == KEYMODE_PHY_KEYCODE) { |
| 237 | k->set_keycode(Key::NONE); |
| 238 | k->set_key_label(Key::NONE); |
| 239 | } else if (key_mode->get_selected_id() == KEYMODE_UNICODE) { |
| 240 | k->set_physical_keycode(Key::NONE); |
| 241 | k->set_keycode(Key::NONE); |
| 242 | } |
| 243 | } |
| 244 | |
| 245 | Ref<InputEventWithModifiers> mod = received_event; |
| 246 | if (mod.is_valid()) { |
| 247 | mod->set_window_id(0); |
| 248 | } |
| 249 | |
| 250 | // Maintain device selection. |
| 251 | received_event->set_device(_get_current_device()); |
| 252 | |
| 253 | _set_event(received_event, received_original_event); |
| 254 | } |
| 255 | |
| 256 | void InputEventConfigurationDialog::_on_listen_focus_changed() { |
| 257 | if (event_listener->has_focus()) { |
| 258 | set_close_on_escape(false); |
| 259 | } else { |
| 260 | set_close_on_escape(true); |
| 261 | } |
| 262 | } |
| 263 | |
| 264 | void InputEventConfigurationDialog::_search_term_updated(const String &) { |
| 265 | _update_input_list(); |
| 266 | } |
| 267 | |
| 268 | void InputEventConfigurationDialog::_update_input_list() { |
| 269 | input_list_tree->clear(); |
| 270 | |
| 271 | TreeItem *root = input_list_tree->create_item(); |
| 272 | String search_term = input_list_search->get_text(); |
| 273 | |
| 274 | bool collapse = input_list_search->get_text().is_empty(); |
| 275 | |
| 276 | if (allowed_input_types & INPUT_KEY) { |
| 277 | TreeItem *kb_root = input_list_tree->create_item(root); |
| 278 | kb_root->set_text(0, TTR("Keyboard Keys" )); |
| 279 | kb_root->set_icon(0, icon_cache.keyboard); |
| 280 | kb_root->set_collapsed(collapse); |
| 281 | kb_root->set_meta("__type" , INPUT_KEY); |
| 282 | |
| 283 | for (int i = 0; i < keycode_get_count(); i++) { |
| 284 | String name = keycode_get_name_by_index(i); |
| 285 | |
| 286 | if (!search_term.is_empty() && name.findn(search_term) == -1) { |
| 287 | continue; |
| 288 | } |
| 289 | |
| 290 | TreeItem *item = input_list_tree->create_item(kb_root); |
| 291 | item->set_text(0, name); |
| 292 | item->set_meta("__keycode" , keycode_get_value_by_index(i)); |
| 293 | } |
| 294 | } |
| 295 | |
| 296 | if (allowed_input_types & INPUT_MOUSE_BUTTON) { |
| 297 | TreeItem *mouse_root = input_list_tree->create_item(root); |
| 298 | mouse_root->set_text(0, TTR("Mouse Buttons" )); |
| 299 | mouse_root->set_icon(0, icon_cache.mouse); |
| 300 | mouse_root->set_collapsed(collapse); |
| 301 | mouse_root->set_meta("__type" , INPUT_MOUSE_BUTTON); |
| 302 | |
| 303 | 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 }; |
| 304 | for (int i = 0; i < 9; i++) { |
| 305 | Ref<InputEventMouseButton> mb; |
| 306 | mb.instantiate(); |
| 307 | mb->set_button_index(mouse_buttons[i]); |
| 308 | String desc = EventListenerLineEdit::get_event_text(mb, false); |
| 309 | |
| 310 | if (!search_term.is_empty() && desc.findn(search_term) == -1) { |
| 311 | continue; |
| 312 | } |
| 313 | |
| 314 | TreeItem *item = input_list_tree->create_item(mouse_root); |
| 315 | item->set_text(0, desc); |
| 316 | item->set_meta("__index" , mouse_buttons[i]); |
| 317 | } |
| 318 | } |
| 319 | |
| 320 | if (allowed_input_types & INPUT_JOY_BUTTON) { |
| 321 | TreeItem *joyb_root = input_list_tree->create_item(root); |
| 322 | joyb_root->set_text(0, TTR("Joypad Buttons" )); |
| 323 | joyb_root->set_icon(0, icon_cache.joypad_button); |
| 324 | joyb_root->set_collapsed(collapse); |
| 325 | joyb_root->set_meta("__type" , INPUT_JOY_BUTTON); |
| 326 | |
| 327 | for (int i = 0; i < (int)JoyButton::MAX; i++) { |
| 328 | Ref<InputEventJoypadButton> joyb; |
| 329 | joyb.instantiate(); |
| 330 | joyb->set_button_index((JoyButton)i); |
| 331 | String desc = EventListenerLineEdit::get_event_text(joyb, false); |
| 332 | |
| 333 | if (!search_term.is_empty() && desc.findn(search_term) == -1) { |
| 334 | continue; |
| 335 | } |
| 336 | |
| 337 | TreeItem *item = input_list_tree->create_item(joyb_root); |
| 338 | item->set_text(0, desc); |
| 339 | item->set_meta("__index" , i); |
| 340 | } |
| 341 | } |
| 342 | |
| 343 | if (allowed_input_types & INPUT_JOY_MOTION) { |
| 344 | TreeItem *joya_root = input_list_tree->create_item(root); |
| 345 | joya_root->set_text(0, TTR("Joypad Axes" )); |
| 346 | joya_root->set_icon(0, icon_cache.joypad_axis); |
| 347 | joya_root->set_collapsed(collapse); |
| 348 | joya_root->set_meta("__type" , INPUT_JOY_MOTION); |
| 349 | |
| 350 | for (int i = 0; i < (int)JoyAxis::MAX * 2; i++) { |
| 351 | int axis = i / 2; |
| 352 | int direction = (i & 1) ? 1 : -1; |
| 353 | Ref<InputEventJoypadMotion> joym; |
| 354 | joym.instantiate(); |
| 355 | joym->set_axis((JoyAxis)axis); |
| 356 | joym->set_axis_value(direction); |
| 357 | String desc = EventListenerLineEdit::get_event_text(joym, false); |
| 358 | |
| 359 | if (!search_term.is_empty() && desc.findn(search_term) == -1) { |
| 360 | continue; |
| 361 | } |
| 362 | |
| 363 | TreeItem *item = input_list_tree->create_item(joya_root); |
| 364 | item->set_text(0, desc); |
| 365 | item->set_meta("__axis" , i >> 1); |
| 366 | item->set_meta("__value" , (i & 1) ? 1 : -1); |
| 367 | } |
| 368 | } |
| 369 | } |
| 370 | |
| 371 | void InputEventConfigurationDialog::_mod_toggled(bool p_checked, int p_index) { |
| 372 | Ref<InputEventWithModifiers> ie = event; |
| 373 | |
| 374 | // Not event with modifiers |
| 375 | if (ie.is_null()) { |
| 376 | return; |
| 377 | } |
| 378 | |
| 379 | if (p_index == 0) { |
| 380 | ie->set_alt_pressed(p_checked); |
| 381 | } else if (p_index == 1) { |
| 382 | ie->set_shift_pressed(p_checked); |
| 383 | } else if (p_index == 2) { |
| 384 | if (!autoremap_command_or_control_checkbox->is_pressed()) { |
| 385 | ie->set_ctrl_pressed(p_checked); |
| 386 | } |
| 387 | } else if (p_index == 3) { |
| 388 | if (!autoremap_command_or_control_checkbox->is_pressed()) { |
| 389 | ie->set_meta_pressed(p_checked); |
| 390 | } |
| 391 | } |
| 392 | |
| 393 | _set_event(ie, original_event); |
| 394 | } |
| 395 | |
| 396 | void InputEventConfigurationDialog::_autoremap_command_or_control_toggled(bool p_checked) { |
| 397 | Ref<InputEventWithModifiers> ie = event; |
| 398 | if (ie.is_valid()) { |
| 399 | ie->set_command_or_control_autoremap(p_checked); |
| 400 | _set_event(ie, original_event); |
| 401 | } |
| 402 | |
| 403 | if (p_checked) { |
| 404 | mod_checkboxes[MOD_META]->hide(); |
| 405 | mod_checkboxes[MOD_CTRL]->hide(); |
| 406 | } else { |
| 407 | mod_checkboxes[MOD_META]->show(); |
| 408 | mod_checkboxes[MOD_CTRL]->show(); |
| 409 | } |
| 410 | } |
| 411 | |
| 412 | void InputEventConfigurationDialog::_key_mode_selected(int p_mode) { |
| 413 | Ref<InputEventKey> k = event; |
| 414 | Ref<InputEventKey> ko = original_event; |
| 415 | if (k.is_null() || ko.is_null()) { |
| 416 | return; |
| 417 | } |
| 418 | |
| 419 | if (key_mode->get_selected_id() == KEYMODE_KEYCODE) { |
| 420 | k->set_keycode(ko->get_keycode()); |
| 421 | k->set_physical_keycode(Key::NONE); |
| 422 | k->set_key_label(Key::NONE); |
| 423 | } else if (key_mode->get_selected_id() == KEYMODE_PHY_KEYCODE) { |
| 424 | k->set_keycode(Key::NONE); |
| 425 | k->set_physical_keycode(ko->get_physical_keycode()); |
| 426 | k->set_key_label(Key::NONE); |
| 427 | } else if (key_mode->get_selected_id() == KEYMODE_UNICODE) { |
| 428 | k->set_physical_keycode(Key::NONE); |
| 429 | k->set_keycode(Key::NONE); |
| 430 | k->set_key_label(ko->get_key_label()); |
| 431 | } |
| 432 | |
| 433 | _set_event(k, original_event); |
| 434 | } |
| 435 | |
| 436 | void InputEventConfigurationDialog::_input_list_item_selected() { |
| 437 | TreeItem *selected = input_list_tree->get_selected(); |
| 438 | |
| 439 | // Called form _set_event, do not update for a second time. |
| 440 | if (in_tree_update) { |
| 441 | return; |
| 442 | } |
| 443 | |
| 444 | // Invalid tree selection - type only exists on the "category" items, which are not a valid selection. |
| 445 | if (selected->has_meta("__type" )) { |
| 446 | return; |
| 447 | } |
| 448 | |
| 449 | InputType input_type = (InputType)(int)selected->get_parent()->get_meta("__type" ); |
| 450 | |
| 451 | switch (input_type) { |
| 452 | case INPUT_KEY: { |
| 453 | Key keycode = (Key)(int)selected->get_meta("__keycode" ); |
| 454 | Ref<InputEventKey> k; |
| 455 | k.instantiate(); |
| 456 | |
| 457 | k->set_physical_keycode(keycode); |
| 458 | k->set_keycode(keycode); |
| 459 | k->set_key_label(keycode); |
| 460 | |
| 461 | // Maintain modifier state from checkboxes. |
| 462 | k->set_alt_pressed(mod_checkboxes[MOD_ALT]->is_pressed()); |
| 463 | k->set_shift_pressed(mod_checkboxes[MOD_SHIFT]->is_pressed()); |
| 464 | if (autoremap_command_or_control_checkbox->is_pressed()) { |
| 465 | k->set_command_or_control_autoremap(true); |
| 466 | } else { |
| 467 | k->set_ctrl_pressed(mod_checkboxes[MOD_CTRL]->is_pressed()); |
| 468 | k->set_meta_pressed(mod_checkboxes[MOD_META]->is_pressed()); |
| 469 | } |
| 470 | |
| 471 | Ref<InputEventKey> ko = k->duplicate(); |
| 472 | |
| 473 | if (key_mode->get_selected_id() == KEYMODE_UNICODE) { |
| 474 | key_mode->select(KEYMODE_PHY_KEYCODE); |
| 475 | } |
| 476 | |
| 477 | if (key_mode->get_selected_id() == KEYMODE_KEYCODE) { |
| 478 | k->set_physical_keycode(Key::NONE); |
| 479 | k->set_keycode(keycode); |
| 480 | k->set_key_label(Key::NONE); |
| 481 | } else if (key_mode->get_selected_id() == KEYMODE_PHY_KEYCODE) { |
| 482 | k->set_physical_keycode(keycode); |
| 483 | k->set_keycode(Key::NONE); |
| 484 | k->set_key_label(Key::NONE); |
| 485 | } |
| 486 | |
| 487 | _set_event(k, ko, false); |
| 488 | } break; |
| 489 | case INPUT_MOUSE_BUTTON: { |
| 490 | MouseButton idx = (MouseButton)(int)selected->get_meta("__index" ); |
| 491 | Ref<InputEventMouseButton> mb; |
| 492 | mb.instantiate(); |
| 493 | mb->set_button_index(idx); |
| 494 | // Maintain modifier state from checkboxes |
| 495 | mb->set_alt_pressed(mod_checkboxes[MOD_ALT]->is_pressed()); |
| 496 | mb->set_shift_pressed(mod_checkboxes[MOD_SHIFT]->is_pressed()); |
| 497 | if (autoremap_command_or_control_checkbox->is_pressed()) { |
| 498 | mb->set_command_or_control_autoremap(true); |
| 499 | } else { |
| 500 | mb->set_ctrl_pressed(mod_checkboxes[MOD_CTRL]->is_pressed()); |
| 501 | mb->set_meta_pressed(mod_checkboxes[MOD_META]->is_pressed()); |
| 502 | } |
| 503 | |
| 504 | // Maintain selected device |
| 505 | mb->set_device(_get_current_device()); |
| 506 | |
| 507 | _set_event(mb, mb, false); |
| 508 | } break; |
| 509 | case INPUT_JOY_BUTTON: { |
| 510 | JoyButton idx = (JoyButton)(int)selected->get_meta("__index" ); |
| 511 | Ref<InputEventJoypadButton> jb = InputEventJoypadButton::create_reference(idx); |
| 512 | |
| 513 | // Maintain selected device |
| 514 | jb->set_device(_get_current_device()); |
| 515 | |
| 516 | _set_event(jb, jb, false); |
| 517 | } break; |
| 518 | case INPUT_JOY_MOTION: { |
| 519 | JoyAxis axis = (JoyAxis)(int)selected->get_meta("__axis" ); |
| 520 | int value = selected->get_meta("__value" ); |
| 521 | |
| 522 | Ref<InputEventJoypadMotion> jm; |
| 523 | jm.instantiate(); |
| 524 | jm->set_axis(axis); |
| 525 | jm->set_axis_value(value); |
| 526 | |
| 527 | // Maintain selected device |
| 528 | jm->set_device(_get_current_device()); |
| 529 | |
| 530 | _set_event(jm, jm, false); |
| 531 | } break; |
| 532 | } |
| 533 | } |
| 534 | |
| 535 | void InputEventConfigurationDialog::_device_selection_changed(int p_option_button_index) { |
| 536 | // Subtract 1 as option index 0 corresponds to "All Devices" (value of -1) |
| 537 | // and option index 1 corresponds to device 0, etc... |
| 538 | event->set_device(p_option_button_index - 1); |
| 539 | event_as_text->set_text(EventListenerLineEdit::get_event_text(event, true)); |
| 540 | } |
| 541 | |
| 542 | void InputEventConfigurationDialog::_set_current_device(int p_device) { |
| 543 | device_id_option->select(p_device + 1); |
| 544 | } |
| 545 | |
| 546 | int InputEventConfigurationDialog::_get_current_device() const { |
| 547 | return device_id_option->get_selected() - 1; |
| 548 | } |
| 549 | |
| 550 | void InputEventConfigurationDialog::_notification(int p_what) { |
| 551 | switch (p_what) { |
| 552 | case NOTIFICATION_VISIBILITY_CHANGED: { |
| 553 | event_listener->grab_focus(); |
| 554 | } break; |
| 555 | |
| 556 | case NOTIFICATION_ENTER_TREE: |
| 557 | case NOTIFICATION_THEME_CHANGED: { |
| 558 | input_list_search->set_right_icon(input_list_search->get_editor_theme_icon(SNAME("Search" ))); |
| 559 | |
| 560 | key_mode->set_item_icon(KEYMODE_KEYCODE, get_editor_theme_icon(SNAME("Keyboard" ))); |
| 561 | key_mode->set_item_icon(KEYMODE_PHY_KEYCODE, get_editor_theme_icon(SNAME("KeyboardPhysical" ))); |
| 562 | key_mode->set_item_icon(KEYMODE_UNICODE, get_editor_theme_icon(SNAME("KeyboardLabel" ))); |
| 563 | |
| 564 | icon_cache.keyboard = get_editor_theme_icon(SNAME("Keyboard" )); |
| 565 | icon_cache.mouse = get_editor_theme_icon(SNAME("Mouse" )); |
| 566 | icon_cache.joypad_button = get_editor_theme_icon(SNAME("JoyButton" )); |
| 567 | icon_cache.joypad_axis = get_editor_theme_icon(SNAME("JoyAxis" )); |
| 568 | |
| 569 | event_as_text->add_theme_font_override("font" , get_theme_font(SNAME("bold" ), EditorStringName(EditorFonts))); |
| 570 | |
| 571 | _update_input_list(); |
| 572 | } break; |
| 573 | } |
| 574 | } |
| 575 | |
| 576 | void InputEventConfigurationDialog::popup_and_configure(const Ref<InputEvent> &p_event) { |
| 577 | if (p_event.is_valid()) { |
| 578 | _set_event(p_event->duplicate(), p_event); |
| 579 | } else { |
| 580 | // Clear Event |
| 581 | _set_event(Ref<InputEvent>(), Ref<InputEvent>()); |
| 582 | |
| 583 | // Clear Checkbox Values |
| 584 | for (int i = 0; i < MOD_MAX; i++) { |
| 585 | mod_checkboxes[i]->set_pressed(false); |
| 586 | } |
| 587 | |
| 588 | // Enable the Physical Key by default to encourage its use. |
| 589 | // Physical Key should be used for most game inputs as it allows keys to work |
| 590 | // on non-QWERTY layouts out of the box. |
| 591 | // This is especially important for WASD movement layouts. |
| 592 | |
| 593 | key_mode->select(KEYMODE_PHY_KEYCODE); |
| 594 | autoremap_command_or_control_checkbox->set_pressed(false); |
| 595 | |
| 596 | // Select "All Devices" by default. |
| 597 | device_id_option->select(0); |
| 598 | } |
| 599 | |
| 600 | popup_centered(Size2(0, 400) * EDSCALE); |
| 601 | } |
| 602 | |
| 603 | Ref<InputEvent> InputEventConfigurationDialog::get_event() const { |
| 604 | return event; |
| 605 | } |
| 606 | |
| 607 | void InputEventConfigurationDialog::set_allowed_input_types(int p_type_masks) { |
| 608 | allowed_input_types = p_type_masks; |
| 609 | event_listener->set_allowed_input_types(p_type_masks); |
| 610 | } |
| 611 | |
| 612 | InputEventConfigurationDialog::InputEventConfigurationDialog() { |
| 613 | allowed_input_types = INPUT_KEY | INPUT_MOUSE_BUTTON | INPUT_JOY_BUTTON | INPUT_JOY_MOTION; |
| 614 | |
| 615 | set_title(TTR("Event Configuration" )); |
| 616 | set_min_size(Size2i(550 * EDSCALE, 0)); // Min width |
| 617 | |
| 618 | VBoxContainer *main_vbox = memnew(VBoxContainer); |
| 619 | add_child(main_vbox); |
| 620 | |
| 621 | event_as_text = memnew(Label); |
| 622 | event_as_text->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART); |
| 623 | event_as_text->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); |
| 624 | event_as_text->add_theme_font_size_override("font_size" , 18 * EDSCALE); |
| 625 | main_vbox->add_child(event_as_text); |
| 626 | |
| 627 | event_listener = memnew(EventListenerLineEdit); |
| 628 | event_listener->set_h_size_flags(Control::SIZE_EXPAND_FILL); |
| 629 | event_listener->set_stretch_ratio(0.75); |
| 630 | event_listener->connect("event_changed" , callable_mp(this, &InputEventConfigurationDialog::_on_listen_input_changed)); |
| 631 | event_listener->connect("focus_entered" , callable_mp(this, &InputEventConfigurationDialog::_on_listen_focus_changed)); |
| 632 | event_listener->connect("focus_exited" , callable_mp(this, &InputEventConfigurationDialog::_on_listen_focus_changed)); |
| 633 | main_vbox->add_child(event_listener); |
| 634 | |
| 635 | main_vbox->add_child(memnew(HSeparator)); |
| 636 | |
| 637 | // List of all input options to manually select from. |
| 638 | VBoxContainer *manual_vbox = memnew(VBoxContainer); |
| 639 | manual_vbox->set_name(TTR("Manual Selection" )); |
| 640 | manual_vbox->set_v_size_flags(Control::SIZE_EXPAND_FILL); |
| 641 | main_vbox->add_child(manual_vbox); |
| 642 | |
| 643 | input_list_search = memnew(LineEdit); |
| 644 | input_list_search->set_h_size_flags(Control::SIZE_EXPAND_FILL); |
| 645 | input_list_search->set_placeholder(TTR("Filter Inputs" )); |
| 646 | input_list_search->set_clear_button_enabled(true); |
| 647 | input_list_search->connect("text_changed" , callable_mp(this, &InputEventConfigurationDialog::_search_term_updated)); |
| 648 | manual_vbox->add_child(input_list_search); |
| 649 | |
| 650 | input_list_tree = memnew(Tree); |
| 651 | input_list_tree->set_custom_minimum_size(Size2(0, 100 * EDSCALE)); // Min height for tree |
| 652 | input_list_tree->connect("item_selected" , callable_mp(this, &InputEventConfigurationDialog::_input_list_item_selected)); |
| 653 | input_list_tree->set_v_size_flags(Control::SIZE_EXPAND_FILL); |
| 654 | manual_vbox->add_child(input_list_tree); |
| 655 | |
| 656 | input_list_tree->set_hide_root(true); |
| 657 | input_list_tree->set_columns(1); |
| 658 | |
| 659 | _update_input_list(); |
| 660 | |
| 661 | // Additional Options |
| 662 | additional_options_container = memnew(VBoxContainer); |
| 663 | additional_options_container->hide(); |
| 664 | |
| 665 | Label *opts_label = memnew(Label); |
| 666 | opts_label->set_theme_type_variation("HeaderSmall" ); |
| 667 | opts_label->set_text(TTR("Additional Options" )); |
| 668 | additional_options_container->add_child(opts_label); |
| 669 | |
| 670 | // Device Selection |
| 671 | device_container = memnew(HBoxContainer); |
| 672 | device_container->set_h_size_flags(Control::SIZE_EXPAND_FILL); |
| 673 | |
| 674 | Label *device_label = memnew(Label); |
| 675 | device_label->set_theme_type_variation("HeaderSmall" ); |
| 676 | device_label->set_text(TTR("Device:" )); |
| 677 | device_container->add_child(device_label); |
| 678 | |
| 679 | device_id_option = memnew(OptionButton); |
| 680 | device_id_option->set_h_size_flags(Control::SIZE_EXPAND_FILL); |
| 681 | for (int i = -1; i < 8; i++) { |
| 682 | device_id_option->add_item(EventListenerLineEdit::get_device_string(i)); |
| 683 | } |
| 684 | device_id_option->connect("item_selected" , callable_mp(this, &InputEventConfigurationDialog::_device_selection_changed)); |
| 685 | _set_current_device(InputMap::ALL_DEVICES); |
| 686 | device_container->add_child(device_id_option); |
| 687 | |
| 688 | device_container->hide(); |
| 689 | additional_options_container->add_child(device_container); |
| 690 | |
| 691 | // Modifier Selection |
| 692 | mod_container = memnew(HBoxContainer); |
| 693 | for (int i = 0; i < MOD_MAX; i++) { |
| 694 | String name = mods[i]; |
| 695 | mod_checkboxes[i] = memnew(CheckBox); |
| 696 | mod_checkboxes[i]->connect("toggled" , callable_mp(this, &InputEventConfigurationDialog::_mod_toggled).bind(i)); |
| 697 | mod_checkboxes[i]->set_text(name); |
| 698 | mod_checkboxes[i]->set_tooltip_text(TTR(mods_tip[i])); |
| 699 | mod_container->add_child(mod_checkboxes[i]); |
| 700 | } |
| 701 | |
| 702 | mod_container->add_child(memnew(VSeparator)); |
| 703 | |
| 704 | autoremap_command_or_control_checkbox = memnew(CheckBox); |
| 705 | autoremap_command_or_control_checkbox->connect("toggled" , callable_mp(this, &InputEventConfigurationDialog::_autoremap_command_or_control_toggled)); |
| 706 | autoremap_command_or_control_checkbox->set_pressed(false); |
| 707 | autoremap_command_or_control_checkbox->set_text(TTR("Command / Control (auto)" )); |
| 708 | autoremap_command_or_control_checkbox->set_tooltip_text(TTR("Automatically remaps between 'Meta' ('Command') and 'Control' depending on current platform." )); |
| 709 | mod_container->add_child(autoremap_command_or_control_checkbox); |
| 710 | |
| 711 | mod_container->hide(); |
| 712 | additional_options_container->add_child(mod_container); |
| 713 | |
| 714 | // Key Mode Selection |
| 715 | |
| 716 | key_mode = memnew(OptionButton); |
| 717 | key_mode->add_item(TTR("Keycode (Latin Equivalent)" ), KEYMODE_KEYCODE); |
| 718 | key_mode->add_item(TTR("Physical Keycode (Position on US QWERTY Keyboard)" ), KEYMODE_PHY_KEYCODE); |
| 719 | key_mode->add_item(TTR("Key Label (Unicode, Case-Insensitive)" ), KEYMODE_UNICODE); |
| 720 | key_mode->connect("item_selected" , callable_mp(this, &InputEventConfigurationDialog::_key_mode_selected)); |
| 721 | key_mode->hide(); |
| 722 | additional_options_container->add_child(key_mode); |
| 723 | |
| 724 | main_vbox->add_child(additional_options_container); |
| 725 | } |
| 726 | |