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