Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/modules/openxr/editor/openxr_action_map_editor.cpp
10278 views
1
/**************************************************************************/
2
/* openxr_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 "openxr_action_map_editor.h"
32
33
#include "core/config/project_settings.h"
34
#include "editor/editor_node.h"
35
#include "editor/gui/editor_bottom_panel.h"
36
#include "editor/gui/editor_file_dialog.h"
37
#include "editor/themes/editor_scale.h"
38
39
HashMap<String, String> OpenXRActionMapEditor::interaction_profile_editors;
40
HashMap<String, String> OpenXRActionMapEditor::binding_modifier_editors;
41
42
void OpenXRActionMapEditor::_bind_methods() {
43
ClassDB::bind_method("_add_action_set_editor", &OpenXRActionMapEditor::_add_action_set_editor);
44
ClassDB::bind_method("_add_interaction_profile_editor", &OpenXRActionMapEditor::_add_interaction_profile_editor);
45
46
ClassDB::bind_method(D_METHOD("_add_action_set", "name"), &OpenXRActionMapEditor::_add_action_set);
47
ClassDB::bind_method(D_METHOD("_remove_action_set", "name"), &OpenXRActionMapEditor::_remove_action_set);
48
49
ClassDB::bind_method(D_METHOD("_do_add_action_set_editor", "action_set_editor"), &OpenXRActionMapEditor::_do_add_action_set_editor);
50
ClassDB::bind_method(D_METHOD("_do_remove_action_set_editor", "action_set_editor"), &OpenXRActionMapEditor::_do_remove_action_set_editor);
51
ClassDB::bind_method(D_METHOD("_do_add_interaction_profile_editor", "interaction_profile_editor"), &OpenXRActionMapEditor::_do_add_interaction_profile_editor);
52
ClassDB::bind_method(D_METHOD("_do_remove_interaction_profile_editor", "interaction_profile_editor"), &OpenXRActionMapEditor::_do_remove_interaction_profile_editor);
53
54
ClassDB::bind_static_method("OpenXRActionMapEditor", D_METHOD("register_interaction_profile_editor", "interaction_profile_path", "editor_class"), &OpenXRActionMapEditor::register_interaction_profile_editor);
55
ClassDB::bind_static_method("OpenXRActionMapEditor", D_METHOD("register_binding_modifier_editor", "binding_modifier_class", "editor_class"), &OpenXRActionMapEditor::register_binding_modifier_editor);
56
}
57
58
void OpenXRActionMapEditor::_notification(int p_what) {
59
switch (p_what) {
60
case NOTIFICATION_THEME_CHANGED: {
61
for (int i = 0; i < tabs->get_child_count(); i++) {
62
Control *tab = Object::cast_to<Control>(tabs->get_child(i));
63
if (tab) {
64
tab->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("Tree")));
65
}
66
}
67
} break;
68
69
case NOTIFICATION_READY: {
70
_create_action_sets();
71
_create_interaction_profiles();
72
} break;
73
}
74
}
75
76
OpenXRActionSetEditor *OpenXRActionMapEditor::_add_action_set_editor(Ref<OpenXRActionSet> p_action_set) {
77
ERR_FAIL_COND_V(p_action_set.is_null(), nullptr);
78
79
OpenXRActionSetEditor *action_set_editor = memnew(OpenXRActionSetEditor(action_map, p_action_set));
80
action_set_editor->connect("remove", callable_mp(this, &OpenXRActionMapEditor::_on_remove_action_set));
81
action_set_editor->connect("action_removed", callable_mp(this, &OpenXRActionMapEditor::_on_action_removed));
82
83
actionsets_vb->add_child(action_set_editor);
84
85
return action_set_editor;
86
}
87
88
void OpenXRActionMapEditor::_create_action_sets() {
89
if (action_map.is_valid()) {
90
Array action_sets = action_map->get_action_sets();
91
for (int i = 0; i < action_sets.size(); i++) {
92
Ref<OpenXRActionSet> action_set = action_sets[i];
93
_add_action_set_editor(action_set);
94
}
95
}
96
}
97
98
OpenXRInteractionProfileEditorBase *OpenXRActionMapEditor::_add_interaction_profile_editor(Ref<OpenXRInteractionProfile> p_interaction_profile) {
99
ERR_FAIL_COND_V(p_interaction_profile.is_null(), nullptr);
100
101
String profile_path = p_interaction_profile->get_interaction_profile_path();
102
103
// need to instance the correct editor for our profile
104
OpenXRInteractionProfileEditorBase *new_profile_editor = nullptr;
105
if (interaction_profile_editors.has(profile_path)) {
106
Object *new_editor = ClassDB::instantiate(interaction_profile_editors[profile_path]);
107
if (new_editor) {
108
new_profile_editor = Object::cast_to<OpenXRInteractionProfileEditorBase>(new_editor);
109
if (!new_profile_editor) {
110
WARN_PRINT("Interaction profile editor type mismatch for " + profile_path);
111
memfree(new_editor);
112
}
113
}
114
}
115
if (!new_profile_editor) {
116
// instance generic editor
117
new_profile_editor = memnew(OpenXRInteractionProfileEditor);
118
}
119
120
// now add it in..
121
ERR_FAIL_NULL_V(new_profile_editor, nullptr);
122
new_profile_editor->setup(action_map, p_interaction_profile);
123
tabs->add_child(new_profile_editor);
124
new_profile_editor->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("Tree")));
125
tabs->set_tab_button_icon(tabs->get_tab_count() - 1, get_theme_icon(SNAME("close"), SNAME("TabBar")));
126
127
if (!new_profile_editor->tooltip.is_empty()) {
128
tabs->set_tab_tooltip(tabs->get_tab_count() - 1, new_profile_editor->tooltip);
129
}
130
131
return new_profile_editor;
132
}
133
134
void OpenXRActionMapEditor::_create_interaction_profiles() {
135
if (action_map.is_valid()) {
136
Array new_interaction_profiles = action_map->get_interaction_profiles();
137
for (int i = 0; i < new_interaction_profiles.size(); i++) {
138
Ref<OpenXRInteractionProfile> interaction_profile = new_interaction_profiles[i];
139
_add_interaction_profile_editor(interaction_profile);
140
}
141
}
142
}
143
144
OpenXRActionSetEditor *OpenXRActionMapEditor::_add_action_set(String p_name) {
145
ERR_FAIL_COND_V(action_map.is_null(), nullptr);
146
Ref<OpenXRActionSet> new_action_set;
147
148
// add our new action set
149
new_action_set.instantiate();
150
new_action_set->set_name(p_name);
151
new_action_set->set_localized_name(p_name);
152
action_map->add_action_set(new_action_set);
153
action_map->set_edited(true);
154
155
// update our editor right away
156
OpenXRActionSetEditor *action_set_editor = _add_action_set_editor(new_action_set);
157
158
undo_redo->create_action(TTR("Add action set"));
159
undo_redo->add_do_method(this, "_do_add_action_set_editor", action_set_editor);
160
undo_redo->add_undo_method(this, "_do_remove_action_set_editor", action_set_editor);
161
undo_redo->commit_action(false);
162
163
return action_set_editor;
164
}
165
166
void OpenXRActionMapEditor::_remove_action_set(String p_name) {
167
ERR_FAIL_COND(action_map.is_null());
168
Ref<OpenXRActionSet> action_set = action_map->find_action_set(p_name);
169
ERR_FAIL_COND(action_set.is_null());
170
171
for (int i = 0; i < actionsets_vb->get_child_count(); i++) {
172
OpenXRActionSetEditor *action_set_editor = Object::cast_to<OpenXRActionSetEditor>(actionsets_vb->get_child(i));
173
if (action_set_editor && action_set_editor->get_action_set() == action_set) {
174
_on_remove_action_set(action_set_editor);
175
}
176
}
177
}
178
179
void OpenXRActionMapEditor::_on_add_action_set() {
180
ERR_FAIL_COND(action_map.is_null());
181
String new_name = "New";
182
int count = 0;
183
184
while (action_map->find_action_set(new_name).is_valid()) {
185
new_name = "New_" + itos(count++);
186
}
187
188
OpenXRActionSetEditor *new_action_set_editor = _add_action_set(new_name);
189
190
// Make sure our action set is the current tab
191
tabs->set_current_tab(0);
192
193
callable_mp(this, &OpenXRActionMapEditor::_set_focus_on_action_set).call_deferred(new_action_set_editor);
194
}
195
196
void OpenXRActionMapEditor::_set_focus_on_action_set(OpenXRActionSetEditor *p_action_set_editor) {
197
// Scroll down to our new entry
198
actionsets_scroll->ensure_control_visible(p_action_set_editor);
199
200
// Set focus on this entry
201
p_action_set_editor->set_focus_on_entry();
202
}
203
204
void OpenXRActionMapEditor::_on_remove_action_set(Object *p_action_set_editor) {
205
ERR_FAIL_COND(action_map.is_null());
206
207
OpenXRActionSetEditor *action_set_editor = Object::cast_to<OpenXRActionSetEditor>(p_action_set_editor);
208
ERR_FAIL_NULL(action_set_editor);
209
ERR_FAIL_COND(action_set_editor->get_parent() != actionsets_vb);
210
Ref<OpenXRActionSet> action_set = action_set_editor->get_action_set();
211
ERR_FAIL_COND(action_set.is_null());
212
213
// Remove all actions first.
214
action_set_editor->remove_all_actions();
215
216
// Make sure we update our interaction profiles.
217
for (int i = 0; i < tabs->get_tab_count(); i++) {
218
// First tab won't be an interaction profile editor, but being thorough..
219
OpenXRInteractionProfileEditorBase *interaction_profile_editor = Object::cast_to<OpenXRInteractionProfileEditorBase>(tabs->get_tab_control(i));
220
if (interaction_profile_editor) {
221
interaction_profile_editor->remove_all_for_action_set(action_set);
222
}
223
}
224
225
// And now we can remove our action set.
226
undo_redo->create_action(TTR("Remove action set"));
227
undo_redo->add_do_method(this, "_do_remove_action_set_editor", action_set_editor);
228
undo_redo->add_undo_method(this, "_do_add_action_set_editor", action_set_editor);
229
undo_redo->commit_action(true);
230
231
action_map->set_edited(true);
232
}
233
234
void OpenXRActionMapEditor::_on_action_removed(Ref<OpenXRAction> p_action) {
235
for (int i = 0; i < tabs->get_tab_count(); i++) {
236
// First tab won't be an interaction profile editor, but being thorough..
237
OpenXRInteractionProfileEditorBase *interaction_profile_editor = Object::cast_to<OpenXRInteractionProfileEditorBase>(tabs->get_tab_control(i));
238
if (interaction_profile_editor) {
239
interaction_profile_editor->remove_all_for_action(p_action);
240
}
241
}
242
}
243
244
void OpenXRActionMapEditor::_on_add_interaction_profile() {
245
ERR_FAIL_COND(action_map.is_null());
246
247
PackedStringArray already_selected;
248
249
for (int i = 0; i < action_map->get_interaction_profile_count(); i++) {
250
already_selected.push_back(action_map->get_interaction_profile(i)->get_interaction_profile_path());
251
}
252
253
select_interaction_profile_dialog->open(already_selected);
254
}
255
256
void OpenXRActionMapEditor::_on_interaction_profile_selected(const String p_path) {
257
ERR_FAIL_COND(action_map.is_null());
258
259
Ref<OpenXRInteractionProfile> new_profile;
260
new_profile.instantiate();
261
new_profile->set_interaction_profile_path(p_path);
262
action_map->add_interaction_profile(new_profile);
263
action_map->set_edited(true);
264
265
OpenXRInteractionProfileEditorBase *interaction_profile_editor = _add_interaction_profile_editor(new_profile);
266
267
undo_redo->create_action(TTR("Add interaction profile"));
268
undo_redo->add_do_method(this, "_do_add_interaction_profile_editor", interaction_profile_editor);
269
undo_redo->add_undo_method(this, "_do_remove_interaction_profile_editor", interaction_profile_editor);
270
undo_redo->commit_action(false);
271
272
tabs->set_current_tab(tabs->get_tab_count() - 1);
273
}
274
275
void OpenXRActionMapEditor::_load_action_map(const String p_path, bool p_create_new_if_missing) {
276
Error err = OK;
277
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
278
if (da->file_exists(p_path)) {
279
action_map = ResourceLoader::load(p_path, "", ResourceFormatLoader::CACHE_MODE_REUSE, &err);
280
if (err != OK) {
281
EditorNode::get_singleton()->show_warning(vformat(TTR("Error loading %s: %s."), edited_path, error_names[err]));
282
283
edited_path = "";
284
header_label->set_text("");
285
return;
286
}
287
} else if (p_create_new_if_missing) {
288
action_map.instantiate();
289
action_map->create_default_action_sets();
290
291
// Save it immediately
292
err = ResourceSaver::save(action_map, p_path);
293
if (err != OK) {
294
// Show warning but continue.
295
EditorNode::get_singleton()->show_warning(vformat(TTR("Error saving file %s: %s"), p_path, error_names[err]));
296
}
297
}
298
299
edited_path = p_path;
300
header_label->set_text(TTR("OpenXR Action map:") + " " + edited_path.get_file());
301
}
302
303
void OpenXRActionMapEditor::_on_save_action_map() {
304
Error err = ResourceSaver::save(action_map, edited_path);
305
if (err != OK) {
306
EditorNode::get_singleton()->show_warning(vformat(TTR("Error saving file %s: %s"), edited_path, error_names[err]));
307
return;
308
}
309
310
// TODO should clear undo/redo history
311
312
// out with the old
313
_clear_action_map();
314
315
_create_action_sets();
316
_create_interaction_profiles();
317
}
318
319
void OpenXRActionMapEditor::_on_reset_to_default_layout() {
320
// TODO should clear undo/redo history
321
322
// out with the old
323
_clear_action_map();
324
325
// create a new one
326
action_map.unref();
327
action_map.instantiate();
328
action_map->create_default_action_sets();
329
action_map->set_edited(true);
330
331
_create_action_sets();
332
_create_interaction_profiles();
333
}
334
335
void OpenXRActionMapEditor::_on_tabs_tab_changed(int p_tab) {
336
}
337
338
void OpenXRActionMapEditor::_on_tab_button_pressed(int p_tab) {
339
OpenXRInteractionProfileEditorBase *interaction_profile_editor = Object::cast_to<OpenXRInteractionProfileEditorBase>(tabs->get_tab_control(p_tab));
340
ERR_FAIL_NULL(interaction_profile_editor);
341
342
undo_redo->create_action(TTR("Remove interaction profile"));
343
undo_redo->add_do_method(this, "_do_remove_interaction_profile_editor", interaction_profile_editor);
344
undo_redo->add_undo_method(this, "_do_add_interaction_profile_editor", interaction_profile_editor);
345
undo_redo->commit_action(true);
346
347
action_map->set_edited(true);
348
}
349
350
void OpenXRActionMapEditor::_do_add_action_set_editor(OpenXRActionSetEditor *p_action_set_editor) {
351
Ref<OpenXRActionSet> action_set = p_action_set_editor->get_action_set();
352
ERR_FAIL_COND(action_set.is_null());
353
354
action_map->add_action_set(action_set);
355
actionsets_vb->add_child(p_action_set_editor);
356
}
357
358
void OpenXRActionMapEditor::_do_remove_action_set_editor(OpenXRActionSetEditor *p_action_set_editor) {
359
Ref<OpenXRActionSet> action_set = p_action_set_editor->get_action_set();
360
ERR_FAIL_COND(action_set.is_null());
361
362
actionsets_vb->remove_child(p_action_set_editor);
363
action_map->remove_action_set(action_set);
364
}
365
366
void OpenXRActionMapEditor::_do_add_interaction_profile_editor(OpenXRInteractionProfileEditorBase *p_interaction_profile_editor) {
367
Ref<OpenXRInteractionProfile> interaction_profile = p_interaction_profile_editor->get_interaction_profile();
368
ERR_FAIL_COND(interaction_profile.is_null());
369
370
action_map->add_interaction_profile(interaction_profile);
371
tabs->add_child(p_interaction_profile_editor);
372
tabs->set_tab_button_icon(tabs->get_tab_count() - 1, get_theme_icon(SNAME("close"), SNAME("TabBar")));
373
374
tabs->set_current_tab(tabs->get_tab_count() - 1);
375
}
376
377
void OpenXRActionMapEditor::_do_remove_interaction_profile_editor(OpenXRInteractionProfileEditorBase *p_interaction_profile_editor) {
378
Ref<OpenXRInteractionProfile> interaction_profile = p_interaction_profile_editor->get_interaction_profile();
379
ERR_FAIL_COND(interaction_profile.is_null());
380
381
tabs->remove_child(p_interaction_profile_editor);
382
action_map->remove_interaction_profile(interaction_profile);
383
}
384
385
void OpenXRActionMapEditor::open_action_map(String p_path) {
386
EditorNode::get_bottom_panel()->make_item_visible(this);
387
388
// out with the old...
389
_clear_action_map();
390
391
// now load in our new action map
392
_load_action_map(p_path);
393
394
_create_action_sets();
395
_create_interaction_profiles();
396
}
397
398
void OpenXRActionMapEditor::_clear_action_map() {
399
while (actionsets_vb->get_child_count() > 0) {
400
Node *child = actionsets_vb->get_child(0);
401
actionsets_vb->remove_child(child);
402
child->queue_free();
403
}
404
405
for (int i = tabs->get_tab_count() - 1; i >= 0; --i) {
406
// First tab won't be an interaction profile editor, but being thorough..
407
OpenXRInteractionProfileEditorBase *interaction_profile_editor = Object::cast_to<OpenXRInteractionProfileEditorBase>(tabs->get_tab_control(i));
408
if (interaction_profile_editor) {
409
tabs->remove_child(interaction_profile_editor);
410
interaction_profile_editor->queue_free();
411
}
412
}
413
}
414
415
void OpenXRActionMapEditor::register_interaction_profile_editor(const String &p_for_path, const String &p_editor_class) {
416
interaction_profile_editors[p_for_path] = p_editor_class;
417
}
418
419
void OpenXRActionMapEditor::register_binding_modifier_editor(const String &p_binding_modifier_class, const String &p_editor_class) {
420
binding_modifier_editors[p_binding_modifier_class] = p_editor_class;
421
}
422
423
String OpenXRActionMapEditor::get_binding_modifier_editor_class(const String &p_binding_modifier_class) {
424
if (binding_modifier_editors.has(p_binding_modifier_class)) {
425
return binding_modifier_editors[p_binding_modifier_class];
426
}
427
428
return OpenXRBindingModifierEditor::get_class_static();
429
}
430
431
OpenXRActionMapEditor::OpenXRActionMapEditor() {
432
undo_redo = EditorUndoRedoManager::get_singleton();
433
set_custom_minimum_size(Size2(0.0, 300.0 * EDSCALE));
434
435
top_hb = memnew(HBoxContainer);
436
add_child(top_hb);
437
438
header_label = memnew(Label);
439
header_label->set_text(String(TTR("Action Map")));
440
header_label->set_clip_text(true);
441
header_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
442
top_hb->add_child(header_label);
443
444
add_action_set = memnew(Button);
445
add_action_set->set_text(TTR("Add Action Set"));
446
add_action_set->set_tooltip_text(TTR("Add an action set."));
447
add_action_set->connect(SceneStringName(pressed), callable_mp(this, &OpenXRActionMapEditor::_on_add_action_set));
448
top_hb->add_child(add_action_set);
449
450
add_interaction_profile = memnew(Button);
451
add_interaction_profile->set_text(TTR("Add profile"));
452
add_interaction_profile->set_tooltip_text(TTR("Add an interaction profile."));
453
add_interaction_profile->connect(SceneStringName(pressed), callable_mp(this, &OpenXRActionMapEditor::_on_add_interaction_profile));
454
top_hb->add_child(add_interaction_profile);
455
456
VSeparator *vseparator = memnew(VSeparator);
457
top_hb->add_child(vseparator);
458
459
save_as = memnew(Button);
460
save_as->set_text(TTR("Save"));
461
save_as->set_tooltip_text(TTR("Save this OpenXR action map."));
462
save_as->connect(SceneStringName(pressed), callable_mp(this, &OpenXRActionMapEditor::_on_save_action_map));
463
top_hb->add_child(save_as);
464
465
_default = memnew(Button);
466
_default->set_text(TTR("Reset to Default"));
467
_default->set_tooltip_text(TTR("Reset to default OpenXR action map."));
468
_default->connect(SceneStringName(pressed), callable_mp(this, &OpenXRActionMapEditor::_on_reset_to_default_layout));
469
top_hb->add_child(_default);
470
471
tabs = memnew(TabContainer);
472
tabs->set_h_size_flags(SIZE_EXPAND_FILL);
473
tabs->set_v_size_flags(SIZE_EXPAND_FILL);
474
tabs->set_theme_type_variation("TabContainerOdd");
475
tabs->connect("tab_changed", callable_mp(this, &OpenXRActionMapEditor::_on_tabs_tab_changed));
476
tabs->connect("tab_button_pressed", callable_mp(this, &OpenXRActionMapEditor::_on_tab_button_pressed));
477
add_child(tabs);
478
479
actionsets_scroll = memnew(ScrollContainer);
480
actionsets_scroll->set_h_size_flags(SIZE_EXPAND_FILL);
481
actionsets_scroll->set_v_size_flags(SIZE_EXPAND_FILL);
482
actionsets_scroll->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED);
483
tabs->add_child(actionsets_scroll);
484
actionsets_scroll->set_name(TTR("Action Sets"));
485
486
actionsets_vb = memnew(VBoxContainer);
487
actionsets_vb->set_h_size_flags(SIZE_EXPAND_FILL);
488
actionsets_scroll->add_child(actionsets_vb);
489
490
select_interaction_profile_dialog = memnew(OpenXRSelectInteractionProfileDialog);
491
select_interaction_profile_dialog->connect("interaction_profile_selected", callable_mp(this, &OpenXRActionMapEditor::_on_interaction_profile_selected));
492
add_child(select_interaction_profile_dialog);
493
494
// Our Action map editor is only shown if openxr is enabled in project settings
495
// So load our action map and if it doesn't exist, create it right away.
496
_load_action_map(ResourceUID::ensure_path(GLOBAL_GET("xr/openxr/default_action_map")), true);
497
}
498
499