Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/editor/settings/action_map_editor.cpp
9902 views
1
/**************************************************************************/
2
/* action_map_editor.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 "action_map_editor.h"
32
33
#include "editor/editor_string_names.h"
34
#include "editor/settings/editor_event_search_bar.h"
35
#include "editor/settings/editor_settings.h"
36
#include "editor/settings/event_listener_line_edit.h"
37
#include "editor/settings/input_event_configuration_dialog.h"
38
#include "editor/themes/editor_scale.h"
39
#include "scene/gui/check_button.h"
40
#include "scene/gui/separator.h"
41
#include "scene/gui/tree.h"
42
43
static bool _is_action_name_valid(const String &p_name) {
44
const char32_t *cstr = p_name.get_data();
45
for (int i = 0; cstr[i]; i++) {
46
if (cstr[i] == '/' || cstr[i] == ':' || cstr[i] == '"' ||
47
cstr[i] == '=' || cstr[i] == '\\' || cstr[i] < 32) {
48
return false;
49
}
50
}
51
return true;
52
}
53
54
void ActionMapEditor::_event_config_confirmed() {
55
Ref<InputEvent> ev = event_config_dialog->get_event();
56
57
Dictionary new_action = current_action.duplicate();
58
Array events = new_action["events"].duplicate();
59
60
if (current_action_event_index == -1) {
61
// Add new event
62
events.push_back(ev);
63
} else {
64
// Edit existing event
65
events[current_action_event_index] = ev;
66
}
67
68
new_action["events"] = events;
69
emit_signal(SNAME("action_edited"), current_action_name, new_action);
70
}
71
72
void ActionMapEditor::_add_action_pressed() {
73
_add_action(add_edit->get_text());
74
}
75
76
String ActionMapEditor::_check_new_action_name(const String &p_name) {
77
if (p_name.is_empty() || !_is_action_name_valid(p_name)) {
78
return TTR("Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or '\"'");
79
}
80
81
if (_has_action(p_name)) {
82
return vformat(TTR("An action with the name '%s' already exists."), p_name);
83
}
84
85
return "";
86
}
87
88
void ActionMapEditor::_add_edit_text_changed(const String &p_name) {
89
const String error = _check_new_action_name(p_name);
90
add_button->set_tooltip_text(error);
91
add_button->set_disabled(!error.is_empty());
92
}
93
94
bool ActionMapEditor::_has_action(const String &p_name) const {
95
for (const ActionInfo &action_info : actions_cache) {
96
if (p_name == action_info.name) {
97
return true;
98
}
99
}
100
return false;
101
}
102
103
void ActionMapEditor::_add_action(const String &p_name) {
104
String error = _check_new_action_name(p_name);
105
if (!error.is_empty()) {
106
show_message(error);
107
return;
108
}
109
110
add_edit->clear();
111
emit_signal(SNAME("action_added"), p_name);
112
}
113
114
void ActionMapEditor::_action_edited() {
115
TreeItem *ti = action_tree->get_edited();
116
if (!ti) {
117
return;
118
}
119
120
if (action_tree->get_selected_column() == 0) {
121
// Name Edited
122
String new_name = ti->get_text(0);
123
String old_name = ti->get_meta("__name");
124
125
if (new_name == old_name) {
126
return;
127
}
128
129
if (new_name.is_empty() || !_is_action_name_valid(new_name)) {
130
ti->set_text(0, old_name);
131
show_message(TTR("Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or '\"'"));
132
return;
133
}
134
135
if (_has_action(new_name)) {
136
ti->set_text(0, old_name);
137
show_message(vformat(TTR("An action with the name '%s' already exists."), new_name));
138
return;
139
}
140
141
emit_signal(SNAME("action_renamed"), old_name, new_name);
142
} else if (action_tree->get_selected_column() == 1) {
143
// Deadzone Edited
144
String name = ti->get_meta("__name");
145
Dictionary old_action = ti->get_meta("__action");
146
Dictionary new_action = old_action.duplicate();
147
new_action["deadzone"] = ti->get_range(1);
148
149
// Call deferred so that input can finish propagating through tree, allowing re-making of tree to occur.
150
call_deferred(SNAME("emit_signal"), "action_edited", name, new_action);
151
}
152
}
153
154
void ActionMapEditor::_tree_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button) {
155
if (p_button != MouseButton::LEFT) {
156
return;
157
}
158
159
ItemButton option = (ItemButton)p_id;
160
161
TreeItem *item = Object::cast_to<TreeItem>(p_item);
162
if (!item) {
163
return;
164
}
165
166
switch (option) {
167
case ActionMapEditor::BUTTON_ADD_EVENT: {
168
current_action = item->get_meta("__action");
169
current_action_name = item->get_meta("__name");
170
current_action_event_index = -1;
171
172
event_config_dialog->popup_and_configure(Ref<InputEvent>(), current_action_name);
173
} break;
174
case ActionMapEditor::BUTTON_EDIT_EVENT: {
175
// Action and Action name is located on the parent of the event.
176
current_action = item->get_parent()->get_meta("__action");
177
current_action_name = item->get_parent()->get_meta("__name");
178
179
current_action_event_index = item->get_meta("__index");
180
181
Ref<InputEvent> ie = item->get_meta("__event");
182
if (ie.is_valid()) {
183
event_config_dialog->popup_and_configure(ie, current_action_name);
184
}
185
} break;
186
case ActionMapEditor::BUTTON_REMOVE_ACTION: {
187
// Send removed action name
188
String name = item->get_meta("__name");
189
emit_signal(SNAME("action_removed"), name);
190
} break;
191
case ActionMapEditor::BUTTON_REMOVE_EVENT: {
192
// Remove event and send updated action
193
Dictionary action = item->get_parent()->get_meta("__action").duplicate();
194
String action_name = item->get_parent()->get_meta("__name");
195
196
int event_index = item->get_meta("__index");
197
198
Array events = action["events"].duplicate();
199
events.remove_at(event_index);
200
action["events"] = events;
201
202
emit_signal(SNAME("action_edited"), action_name, action);
203
} break;
204
case ActionMapEditor::BUTTON_REVERT_ACTION: {
205
ERR_FAIL_COND_MSG(!item->has_meta("__action_initial"), "Tree Item for action which can be reverted is expected to have meta value with initial value of action.");
206
207
Dictionary action = item->get_meta("__action_initial").duplicate();
208
String action_name = item->get_meta("__name");
209
210
emit_signal(SNAME("action_edited"), action_name, action);
211
} break;
212
default:
213
break;
214
}
215
}
216
217
void ActionMapEditor::_tree_item_activated() {
218
TreeItem *item = action_tree->get_selected();
219
220
if (!item || !item->has_meta("__event")) {
221
return;
222
}
223
224
_tree_button_pressed(item, 2, BUTTON_EDIT_EVENT, MouseButton::LEFT);
225
}
226
227
void ActionMapEditor::_set_show_builtin_actions(bool p_show) {
228
show_builtin_actions = p_show;
229
EditorSettings::get_singleton()->set_project_metadata("project_settings", "show_builtin_actions", show_builtin_actions);
230
231
// Prevent unnecessary updates of action list when cache is empty.
232
if (!actions_cache.is_empty()) {
233
update_action_list();
234
}
235
}
236
237
void ActionMapEditor::_on_search_bar_value_changed() {
238
if (action_list_search_bar->is_searching()) {
239
show_builtin_actions_checkbutton->set_pressed_no_signal(true);
240
show_builtin_actions_checkbutton->set_disabled(true);
241
show_builtin_actions_checkbutton->set_tooltip_text(TTRC("Built-in actions are always shown when searching."));
242
} else {
243
show_builtin_actions_checkbutton->set_pressed_no_signal(show_builtin_actions);
244
show_builtin_actions_checkbutton->set_disabled(false);
245
show_builtin_actions_checkbutton->set_tooltip_text(String());
246
}
247
update_action_list();
248
}
249
250
Variant ActionMapEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) {
251
TreeItem *selected = action_tree->get_selected();
252
if (!selected) {
253
return Variant();
254
}
255
256
String name = selected->get_text(0);
257
Label *label = memnew(Label(name));
258
label->set_theme_type_variation("HeaderSmall");
259
label->set_modulate(Color(1, 1, 1, 1.0f));
260
label->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
261
action_tree->set_drag_preview(label);
262
263
get_viewport()->gui_set_drag_description(vformat(RTR("Action %s"), name));
264
265
Dictionary drag_data;
266
267
if (selected->has_meta("__action")) {
268
drag_data["input_type"] = "action";
269
}
270
271
if (selected->has_meta("__event")) {
272
drag_data["input_type"] = "event";
273
}
274
275
drag_data["source"] = selected->get_instance_id();
276
277
action_tree->set_drop_mode_flags(Tree::DROP_MODE_INBETWEEN);
278
279
return drag_data;
280
}
281
282
bool ActionMapEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
283
Dictionary d = p_data;
284
if (!d.has("input_type")) {
285
return false;
286
}
287
288
TreeItem *source = Object::cast_to<TreeItem>(ObjectDB::get_instance(d["source"].operator ObjectID()));
289
TreeItem *selected = action_tree->get_selected();
290
291
TreeItem *item = (p_point == Vector2(Math::INF, Math::INF)) ? selected : action_tree->get_item_at_position(p_point);
292
if (!selected || !item || item == source) {
293
return false;
294
}
295
296
// Don't allow moving an action in-between events.
297
if (d["input_type"] == "action" && item->has_meta("__event")) {
298
return false;
299
}
300
301
// Don't allow moving an event to a different action.
302
if (d["input_type"] == "event" && item->get_parent() != selected->get_parent()) {
303
return false;
304
}
305
306
return true;
307
}
308
309
void ActionMapEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
310
if (!can_drop_data_fw(p_point, p_data, p_from)) {
311
return;
312
}
313
314
TreeItem *selected = action_tree->get_selected();
315
TreeItem *target = (p_point == Vector2(Math::INF, Math::INF)) ? selected : action_tree->get_item_at_position(p_point);
316
if (!target) {
317
return;
318
}
319
320
bool drop_above = ((p_point == Vector2(Math::INF, Math::INF)) ? action_tree->get_drop_section_at_position(action_tree->get_item_rect(target).position) : action_tree->get_drop_section_at_position(p_point)) == -1;
321
322
Dictionary d = p_data;
323
if (d["input_type"] == "action") {
324
// Change action order.
325
String relative_to = target->get_meta("__name");
326
String action_name = selected->get_meta("__name");
327
emit_signal(SNAME("action_reordered"), action_name, relative_to, drop_above);
328
329
} else if (d["input_type"] == "event") {
330
// Change event order
331
int current_index = selected->get_meta("__index");
332
int target_index = target->get_meta("__index");
333
334
// Construct new events array.
335
Dictionary new_action = selected->get_parent()->get_meta("__action");
336
337
Array events = new_action["events"];
338
Array new_events;
339
340
// The following method was used to perform the array changes since `remove` followed by `insert` was not working properly at time of writing.
341
// Loop thought existing events
342
for (int i = 0; i < events.size(); i++) {
343
// If you come across the current index, just skip it, as it has been moved.
344
if (i == current_index) {
345
continue;
346
} else if (i == target_index) {
347
// We are at the target index. If drop above, add selected event there first, then target, so moved event goes on top.
348
if (drop_above) {
349
new_events.push_back(events[current_index]);
350
new_events.push_back(events[target_index]);
351
} else {
352
new_events.push_back(events[target_index]);
353
new_events.push_back(events[current_index]);
354
}
355
} else {
356
new_events.push_back(events[i]);
357
}
358
}
359
360
new_action["events"] = new_events;
361
emit_signal(SNAME("action_edited"), selected->get_parent()->get_meta("__name"), new_action);
362
}
363
}
364
365
void ActionMapEditor::_notification(int p_what) {
366
switch (p_what) {
367
case NOTIFICATION_TRANSLATION_CHANGED: {
368
if (!actions_cache.is_empty()) {
369
update_action_list();
370
}
371
if (!add_button->get_tooltip_text().is_empty()) {
372
_add_edit_text_changed(add_edit->get_text());
373
}
374
} break;
375
376
case NOTIFICATION_THEME_CHANGED: {
377
add_button->set_button_icon(get_editor_theme_icon(SNAME("Add")));
378
if (!actions_cache.is_empty()) {
379
update_action_list();
380
}
381
} break;
382
}
383
}
384
385
void ActionMapEditor::_bind_methods() {
386
ADD_SIGNAL(MethodInfo("action_added", PropertyInfo(Variant::STRING, "name")));
387
ADD_SIGNAL(MethodInfo("action_edited", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::DICTIONARY, "new_action")));
388
ADD_SIGNAL(MethodInfo("action_removed", PropertyInfo(Variant::STRING, "name")));
389
ADD_SIGNAL(MethodInfo("action_renamed", PropertyInfo(Variant::STRING, "old_name"), PropertyInfo(Variant::STRING, "new_name")));
390
ADD_SIGNAL(MethodInfo("action_reordered", PropertyInfo(Variant::STRING, "action_name"), PropertyInfo(Variant::STRING, "relative_to"), PropertyInfo(Variant::BOOL, "before")));
391
}
392
393
LineEdit *ActionMapEditor::get_search_box() const {
394
return action_list_search_bar->get_name_search_box();
395
}
396
397
LineEdit *ActionMapEditor::get_path_box() const {
398
return add_edit;
399
}
400
401
InputEventConfigurationDialog *ActionMapEditor::get_configuration_dialog() {
402
return event_config_dialog;
403
}
404
405
bool ActionMapEditor::_should_display_action(const String &p_name, const Array &p_events) const {
406
const Ref<InputEvent> search_ev = action_list_search_bar->get_event();
407
bool event_match = true;
408
if (search_ev.is_valid()) {
409
event_match = false;
410
for (int i = 0; i < p_events.size(); ++i) {
411
const Ref<InputEvent> ev = p_events[i];
412
if (ev.is_valid() && ev->is_match(search_ev, true)) {
413
event_match = true;
414
}
415
}
416
}
417
418
return event_match && action_list_search_bar->get_name().is_subsequence_ofn(p_name);
419
}
420
421
void ActionMapEditor::update_action_list(const Vector<ActionInfo> &p_action_infos) {
422
if (!p_action_infos.is_empty()) {
423
actions_cache = p_action_infos;
424
}
425
426
HashSet<String> collapsed_actions;
427
TreeItem *root = action_tree->get_root();
428
if (root) {
429
for (TreeItem *child = root->get_first_child(); child; child = child->get_next()) {
430
if (child->is_collapsed()) {
431
collapsed_actions.insert(child->get_meta("__name"));
432
}
433
}
434
}
435
436
action_tree->clear();
437
root = action_tree->create_item();
438
439
for (const ActionInfo &action_info : actions_cache) {
440
const Array events = action_info.action["events"];
441
if (!_should_display_action(action_info.name, events)) {
442
continue;
443
}
444
445
if (!action_info.editable && !action_list_search_bar->is_searching() && !show_builtin_actions) {
446
continue;
447
}
448
449
const Variant deadzone = action_info.action["deadzone"];
450
451
// Update Tree...
452
453
TreeItem *action_item = action_tree->create_item(root);
454
ERR_FAIL_NULL(action_item);
455
action_item->set_meta("__action", action_info.action);
456
action_item->set_meta("__name", action_info.name);
457
action_item->set_collapsed(collapsed_actions.has(action_info.name));
458
459
// First Column - Action Name
460
action_item->set_auto_translate_mode(0, AUTO_TRANSLATE_MODE_DISABLED);
461
action_item->set_text(0, action_info.name);
462
action_item->set_editable(0, action_info.editable);
463
action_item->set_icon(0, action_info.icon);
464
465
// Second Column - Deadzone
466
action_item->set_editable(1, true);
467
action_item->set_cell_mode(1, TreeItem::CELL_MODE_RANGE);
468
action_item->set_range_config(1, 0.0, 1.0, 0.01);
469
action_item->set_range(1, deadzone);
470
471
// Third column - buttons
472
if (action_info.has_initial) {
473
bool deadzone_eq = action_info.action_initial["deadzone"] == action_info.action["deadzone"];
474
bool events_eq = Shortcut::is_event_array_equal(action_info.action_initial["events"], action_info.action["events"]);
475
bool action_eq = deadzone_eq && events_eq;
476
action_item->set_meta("__action_initial", action_info.action_initial);
477
action_item->add_button(2, get_editor_theme_icon(SNAME("ReloadSmall")), BUTTON_REVERT_ACTION, action_eq, action_eq ? TTRC("Cannot Revert - Action is same as initial") : TTRC("Revert Action"));
478
}
479
action_item->add_button(2, get_editor_theme_icon(SNAME("Add")), BUTTON_ADD_EVENT, false, TTRC("Add Event"));
480
action_item->add_button(2, get_editor_theme_icon(SNAME("Remove")), BUTTON_REMOVE_ACTION, !action_info.editable, action_info.editable ? TTRC("Remove Action") : TTRC("Cannot Remove Action"));
481
482
action_item->set_custom_bg_color(0, get_theme_color(SNAME("prop_subsection"), EditorStringName(Editor)));
483
action_item->set_custom_bg_color(1, get_theme_color(SNAME("prop_subsection"), EditorStringName(Editor)));
484
485
for (int evnt_idx = 0; evnt_idx < events.size(); evnt_idx++) {
486
Ref<InputEvent> event = events[evnt_idx];
487
if (event.is_null()) {
488
continue;
489
}
490
491
TreeItem *event_item = action_tree->create_item(action_item);
492
493
// First Column - Text
494
event_item->set_auto_translate_mode(0, AUTO_TRANSLATE_MODE_DISABLED);
495
event_item->set_text(0, EventListenerLineEdit::get_event_text(event, true));
496
event_item->set_meta("__event", event);
497
event_item->set_meta("__index", evnt_idx);
498
499
// First Column - Icon
500
Ref<InputEventKey> k = event;
501
if (k.is_valid()) {
502
if (k->get_physical_keycode() == Key::NONE && k->get_keycode() == Key::NONE && k->get_key_label() != Key::NONE) {
503
event_item->set_icon(0, get_editor_theme_icon(SNAME("KeyboardLabel")));
504
} else if (k->get_keycode() != Key::NONE) {
505
event_item->set_icon(0, get_editor_theme_icon(SNAME("Keyboard")));
506
} else if (k->get_physical_keycode() != Key::NONE) {
507
event_item->set_icon(0, get_editor_theme_icon(SNAME("KeyboardPhysical")));
508
} else {
509
event_item->set_icon(0, get_editor_theme_icon(SNAME("KeyboardError")));
510
}
511
}
512
513
Ref<InputEventMouseButton> mb = event;
514
if (mb.is_valid()) {
515
event_item->set_icon(0, get_editor_theme_icon(SNAME("Mouse")));
516
}
517
518
Ref<InputEventJoypadButton> jb = event;
519
if (jb.is_valid()) {
520
event_item->set_icon(0, get_editor_theme_icon(SNAME("JoyButton")));
521
}
522
523
Ref<InputEventJoypadMotion> jm = event;
524
if (jm.is_valid()) {
525
event_item->set_icon(0, get_editor_theme_icon(SNAME("JoyAxis")));
526
}
527
528
// Third Column - Buttons
529
event_item->add_button(2, get_editor_theme_icon(SNAME("Edit")), BUTTON_EDIT_EVENT, false, TTRC("Edit Event"), TTRC("Edit Event"));
530
event_item->add_button(2, get_editor_theme_icon(SNAME("Remove")), BUTTON_REMOVE_EVENT, false, TTRC("Remove Event"), TTRC("Remove Event"));
531
event_item->set_button_color(2, 0, Color(1, 1, 1, 0.75));
532
event_item->set_button_color(2, 1, Color(1, 1, 1, 0.75));
533
}
534
}
535
}
536
537
void ActionMapEditor::show_message(const String &p_message) {
538
message->set_text(p_message);
539
message->popup_centered();
540
}
541
542
ActionMapEditor::ActionMapEditor() {
543
// Main Vbox Container
544
VBoxContainer *main_vbox = memnew(VBoxContainer);
545
main_vbox->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
546
add_child(main_vbox);
547
548
action_list_search_bar = memnew(EditorEventSearchBar);
549
action_list_search_bar->connect(SceneStringName(value_changed), callable_mp(this, &ActionMapEditor::_on_search_bar_value_changed));
550
main_vbox->add_child(action_list_search_bar);
551
552
// Adding Action line edit + button
553
add_hbox = memnew(HBoxContainer);
554
add_hbox->set_h_size_flags(Control::SIZE_EXPAND_FILL);
555
556
add_edit = memnew(LineEdit);
557
add_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL);
558
add_edit->set_placeholder(TTRC("Add New Action"));
559
add_edit->set_accessibility_name(TTRC("Add New Action"));
560
add_edit->set_clear_button_enabled(true);
561
add_edit->set_keep_editing_on_text_submit(true);
562
add_edit->connect(SceneStringName(text_changed), callable_mp(this, &ActionMapEditor::_add_edit_text_changed));
563
add_edit->connect(SceneStringName(text_submitted), callable_mp(this, &ActionMapEditor::_add_action));
564
add_hbox->add_child(add_edit);
565
566
add_button = memnew(Button);
567
add_button->set_text(TTRC("Add"));
568
add_button->connect(SceneStringName(pressed), callable_mp(this, &ActionMapEditor::_add_action_pressed));
569
add_hbox->add_child(add_button);
570
// Disable the button and set its tooltip.
571
_add_edit_text_changed(add_edit->get_text());
572
573
add_hbox->add_child(memnew(VSeparator));
574
575
show_builtin_actions_checkbutton = memnew(CheckButton);
576
show_builtin_actions_checkbutton->set_text(TTRC("Show Built-in Actions"));
577
show_builtin_actions_checkbutton->connect(SceneStringName(toggled), callable_mp(this, &ActionMapEditor::_set_show_builtin_actions));
578
add_hbox->add_child(show_builtin_actions_checkbutton);
579
580
show_builtin_actions = EditorSettings::get_singleton()->get_project_metadata("project_settings", "show_builtin_actions", false);
581
show_builtin_actions_checkbutton->set_pressed_no_signal(show_builtin_actions);
582
583
main_vbox->add_child(add_hbox);
584
585
// Action Editor Tree
586
action_tree = memnew(Tree);
587
action_tree->set_v_size_flags(Control::SIZE_EXPAND_FILL);
588
action_tree->set_accessibility_name(TTRC("Action Map"));
589
action_tree->set_columns(3);
590
action_tree->set_hide_root(true);
591
action_tree->set_column_titles_visible(true);
592
action_tree->set_column_title(0, TTRC("Action"));
593
action_tree->set_column_clip_content(0, true);
594
action_tree->set_column_title(1, TTRC("Deadzone"));
595
action_tree->set_column_expand(1, false);
596
action_tree->set_column_custom_minimum_width(1, 80 * EDSCALE);
597
action_tree->set_column_expand(2, false);
598
action_tree->set_column_custom_minimum_width(2, 50 * EDSCALE);
599
action_tree->connect("item_edited", callable_mp(this, &ActionMapEditor::_action_edited), CONNECT_DEFERRED);
600
action_tree->connect("item_activated", callable_mp(this, &ActionMapEditor::_tree_item_activated));
601
action_tree->connect("button_clicked", callable_mp(this, &ActionMapEditor::_tree_button_pressed));
602
main_vbox->add_child(action_tree);
603
604
SET_DRAG_FORWARDING_GCD(action_tree, ActionMapEditor);
605
606
// Adding event dialog
607
event_config_dialog = memnew(InputEventConfigurationDialog);
608
event_config_dialog->connect(SceneStringName(confirmed), callable_mp(this, &ActionMapEditor::_event_config_confirmed));
609
add_child(event_config_dialog);
610
611
message = memnew(AcceptDialog);
612
add_child(message);
613
}
614
615