Unravel Engine C++ Reference
Loading...
Searching...
No Matches
hierarchy_panel.cpp
Go to the documentation of this file.
1#include "hierarchy_panel.h"
2#include "../panel.h"
3#include "../panels_defs.h"
4#include "imgui/imgui.h"
5#include "imgui_widgets/tooltips.h"
6#include <imgui/imgui_internal.h>
7
9#include <editor/events.h>
11
18#include <engine/ecs/ecs.h>
20
22
23namespace unravel
24{
25
26namespace
27{
28
29// ============================================================================
30// State Management
31// ============================================================================
32
33// Label editing state
34bool prev_edit_label{};
35bool edit_label_{};
36
37auto update_editing() -> void
38{
39 prev_edit_label = edit_label_;
40}
41
42auto is_just_started_editing_label() -> bool
43{
44 return edit_label_ && edit_label_ != prev_edit_label;
45}
46
47auto is_editing_label() -> bool
48{
49 return edit_label_;
50}
51
52void start_editing_label(rtti::context& ctx, imgui_panels* panels, entt::handle entity)
53{
54 auto& em = ctx.get_cached<editing_manager>();
55 em.select(entity);
56 edit_label_ = true;
57}
58
59void stop_editing_label(rtti::context& ctx, imgui_panels* panels, entt::handle entity)
60{
61 edit_label_ = false;
62}
63
64// ============================================================================
65// Entity Creation Helper Functions
66// ============================================================================
67
68void create_empty_entity(rtti::context& ctx, imgui_panels* panels, entt::handle parent_entity)
69{
70 auto& em = ctx.get_cached<editing_manager>();
71 em.queue_action("Create Empty Entity",
72 [&ctx, panels, parent_entity]() mutable
73 {
74 auto& em = ctx.get_cached<editing_manager>();
75 auto* active_scene = em.get_active_scene(ctx);
76 if (active_scene) {
77 auto new_entity = active_scene->create_entity({}, parent_entity);
78 start_editing_label(ctx, panels, new_entity);
79 }
80 });
81}
82
83void create_empty_parent_entity(rtti::context& ctx, imgui_panels* panels, entt::handle child_entity)
84{
85 auto& em = ctx.get_cached<editing_manager>();
86 em.queue_action("Create Empty Parent Entity",
87 [&ctx, panels, child_entity]() mutable
88 {
89 auto current_parent = child_entity.get<transform_component>().get_parent();
90 auto& em = ctx.get_cached<editing_manager>();
91 auto* active_scene = em.get_active_scene(ctx);
92
93 if (active_scene) {
94 auto new_entity = active_scene->create_entity({}, current_parent);
95 child_entity.get<transform_component>().set_parent(new_entity);
96 start_editing_label(ctx, panels, new_entity);
97 }
98 });
99}
100
101void create_mesh_entity(rtti::context& ctx, imgui_panels* panels, entt::handle parent_entity, const std::string& mesh_name)
102{
103 auto& em = ctx.get_cached<editing_manager>();
104 em.queue_action("Create Mesh Entity",
105 [&ctx, panels, parent_entity, mesh_name]() mutable
106 {
107 auto& em = ctx.get_cached<editing_manager>();
108 auto* active_scene = em.get_active_scene(ctx);
109 if (active_scene) {
110 auto object = defaults::create_embedded_mesh_entity(ctx, *active_scene, mesh_name);
111
112 if(object)
113 {
114 object.get<transform_component>().set_parent(parent_entity, false);
115 }
116 em.select(object);
117 start_editing_label(ctx, panels, object);
118 }
119 });
120}
121
122void create_text_entity(rtti::context& ctx, imgui_panels* panels, entt::handle parent_entity)
123{
124 auto& em = ctx.get_cached<editing_manager>();
125 em.queue_action("Create Text Entity",
126 [&ctx, panels, parent_entity]() mutable
127 {
128 auto& em = ctx.get_cached<editing_manager>();
129 auto* active_scene = em.get_active_scene(ctx);
130 auto object = defaults::create_text_entity(ctx, *active_scene, "Text");
131
132 if(object)
133 {
134 object.get<transform_component>().set_parent(parent_entity, false);
135 }
136 em.select(object);
137 start_editing_label(ctx, panels, object);
138 });
139}
140
141void create_particle_emitter_entity(rtti::context& ctx, imgui_panels* panels, entt::handle parent_entity)
142{
143 auto& em = ctx.get_cached<editing_manager>();
144 em.queue_action("Create Particle Emitter Entity",
145 [&ctx, panels, parent_entity]() mutable
146 {
147 auto& em = ctx.get_cached<editing_manager>();
148 auto* active_scene = em.get_active_scene(ctx);
149 auto object = defaults::create_particle_emitter_entity(ctx, *active_scene, "Particle Emitter");
150 if(object)
151 {
152 object.get<transform_component>().set_parent(parent_entity, false);
153 }
154 em.select(object);
155 start_editing_label(ctx, panels, object);
156 });
157}
158
159void create_light_entity(rtti::context& ctx, imgui_panels* panels, entt::handle parent_entity, light_type type, const std::string& name)
160{
161 auto& em = ctx.get_cached<editing_manager>();
162 em.queue_action("Create Light Entity",
163 [&ctx, panels, parent_entity, type, name]() mutable
164 {
165 auto& em = ctx.get_cached<editing_manager>();
166 auto* active_scene = em.get_active_scene(ctx);
167 auto object = defaults::create_light_entity(ctx, *active_scene, type, name);
168 if(object)
169 {
170 object.get<transform_component>().set_parent(parent_entity, false);
171 }
172 em.select(object);
173 start_editing_label(ctx, panels, object);
174 });
175}
176
177void create_reflection_probe_entity(rtti::context& ctx, imgui_panels* panels, entt::handle parent_entity, probe_type type, const std::string& name)
178{
179 auto& em = ctx.get_cached<editing_manager>();
180 em.queue_action("Create Reflection Probe Entity",
181 [&ctx, panels, parent_entity, type, name]() mutable
182 {
183 auto& em = ctx.get_cached<editing_manager>();
184 auto* active_scene = em.get_active_scene(ctx);
185 auto object = defaults::create_reflection_probe_entity(ctx, *active_scene, type, name);
186 if(object)
187 {
188 object.get<transform_component>().set_parent(parent_entity, false);
189 }
190 em.select(object);
191 start_editing_label(ctx, panels, object);
192 });
193}
194
195void create_camera_entity(rtti::context& ctx, imgui_panels* panels, entt::handle parent_entity)
196{
197 auto& em = ctx.get_cached<editing_manager>();
198 em.queue_action("Create Camera Entity",
199 [&ctx, panels, parent_entity]() mutable
200 {
201 auto& em = ctx.get_cached<editing_manager>();
202 auto* active_scene = em.get_active_scene(ctx);
203 auto object = defaults::create_camera_entity(ctx, *active_scene, "Camera");
204 if(object)
205 {
206 object.get<transform_component>().set_parent(parent_entity, false);
207 }
208 em.select(object);
209 start_editing_label(ctx, panels, object);
210 });
211}
212
213void create_audio_source_entity(rtti::context& ctx, imgui_panels* panels, entt::handle parent_entity)
214{
215 auto& em = ctx.get_cached<editing_manager>();
216 em.queue_action("Create Audio Source Entity",
217 [&ctx, panels, parent_entity]() mutable
218 {
219 auto& em = ctx.get_cached<editing_manager>();
220 auto* active_scene = em.get_active_scene(ctx);
221 auto object = defaults::create_audio_source_entity(ctx, *active_scene, "Audio Source");
222 if(object)
223 {
224 object.get<transform_component>().set_parent(parent_entity, false);
225 }
226 em.select(object);
227 start_editing_label(ctx, panels, object);
228 });
229}
230
231void create_ui_document_entity(rtti::context& ctx, imgui_panels* panels, entt::handle parent_entity)
232{
233 auto& em = ctx.get_cached<editing_manager>();
234 em.queue_action("Create UI Document Entity",
235 [&ctx, panels, parent_entity]() mutable
236 {
237 auto& em = ctx.get_cached<editing_manager>();
238 auto* active_scene = em.get_active_scene(ctx);
239 auto object = defaults::create_ui_document_entity(ctx, *active_scene, "UI Document");
240 if(object)
241 {
242 object.get<transform_component>().set_parent(parent_entity, false);
243 }
244 em.select(object);
245 start_editing_label(ctx, panels, object);
246 });
247}
248
249// ============================================================================
250// Drag and Drop Operations
251// ============================================================================
252
253auto process_drag_drop_source(entt::handle entity) -> bool
254{
255 if(entity && ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceAllowNullID))
256 {
257 ImGui::TextUnformatted(entity_panel::get_entity_name(entity).c_str());
258 ImGui::SetDragDropPayload("entity", &entity, sizeof(entity));
259 ImGui::EndDragDropSource();
260 return true;
261 }
262
263 return false;
264}
265
266void handle_entity_drop(rtti::context& ctx, imgui_panels* panels, entt::handle target_entity, entt::handle dropped_entity)
267{
268 auto& em = ctx.get_cached<editing_manager>();
269
270 auto do_action = [&](entt::handle dropped)
271 {
272 auto& em = ctx.get_cached<editing_manager>();
273 em.queue_action("Drop Entity",
274 [&ctx, target_entity, dropped]() mutable
275 {
276 auto trans_comp = dropped.try_get<transform_component>();
277 if(trans_comp)
278 {
279 trans_comp->set_parent(target_entity);
280 }
281 });
282 };
283
284 if(em.is_selected(dropped_entity))
285 {
286 for(auto e : em.try_get_selections_as<entt::handle>())
287 {
288 if(e)
289 {
290 do_action(*e);
291 }
292 }
293 }
294 else
295 {
296 do_action(dropped_entity);
297 }
298}
299
300void handle_mesh_drop(rtti::context& ctx, const std::string& absolute_path)
301{
302
303 auto& em = ctx.get_cached<editing_manager>();
304 em.queue_action("Drop Mesh",
305 [&ctx, absolute_path]() mutable
306 {
307 std::string key = fs::convert_to_protocol(fs::path(absolute_path)).generic_string();
308 auto& em = ctx.get_cached<editing_manager>();
309 auto* active_scene = em.get_active_scene(ctx);
310
311 if (active_scene)
312 {
313 auto object = defaults::create_mesh_entity_at(ctx, *active_scene, key);
314 em.select(object);
315 }
316 });
317
318}
319
320void handle_prefab_drop(rtti::context& ctx, const std::string& absolute_path)
321{
322 auto& em = ctx.get_cached<editing_manager>();
323 em.queue_action("Drop Prefab",
324 [&ctx, absolute_path]() mutable
325 {
326 std::string key = fs::convert_to_protocol(fs::path(absolute_path)).generic_string();
327
328 auto& em = ctx.get_cached<editing_manager>();
329 auto* active_scene = em.get_active_scene(ctx);
330
331 if (active_scene)
332 {
333 auto object = defaults::create_prefab_at(ctx, *active_scene, key);
334 em.select(object);
335 }
336 });
337}
338
339void process_drag_drop_target(rtti::context& ctx, imgui_panels* panels, entt::handle entity)
340{
341 if(!ImGui::BeginDragDropTarget())
342 {
343 return;
344 }
345
346 if(ImGui::IsDragDropPayloadBeingAccepted())
347 {
348 ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
349 }
350 else
351 {
352 ImGui::SetMouseCursor(ImGuiMouseCursor_NotAllowed);
353 }
354
355 // Handle entity drag and drop
356 auto payload = ImGui::AcceptDragDropPayload("entity");
357 if(payload != nullptr)
358 {
359 entt::handle dropped{};
360 std::memcpy(&dropped, payload->Data, size_t(payload->DataSize));
361 if(dropped)
362 {
363 handle_entity_drop(ctx, panels, entity, dropped);
364 }
365 }
366
367 // Handle mesh drag and drop
368 for(const auto& type : ex::get_suported_formats<mesh>())
369 {
370 auto mesh_payload = ImGui::AcceptDragDropPayload(type.c_str());
371 if(mesh_payload != nullptr)
372 {
373 std::string absolute_path(reinterpret_cast<const char*>(mesh_payload->Data), std::size_t(mesh_payload->DataSize));
374 handle_mesh_drop(ctx, absolute_path);
375 }
376 }
377
378 // Handle prefab drag and drop
379 for(const auto& type : ex::get_suported_formats<prefab>())
380 {
381 auto prefab_payload = ImGui::AcceptDragDropPayload(type.c_str());
382 if(prefab_payload != nullptr)
383 {
384 std::string absolute_path(reinterpret_cast<const char*>(prefab_payload->Data), std::size_t(prefab_payload->DataSize));
385 handle_prefab_drop(ctx, absolute_path);
386 }
387 }
388
389 ImGui::EndDragDropTarget();
390}
391
392void check_drag(rtti::context& ctx, imgui_panels* panels, entt::handle entity)
393{
394 if(!process_drag_drop_source(entity))
395 {
396 process_drag_drop_target(ctx, panels, entity);
397 }
398}
399
400// ============================================================================
401// Context Menu Functions
402// ============================================================================
403
404void draw_3d_objects_menu(rtti::context& ctx, imgui_panels* panels, entt::handle parent_entity)
405{
406 if(!ImGui::BeginMenu("3D Objects"))
407 {
408 return;
409 }
410
411 static const std::vector<std::pair<std::string, std::vector<std::string>>> menu_objects = {
412 {"Cube", {"Cube"}},
413 {"Cube Rounded", {"Cube Rounded"}},
414 {"Sphere", {"Sphere"}},
415 {"Plane", {"Plane"}},
416 {"Cylinder", {"Cylinder"}},
417 {"Capsule_1m", {"Capsule_1m"}},
418 {"Capsule_2m", {"Capsule_2m"}},
419 {"Cone", {"Cone"}},
420 {"Torus", {"Torus"}},
421 {"Teapot", {"Teapot"}},
422 {"Separator", {}},
423 {"Polygon", {"Icosahedron", "Dodecahedron"}},
424 {"Icosphere", {"Icosphere0", "Icosphere1", "Icosphere2", "Icosphere3", "Icosphere4",
425 "Icosphere5", "Icosphere6", "Icosphere7", "Icosphere8", "Icosphere9",
426 "Icosphere10", "Icosphere11", "Icosphere12", "Icosphere13", "Icosphere14",
427 "Icosphere15", "Icosphere16", "Icosphere17", "Icosphere18", "Icosphere19"}}};
428
429 for(const auto& p : menu_objects)
430 {
431 const auto& name = p.first;
432 const auto& objects_name = p.second;
433
434 if(name == "Separator")
435 {
436 ImGui::Separator();
437 }
438 else if(name == "New Line")
439 {
441 }
442 else if(objects_name.size() == 1)
443 {
444 if(ImGui::MenuItem(name.c_str()))
445 {
446 create_mesh_entity(ctx, panels, parent_entity, name);
447 }
448 }
449 else
450 {
451 if(ImGui::BeginMenu(name.c_str()))
452 {
453 for(const auto& n : objects_name)
454 {
455 if(ImGui::MenuItem(n.c_str()))
456 {
457 create_mesh_entity(ctx, panels, parent_entity, n);
458 }
459 }
460 ImGui::EndMenu();
461 }
462 }
463 }
464
466 ImGui::Separator();
467
468 if(ImGui::MenuItem("Text"))
469 {
470 create_text_entity(ctx, panels, parent_entity);
471 }
472
473 ImGui::EndMenu();
474}
475
476void draw_lighting_menu(rtti::context& ctx, imgui_panels* panels, entt::handle parent_entity)
477{
478 if(!ImGui::BeginMenu("Lighting"))
479 {
480 return;
481 }
482
483 // Light submenu
484 if(ImGui::BeginMenu("Light"))
485 {
486 static const std::vector<std::pair<std::string, light_type>> light_objects = {
487 {"Directional", light_type::directional},
488 {"Spot", light_type::spot},
489 {"Point", light_type::point}};
490
491 for(const auto& p : light_objects)
492 {
493 const auto& name = p.first;
494 const auto& type = p.second;
495 if(ImGui::MenuItem(name.c_str()))
496 {
497 create_light_entity(ctx, panels, parent_entity, type, name);
498 }
499 }
500 ImGui::EndMenu();
501 }
502
503 // Reflection probes submenu
504 if(ImGui::BeginMenu("Reflection Probes"))
505 {
506 static const std::vector<std::pair<std::string, probe_type>> reflection_probes = {
507 {"Sphere", probe_type::sphere},
508 {"Box", probe_type::box}};
509
510 for(const auto& p : reflection_probes)
511 {
512 const auto& name = p.first;
513 const auto& type = p.second;
514
515 if(ImGui::MenuItem(name.c_str()))
516 {
517 create_reflection_probe_entity(ctx, panels, parent_entity, type, name);
518 }
519 }
520 ImGui::EndMenu();
521 }
522
523 ImGui::EndMenu();
524}
525
526void draw_common_menu_items(rtti::context& ctx, imgui_panels* panels, entt::handle parent_entity)
527{
528 if(ImGui::MenuItem("Create Empty"))
529 {
530 create_empty_entity(ctx, panels, parent_entity);
531 }
532
533 draw_3d_objects_menu(ctx, panels, parent_entity);
534 draw_lighting_menu(ctx, panels, parent_entity);
535
536 if(ImGui::MenuItem("Camera"))
537 {
538 create_camera_entity(ctx, panels, parent_entity);
539 }
540
541 if(ImGui::MenuItem("Audio Source"))
542 {
543 create_audio_source_entity(ctx, panels, parent_entity);
544 }
545
546
547 if(ImGui::MenuItem("Particle Emitter"))
548 {
549 create_particle_emitter_entity(ctx, panels, parent_entity);
550 }
551
552 if(ImGui::MenuItem("UI Document"))
553 {
554 create_ui_document_entity(ctx, panels, parent_entity);
555 }
556}
557
558void draw_entity_context_menu(rtti::context& ctx, imgui_panels* panels, entt::handle entity)
559{
560 if(!ImGui::BeginPopupContextItem("Entity Context Menu"))
561 {
562 return;
563 }
564
565 if(ImGui::MenuItem("Create Empty Parent"))
566 {
567 create_empty_parent_entity(ctx, panels, entity);
568 }
569
570 draw_common_menu_items(ctx, panels, entity);
571
572 ImGui::Separator();
573
574 if(ImGui::MenuItem("Rename", ImGui::GetKeyName(shortcuts::rename_item)))
575 {
576 auto& em = ctx.get_cached<editing_manager>();
577 em.queue_action("Rename Entity",
578 [ctx, panels, entity]() mutable
579 {
580 start_editing_label(ctx, panels, entity);
581 });
582 }
583
584 if(ImGui::MenuItem("Duplicate", ImGui::GetKeyCombinationName(shortcuts::duplicate_item).c_str()))
585 {
586 panels->get_scene_panel().duplicate_entities({entity});
587 }
588
589 if(ImGui::MenuItem("Delete", ImGui::GetKeyName(shortcuts::delete_item)))
590 {
591 panels->get_scene_panel().delete_entities({entity});
592 }
593
594 if(ImGui::MenuItem("Focus", ImGui::GetKeyName(shortcuts::focus_selected)))
595 {
596 panels->get_scene_panel().focus_entities(panels->get_scene_panel().get_camera(), {entity});
597 }
598
599 ImGui::Separator();
600
601 if(entity.any_of<prefab_component, prefab_id_component>())
602 {
603 if(ImGui::MenuItem("Open Prefab"))
604 {
605 auto& em = ctx.get_cached<editing_manager>();
606 em.queue_action("Open Prefab",
607 [&ctx, entity, panels]() mutable
608 {
610 if(prefab_root)
611 {
612 auto prefab = prefab_root.get<prefab_component>().source;
613 if(prefab)
614 {
615 auto& em = ctx.get_cached<editing_manager>();
616 em.enter_prefab_mode(ctx, prefab, true);
617 }
618 }
619 });
620 }
621
622 if(ImGui::MenuItem("Unlink from Prefab"))
623 {
624 auto& em = ctx.get_cached<editing_manager>();
625 em.queue_action("Unlink from Prefab",
626 [entity]() mutable
627 {
628 entity.remove<prefab_component, prefab_id_component>();
629 });
630 }
631 }
632
633 ImGui::EndPopup();
634}
635
636void draw_window_context_menu(rtti::context& ctx, imgui_panels* panels)
637{
638 if(!ImGui::BeginPopupContextWindowEx())
639 {
640 return;
641 }
642
643 draw_common_menu_items(ctx, panels, {});
644 ImGui::EndPopup();
645}
646
647void check_context_menu(rtti::context& ctx, imgui_panels* panels, entt::handle entity)
648{
649 ImGui::PushStyleColor(ImGuiCol_Separator, ImGui::GetStyleColorVec4(ImGuiCol_Text));
650
651 if(entity)
652 {
653 draw_entity_context_menu(ctx, panels, entity);
654 }
655 else
656 {
657 draw_window_context_menu(ctx, panels);
658 }
659
660 ImGui::PopStyleColor();
661}
662
663// ============================================================================
664// Entity Drawing and Interaction
665// ============================================================================
666
667void draw_activity(rtti::context& ctx, transform_component& trans_comp)
668{
669 bool is_active_local = trans_comp.is_active();
670 if(!is_active_local)
671 {
672 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.5f, 0.5f, 0.5f, 1.0f));
673 }
674
675 if(ImGui::Button(is_active_local ? ICON_MDI_EYE : ICON_MDI_EYE_OFF))
676 {
677 trans_comp.set_active(!is_active_local);
678
679 auto entity = trans_comp.get_owner();
680 auto& em = ctx.get_cached<editing_manager>();
681
682 em.push_undo_stack_enabled(true);
683
684 em.queue_action<entity_set_active_action_t>({},
685 entity,
686 is_active_local,
687 !is_active_local);
688
689 em.pop_undo_stack_enabled();
690
691 }
692
693 if(!is_active_local)
694 {
695 ImGui::PopStyleColor();
696 }
697}
698
699auto is_parent_of_focused(rtti::context& ctx, entt::handle entity) -> bool
700{
701 auto& em = ctx.get_cached<editing_manager>();
702 auto focus = em.try_get_active_focus_as<entt::handle>();
703 if(focus)
704 {
706 {
707 return true;
708 }
709 }
710
711 return false;
712}
713
714auto get_entity_tree_node_flags(rtti::context& ctx, entt::handle entity, bool has_children) -> ImGuiTreeNodeFlags
715{
716 auto& em = ctx.get_cached<editing_manager>();
717 ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_AllowOverlap | ImGuiTreeNodeFlags_OpenOnArrow;
718
719 if(em.is_selected(entity))
720 {
721 flags |= ImGuiTreeNodeFlags_Selected;
722 }
723
724 if(!has_children)
725 {
726 flags |= ImGuiTreeNodeFlags_Leaf;
727 }
728
729 flags |= ImGuiTreeNodeFlags_DrawLinesToNodes;
730
731 return flags;
732}
733
734auto get_entity_display_label(entt::handle entity) -> std::string
735{
738
739 const auto ent = entity.entity();
740 const auto id = entt::to_integral(ent);
741
742 return icon + name +"###" + std::to_string(id);
743}
744
745void handle_entity_selection(rtti::context& ctx, imgui_panels* panels, entt::handle entity)
746{
747 auto& em = ctx.get_cached<editing_manager>();
748 auto mode = em.get_select_mode();
749 em.queue_action("Select Entity",
750 [&ctx, panels, entity, mode]() mutable
751 {
752 stop_editing_label(ctx, panels, entity);
753 auto& em = ctx.get_cached<editing_manager>();
754 em.select(entity, mode);
755 });
756}
757
758void handle_entity_keyboard_shortcuts(rtti::context& ctx, imgui_panels* panels, entt::handle entity)
759{
760 if(ImGui::IsItemKeyPressed(shortcuts::rename_item))
761 {
762 auto& em = ctx.get_cached<editing_manager>();
763 em.queue_action("Rename Entity",
764 [&ctx, panels, entity]() mutable
765 {
766 start_editing_label(ctx, panels, entity);
767 });
768 }
769
770 if(ImGui::IsItemKeyPressed(shortcuts::delete_item))
771 {
772 panels->get_scene_panel().delete_entities({entity});
773 }
774
775 if(ImGui::IsItemKeyPressed(shortcuts::focus_selected))
776 {
777 panels->get_scene_panel().focus_entities(panels->get_scene_panel().get_camera(), {entity});
778 }
779
780 if(ImGui::IsItemCombinationKeyPressed(shortcuts::duplicate_item))
781 {
782 panels->get_scene_panel().duplicate_entities({entity});
783 }
784}
785
786void handle_entity_mouse_interactions(rtti::context& ctx, imgui_panels* panels, entt::handle entity, bool is_item_clicked_middle, bool is_item_double_clicked_left)
787{
788 if(is_item_clicked_middle)
789 {
790 panels->get_scene_panel().focus_entities(panels->get_scene_panel().get_camera(), {entity});
791 }
792
793 if(is_item_double_clicked_left)
794 {
795 panels->get_scene_panel().focus_entities(panels->get_scene_panel().get_camera(), {entity});
796 }
797}
798
799void draw_entity_name_editor(rtti::context& ctx, imgui_panels* panels, entt::handle entity, const ImVec2& pos)
800{
801 auto& em = ctx.get_cached<editing_manager>();
802 if(!em.is_selected(entity) || !is_editing_label())
803 {
804 return;
805 }
806
807 if(is_just_started_editing_label())
808 {
809 ImGui::SetKeyboardFocusHere();
810 }
811
812 ImGui::SetCursorScreenPos(pos);
813 ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x);
814
815 auto edit_name = entity_panel::get_entity_name(entity);
816 auto old_name = edit_name;
817 ImGui::InputTextWidget("##rename", edit_name, false, ImGuiInputTextFlags_AutoSelectAll);
818
819 if(ImGui::IsItemDeactivatedAfterEdit())
820 {
821
822 auto& em = ctx.get_cached<editing_manager>();
823 em.push_undo_stack_enabled(true);
824 em.queue_action<entity_set_name_action_t>({},
825 entity,
826 old_name,
827 edit_name);
828 em.pop_undo_stack_enabled();
829 stop_editing_label(ctx, panels, entity);
830 }
831
832 ImGui::PopItemWidth();
833
834 if(ImGui::IsItemDeactivated())
835 {
836 stop_editing_label(ctx, panels, entity);
837 }
838}
839
840void draw_entity(rtti::context& ctx, imgui_panels* panels, entt::handle entity)
841{
842 if(!entity)
843 {
844 return;
845 }
846
847 auto& em = ctx.get_cached<editing_manager>();
848 ImGui::PushID(static_cast<int>(entity.entity()));
849
850 auto& trans_comp = entity.get<transform_component>();
851 bool has_children = !trans_comp.get_children().empty();
852
853 ImGuiTreeNodeFlags flags = get_entity_tree_node_flags(ctx, entity, has_children);
854
855 if(is_parent_of_focused(ctx, entity))
856 {
857 ImGui::SetNextItemOpen(true, 0);
858 ImGui::SetScrollHereY();
859 }
860
861 auto pos = ImGui::GetCursorScreenPos() + ImVec2(ImGui::GetTextLineHeightWithSpacing(), 0.0f);
862 ImGui::AlignTextToFramePadding();
863
864 auto label = get_entity_display_label(entity);
866
867 ImGui::PushStyleColor(ImGuiCol_Text, col);
868 ImGui::PushStyleVarX(ImGuiStyleVar_ItemInnerSpacing, 0.0f);
869 bool opened = ImGui::TreeNodeEx(label.c_str(), flags);
870 ImGui::PopStyleVar();
871
872 if(ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip))
873 {
874 const auto ent = entity.entity();
875 const auto idx = entt::to_entity(ent);
876 const auto ver = entt::to_version(ent);
877 const auto id = entt::to_integral(ent);
878
879 ImGui::SetItemTooltipEx("Id: %d\nIndex: %d\nVersion: %d", id, idx, ver);
880 }
881
882 ImGui::PopStyleColor();
883
884 if(em.is_focused(entity))
885 {
886 ImGui::SetItemFocusFrame(ImGui::GetColorU32(ImVec4(1.0f, 1.0f, 0.0f, 1.0f)));
887 }
888
889 if(!is_editing_label())
890 {
891 check_drag(ctx, panels, entity);
892 check_context_menu(ctx, panels, entity);
893 }
894
895 // Collect interaction states
896 bool is_item_focus_changed = ImGui::IsItemFocusChanged();
897 bool is_item_released_left = ImGui::IsItemReleased(ImGuiMouseButton_Left);
898 bool is_item_clicked_middle = ImGui::IsItemClicked(ImGuiMouseButton_Middle);
899 bool is_item_double_clicked_left = ImGui::IsItemDoubleClicked(ImGuiMouseButton_Left);
900 bool activity_hovered = false;
901
902 // Draw activity button
903 ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
904 ImGui::AlignedItem(1.0f,
905 ImGui::GetContentRegionAvail().x - ImGui::GetStyle().FramePadding.x,
906 ImGui::GetFrameHeight(),
907 [&]()
908 {
909 draw_activity(ctx, trans_comp);
910 activity_hovered = ImGui::IsItemHovered();
911 });
912
913 // Handle interactions (only if not hovering activity button)
914 if(!activity_hovered)
915 {
916 if(is_item_released_left || is_item_focus_changed)
917 {
918 handle_entity_selection(ctx, panels, entity);
919 }
920
921 if(em.is_selected(entity))
922 {
923 handle_entity_mouse_interactions(ctx, panels, entity, is_item_clicked_middle, is_item_double_clicked_left);
924 handle_entity_keyboard_shortcuts(ctx, panels, entity);
925 }
926 }
927
928 // Draw name editor if in editing mode
929 draw_entity_name_editor(ctx, panels, entity, pos);
930
931 // Draw children
932 if(opened)
933 {
934 if(has_children)
935 {
936 const auto& children = trans_comp.get_children();
937 for(auto& child : children)
938 {
939 if(child)
940 {
941 draw_entity(ctx, panels, child);
942 }
943 }
944 }
945
946 ImGui::TreePop();
947 }
948
949 ImGui::PopID();
950}
951
952} // namespace
953
954// ============================================================================
955// Hierarchy Panel Implementation
956// ============================================================================
957
961
965
966void hierarchy_panel::draw_prefab_mode_header(rtti::context& ctx) const
967{
968 auto& em = ctx.get_cached<editing_manager>();
969
970 if(!em.is_prefab_mode())
971 {
972 return;
973 }
974
975 ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_ButtonActive));
976 if (ImGui::Button(ICON_MDI_KEYBOARD_RETURN " Back to Scene"))
977 {
978 em.exit_prefab_mode(ctx, editing_manager::save_option::yes);
979 }
980 ImGui::PopStyleColor();
981
982 if (em.edited_prefab)
983 {
984 ImGui::SameLine();
985 ImGui::Text("Editing Prefab: %s", fs::path(em.edited_prefab.id()).filename().string().c_str());
986 }
987
988 ImGui::Separator();
989}
990
991auto hierarchy_panel::get_scene_display_name(const editing_manager& em, scene* target_scene) const -> std::string
992{
993 std::string name;
994
995 if (em.is_prefab_mode())
996 {
997 name = fs::path(em.edited_prefab.id()).filename().string();
998 if (name.empty())
999 {
1000 name = "Prefab";
1001 }
1002 }
1003 else
1004 {
1005 name = target_scene->source.name();
1006 if (name.empty())
1007 {
1008 name = "Unnamed";
1009 }
1010 name.append(" ").append(ex::get_type<scene_prefab>());
1011
1012 if(em.has_unsaved_changes())
1013 {
1014 name.append("*");
1015 }
1016 }
1017
1018 return name;
1019}
1020
1021void hierarchy_panel::draw_scene_hierarchy(rtti::context& ctx) const
1022{
1023 auto& em = ctx.get_cached<editing_manager>();
1024 scene* target_scene = em.get_active_scene(ctx);
1025
1026 if (!target_scene)
1027 {
1028 return;
1029 }
1030
1031 std::string scene_name = get_scene_display_name(em, target_scene);
1032
1033 ImGui::SetNextItemOpen(true, ImGuiCond_Appearing);
1034 if(ImGui::CollapsingHeader(scene_name.c_str()))
1035 {
1037 {
1038 target_scene->registry->sort<root_component>(
1039 [](auto const& lhs, auto const& rhs)
1040 {
1041 // Return true if lhs should come before rhs
1042 return lhs.order < rhs.order;
1043 });
1044
1046 }
1047
1048 // lead by root_component, so that the order is determined by it.
1049 target_scene->registry->view<root_component, transform_component>().each(
1050 [&](auto e, auto&& root, auto&& comp)
1051 {
1052 draw_entity(ctx, parent_, comp.get_owner());
1053 });
1054 }
1055
1056 handle_window_empty_click(ctx);
1057}
1058
1059void hierarchy_panel::handle_window_empty_click(rtti::context& ctx) const
1060{
1061 auto& em = ctx.get_cached<editing_manager>();
1062 if(ImGui::IsWindowHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left))
1063 {
1064 if(!ImGui::IsAnyItemHovered())
1065 {
1066 em.unselect();
1067 }
1068 }
1069}
1070
1072{
1074
1075 if(ImGui::Begin(name))
1076 {
1077 draw_prefab_mode_header(ctx);
1078
1079 ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize |
1080 ImGuiWindowFlags_NoSavedSettings;
1081
1082 if(ImGui::BeginChild("hierarchy_content", ImGui::GetContentRegionAvail(), 0, flags))
1083 {
1084 check_context_menu(ctx, parent_, {});
1085 draw_scene_hierarchy(ctx);
1086 }
1087 ImGui::EndChild();
1088
1089 check_drag(ctx, parent_, {});
1090 }
1091 ImGui::End();
1092
1093 update_editing();
1094}
1095
1096} // namespace unravel
manifold_type type
static auto get_entity_icon(entt::handle entity) -> std::string
static auto get_entity_name(entt::handle entity) -> std::string
Gets the entity name from tag component.
static auto get_entity_display_color(entt::handle entity) -> ImVec4
imgui_panels * parent_
hierarchy_panel(imgui_panels *parent)
void init(rtti::context &ctx)
static auto is_parent_of(entt::handle parent_to_test, entt::handle child) -> bool
std::string name
Definition hub.cpp:27
#define ICON_MDI_EYE_OFF
#define ICON_MDI_EYE
#define ICON_MDI_KEYBOARD_RETURN
const char * icon
void NextLine()
Definition imgui.h:218
auto get_type() -> const std::string &
auto get_suported_formats() -> const std::vector< std::string > &
path convert_to_protocol(const path &_path)
Oposite of the resolve_protocol this function tries to convert to protocol path from an absolute one.
constexpr ImGuiKey delete_item
Definition shortcuts.h:33
const ImGuiKeyCombination duplicate_item
Definition shortcuts.h:34
constexpr ImGuiKey rename_item
Definition shortcuts.h:32
constexpr ImGuiKey focus_selected
Definition shortcuts.h:62
auto is_roots_order_changed() -> bool
void reset_roots_order_changed()
probe_type
Enum class representing the type of reflection probe.
@ sphere
Sphere type reflection probe.
@ box
Box type reflection probe.
light_type
Enum representing the type of light.
Definition light.h:14
entt::entity entity
float x
auto get_cached() -> T &
Definition context.hpp:49
static auto create_reflection_probe_entity(rtti::context &ctx, scene &scn, probe_type type, const std::string &name) -> entt::handle
Creates a reflection probe entity.
Definition defaults.cpp:570
static auto create_ui_document_entity(rtti::context &ctx, scene &scn, const std::string &name) -> entt::handle
Creates a UI document entity.
Definition defaults.cpp:608
static auto create_text_entity(rtti::context &ctx, scene &scn, const std::string &name) -> entt::handle
Creates a text entity.
Definition defaults.cpp:615
static auto create_audio_source_entity(rtti::context &ctx, scene &scn, const std::string &name) -> entt::handle
Creates a audio source entity.
Definition defaults.cpp:632
static auto create_embedded_mesh_entity(rtti::context &ctx, scene &scn, const std::string &name) -> entt::handle
Creates an embedded mesh entity.
Definition defaults.cpp:433
static auto create_particle_emitter_entity(rtti::context &ctx, scene &scn, const std::string &name) -> entt::handle
Creates a particle emitter entity.
Definition defaults.cpp:625
static auto create_light_entity(rtti::context &ctx, scene &scn, light_type type, const std::string &name) -> entt::handle
Creates a light entity.
Definition defaults.cpp:538
static auto create_prefab_at(rtti::context &ctx, scene &scn, const std::string &key, const camera &cam, math::vec2 pos) -> entt::handle
Creates a prefab entity at a specified position.
Definition defaults.cpp:479
static auto create_camera_entity(rtti::context &ctx, scene &scn, const std::string &name) -> entt::handle
Creates a camera entity.
Definition defaults.cpp:592
static auto create_mesh_entity_at(rtti::context &ctx, scene &scn, const std::string &key, const camera &cam, math::vec2 pos) -> entt::handle
Creates a mesh entity at a specified position.
Definition defaults.cpp:523
static auto find_prefab_root_entity(entt::handle entity) -> entt::handle
Finds the prefab root entity by traversing up the parent hierarchy.