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 | |