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_light_entity(rtti::context& ctx, imgui_panels* panels, entt::handle parent_entity, light_type type, const std::string& name)
142{
143 auto& em = ctx.get_cached<editing_manager>();
144 em.queue_action("Create Light Entity",
145 [&ctx, panels, parent_entity, type, name]() mutable
146 {
147 auto& em = ctx.get_cached<editing_manager>();
148 auto* active_scene = em.get_active_scene(ctx);
149 auto object = defaults::create_light_entity(ctx, *active_scene, type, name);
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_reflection_probe_entity(rtti::context& ctx, imgui_panels* panels, entt::handle parent_entity, probe_type type, const std::string& name)
160{
161 auto& em = ctx.get_cached<editing_manager>();
162 em.queue_action("Create Reflection Probe 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_reflection_probe_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_camera_entity(rtti::context& ctx, imgui_panels* panels, entt::handle parent_entity)
178{
179 auto& em = ctx.get_cached<editing_manager>();
180 em.queue_action("Create Camera Entity",
181 [&ctx, panels, parent_entity]() mutable
182 {
183 auto& em = ctx.get_cached<editing_manager>();
184 auto* active_scene = em.get_active_scene(ctx);
185 auto object = defaults::create_camera_entity(ctx, *active_scene, "Camera");
186 em.select(object);
187 start_editing_label(ctx, panels, object);
188 });
189}
190
191void create_ui_document_entity(rtti::context& ctx, imgui_panels* panels, entt::handle parent_entity)
192{
193 auto& em = ctx.get_cached<editing_manager>();
194 em.queue_action("Create UI Document Entity",
195 [&ctx, panels, parent_entity]() mutable
196 {
197 auto& em = ctx.get_cached<editing_manager>();
198 auto* active_scene = em.get_active_scene(ctx);
199 auto object = defaults::create_ui_document_entity(ctx, *active_scene, "UI Document");
200 em.select(object);
201 start_editing_label(ctx, panels, object);
202 });
203}
204
205// ============================================================================
206// Drag and Drop Operations
207// ============================================================================
208
209auto process_drag_drop_source(entt::handle entity) -> bool
210{
211 if(entity && ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceAllowNullID))
212 {
213 ImGui::TextUnformatted(entity_panel::get_entity_name(entity).c_str());
214 ImGui::SetDragDropPayload("entity", &entity, sizeof(entity));
215 ImGui::EndDragDropSource();
216 return true;
217 }
218
219 return false;
220}
221
222void handle_entity_drop(rtti::context& ctx, imgui_panels* panels, entt::handle target_entity, entt::handle dropped_entity)
223{
224 auto& em = ctx.get_cached<editing_manager>();
225
226 auto do_action = [&](entt::handle dropped)
227 {
228 auto& em = ctx.get_cached<editing_manager>();
229 em.queue_action("Drop Entity",
230 [&ctx, target_entity, dropped]() mutable
231 {
232 auto trans_comp = dropped.try_get<transform_component>();
233 if(trans_comp)
234 {
235 trans_comp->set_parent(target_entity);
236 }
237 });
238 };
239
240 if(em.is_selected(dropped_entity))
241 {
242 for(auto e : em.try_get_selections_as<entt::handle>())
243 {
244 if(e)
245 {
246 do_action(*e);
247 }
248 }
249 }
250 else
251 {
252 do_action(dropped_entity);
253 }
254}
255
256void handle_mesh_drop(rtti::context& ctx, const std::string& absolute_path)
257{
258
259 auto& em = ctx.get_cached<editing_manager>();
260 em.queue_action("Drop Mesh",
261 [&ctx, absolute_path]() mutable
262 {
263 std::string key = fs::convert_to_protocol(fs::path(absolute_path)).generic_string();
264 auto& em = ctx.get_cached<editing_manager>();
265 auto* active_scene = em.get_active_scene(ctx);
266
267 if (active_scene)
268 {
269 auto object = defaults::create_mesh_entity_at(ctx, *active_scene, key);
270 em.select(object);
271 }
272 });
273
274}
275
276void handle_prefab_drop(rtti::context& ctx, const std::string& absolute_path)
277{
278 auto& em = ctx.get_cached<editing_manager>();
279 em.queue_action("Drop Prefab",
280 [&ctx, absolute_path]() mutable
281 {
282 std::string key = fs::convert_to_protocol(fs::path(absolute_path)).generic_string();
283
284 auto& em = ctx.get_cached<editing_manager>();
285 auto* active_scene = em.get_active_scene(ctx);
286
287 if (active_scene)
288 {
289 auto object = defaults::create_prefab_at(ctx, *active_scene, key);
290 em.select(object);
291 }
292 });
293}
294
295void process_drag_drop_target(rtti::context& ctx, imgui_panels* panels, entt::handle entity)
296{
297 if(!ImGui::BeginDragDropTarget())
298 {
299 return;
300 }
301
302 if(ImGui::IsDragDropPayloadBeingAccepted())
303 {
304 ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
305 }
306 else
307 {
308 ImGui::SetMouseCursor(ImGuiMouseCursor_NotAllowed);
309 }
310
311 // Handle entity drag and drop
312 auto payload = ImGui::AcceptDragDropPayload("entity");
313 if(payload != nullptr)
314 {
315 entt::handle dropped{};
316 std::memcpy(&dropped, payload->Data, size_t(payload->DataSize));
317 if(dropped)
318 {
319 handle_entity_drop(ctx, panels, entity, dropped);
320 }
321 }
322
323 // Handle mesh drag and drop
324 for(const auto& type : ex::get_suported_formats<mesh>())
325 {
326 auto mesh_payload = ImGui::AcceptDragDropPayload(type.c_str());
327 if(mesh_payload != nullptr)
328 {
329 std::string absolute_path(reinterpret_cast<const char*>(mesh_payload->Data), std::size_t(mesh_payload->DataSize));
330 handle_mesh_drop(ctx, absolute_path);
331 }
332 }
333
334 // Handle prefab drag and drop
335 for(const auto& type : ex::get_suported_formats<prefab>())
336 {
337 auto prefab_payload = ImGui::AcceptDragDropPayload(type.c_str());
338 if(prefab_payload != nullptr)
339 {
340 std::string absolute_path(reinterpret_cast<const char*>(prefab_payload->Data), std::size_t(prefab_payload->DataSize));
341 handle_prefab_drop(ctx, absolute_path);
342 }
343 }
344
345 ImGui::EndDragDropTarget();
346}
347
348void check_drag(rtti::context& ctx, imgui_panels* panels, entt::handle entity)
349{
350 if(!process_drag_drop_source(entity))
351 {
352 process_drag_drop_target(ctx, panels, entity);
353 }
354}
355
356// ============================================================================
357// Context Menu Functions
358// ============================================================================
359
360void draw_3d_objects_menu(rtti::context& ctx, imgui_panels* panels, entt::handle parent_entity)
361{
362 if(!ImGui::BeginMenu("3D Objects"))
363 {
364 return;
365 }
366
367 static const std::vector<std::pair<std::string, std::vector<std::string>>> menu_objects = {
368 {"Cube", {"Cube"}},
369 {"Cube Rounded", {"Cube Rounded"}},
370 {"Sphere", {"Sphere"}},
371 {"Plane", {"Plane"}},
372 {"Cylinder", {"Cylinder"}},
373 {"Capsule", {"Capsule"}},
374 {"Cone", {"Cone"}},
375 {"Torus", {"Torus"}},
376 {"Teapot", {"Teapot"}},
377 {"Separator", {}},
378 {"Polygon", {"Icosahedron", "Dodecahedron"}},
379 {"Icosphere", {"Icosphere0", "Icosphere1", "Icosphere2", "Icosphere3", "Icosphere4",
380 "Icosphere5", "Icosphere6", "Icosphere7", "Icosphere8", "Icosphere9",
381 "Icosphere10", "Icosphere11", "Icosphere12", "Icosphere13", "Icosphere14",
382 "Icosphere15", "Icosphere16", "Icosphere17", "Icosphere18", "Icosphere19"}}};
383
384 for(const auto& p : menu_objects)
385 {
386 const auto& name = p.first;
387 const auto& objects_name = p.second;
388
389 if(name == "Separator")
390 {
391 ImGui::Separator();
392 }
393 else if(name == "New Line")
394 {
396 }
397 else if(objects_name.size() == 1)
398 {
399 if(ImGui::MenuItem(name.c_str()))
400 {
401 create_mesh_entity(ctx, panels, parent_entity, name);
402 }
403 }
404 else
405 {
406 if(ImGui::BeginMenu(name.c_str()))
407 {
408 for(const auto& n : objects_name)
409 {
410 if(ImGui::MenuItem(n.c_str()))
411 {
412 create_mesh_entity(ctx, panels, parent_entity, n);
413 }
414 }
415 ImGui::EndMenu();
416 }
417 }
418 }
419
421 ImGui::Separator();
422
423 if(ImGui::MenuItem("Text"))
424 {
425 create_text_entity(ctx, panels, parent_entity);
426 }
427
428 ImGui::EndMenu();
429}
430
431void draw_lighting_menu(rtti::context& ctx, imgui_panels* panels, entt::handle parent_entity)
432{
433 if(!ImGui::BeginMenu("Lighting"))
434 {
435 return;
436 }
437
438 // Light submenu
439 if(ImGui::BeginMenu("Light"))
440 {
441 static const std::vector<std::pair<std::string, light_type>> light_objects = {
442 {"Directional", light_type::directional},
443 {"Spot", light_type::spot},
444 {"Point", light_type::point}};
445
446 for(const auto& p : light_objects)
447 {
448 const auto& name = p.first;
449 const auto& type = p.second;
450 if(ImGui::MenuItem(name.c_str()))
451 {
452 create_light_entity(ctx, panels, parent_entity, type, name);
453 }
454 }
455 ImGui::EndMenu();
456 }
457
458 // Reflection probes submenu
459 if(ImGui::BeginMenu("Reflection Probes"))
460 {
461 static const std::vector<std::pair<std::string, probe_type>> reflection_probes = {
462 {"Sphere", probe_type::sphere},
463 {"Box", probe_type::box}};
464
465 for(const auto& p : reflection_probes)
466 {
467 const auto& name = p.first;
468 const auto& type = p.second;
469
470 if(ImGui::MenuItem(name.c_str()))
471 {
472 create_reflection_probe_entity(ctx, panels, parent_entity, type, name);
473 }
474 }
475 ImGui::EndMenu();
476 }
477
478 ImGui::EndMenu();
479}
480
481void draw_common_menu_items(rtti::context& ctx, imgui_panels* panels, entt::handle parent_entity)
482{
483 if(ImGui::MenuItem("Create Empty"))
484 {
485 create_empty_entity(ctx, panels, parent_entity);
486 }
487
488 draw_3d_objects_menu(ctx, panels, parent_entity);
489 draw_lighting_menu(ctx, panels, parent_entity);
490
491 if(ImGui::MenuItem("Camera"))
492 {
493 create_camera_entity(ctx, panels, parent_entity);
494 }
495
496 if(ImGui::MenuItem("UI Document"))
497 {
498 create_ui_document_entity(ctx, panels, parent_entity);
499 }
500}
501
502void draw_entity_context_menu(rtti::context& ctx, imgui_panels* panels, entt::handle entity)
503{
504 if(!ImGui::BeginPopupContextItem("Entity Context Menu"))
505 {
506 return;
507 }
508
509 if(ImGui::MenuItem("Create Empty Parent"))
510 {
511 create_empty_parent_entity(ctx, panels, entity);
512 }
513
514 draw_common_menu_items(ctx, panels, entity);
515
516 ImGui::Separator();
517
518 if(ImGui::MenuItem("Rename", ImGui::GetKeyName(shortcuts::rename_item)))
519 {
520 auto& em = ctx.get_cached<editing_manager>();
521 em.queue_action("Rename Entity",
522 [ctx, panels, entity]() mutable
523 {
524 start_editing_label(ctx, panels, entity);
525 });
526 }
527
528 if(ImGui::MenuItem("Duplicate", ImGui::GetKeyCombinationName(shortcuts::duplicate_item).c_str()))
529 {
530 panels->get_scene_panel().duplicate_entities({entity});
531 }
532
533 if(ImGui::MenuItem("Delete", ImGui::GetKeyName(shortcuts::delete_item)))
534 {
535 panels->get_scene_panel().delete_entities({entity});
536 }
537
538 if(ImGui::MenuItem("Focus", ImGui::GetKeyName(shortcuts::focus_selected)))
539 {
540 panels->get_scene_panel().focus_entities(panels->get_scene_panel().get_camera(), {entity});
541 }
542
543 ImGui::Separator();
544
545 if(entity.any_of<prefab_component, prefab_id_component>())
546 {
547 if(ImGui::MenuItem("Open Prefab"))
548 {
549 auto& em = ctx.get_cached<editing_manager>();
550 em.queue_action("Open Prefab",
551 [&ctx, entity, panels]() mutable
552 {
554 if(prefab_root)
555 {
556 auto prefab = prefab_root.get<prefab_component>().source;
557 if(prefab)
558 {
559 auto& em = ctx.get_cached<editing_manager>();
560 em.enter_prefab_mode(ctx, prefab, true);
561 }
562 }
563 });
564 }
565
566 if(ImGui::MenuItem("Unlink from Prefab"))
567 {
568 auto& em = ctx.get_cached<editing_manager>();
569 em.queue_action("Unlink from Prefab",
570 [entity]() mutable
571 {
572 entity.remove<prefab_component, prefab_id_component>();
573 });
574 }
575 }
576
577 ImGui::EndPopup();
578}
579
580void draw_window_context_menu(rtti::context& ctx, imgui_panels* panels)
581{
582 if(!ImGui::BeginPopupContextWindowEx())
583 {
584 return;
585 }
586
587 draw_common_menu_items(ctx, panels, {});
588 ImGui::EndPopup();
589}
590
591void check_context_menu(rtti::context& ctx, imgui_panels* panels, entt::handle entity)
592{
593 ImGui::PushStyleColor(ImGuiCol_Separator, ImGui::GetStyleColorVec4(ImGuiCol_Text));
594
595 if(entity)
596 {
597 draw_entity_context_menu(ctx, panels, entity);
598 }
599 else
600 {
601 draw_window_context_menu(ctx, panels);
602 }
603
604 ImGui::PopStyleColor();
605}
606
607// ============================================================================
608// Entity Drawing and Interaction
609// ============================================================================
610
611void draw_activity(rtti::context& ctx, transform_component& trans_comp)
612{
613 bool is_active_local = trans_comp.is_active();
614 if(!is_active_local)
615 {
616 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.5f, 0.5f, 0.5f, 1.0f));
617 }
618
619 if(ImGui::Button(is_active_local ? ICON_MDI_EYE : ICON_MDI_EYE_OFF))
620 {
621 trans_comp.set_active(!is_active_local);
622
623 auto entity = trans_comp.get_owner();
624 auto& em = ctx.get_cached<editing_manager>();
625
626 em.push_undo_stack_enabled(true);
627
628 em.queue_action<entity_set_active_action_t>({},
629 entity,
630 is_active_local,
631 !is_active_local);
632
633 em.pop_undo_stack_enabled();
634
635 }
636
637 if(!is_active_local)
638 {
639 ImGui::PopStyleColor();
640 }
641}
642
643auto is_parent_of_focused(rtti::context& ctx, entt::handle entity) -> bool
644{
645 auto& em = ctx.get_cached<editing_manager>();
646 auto focus = em.try_get_active_focus_as<entt::handle>();
647 if(focus)
648 {
650 {
651 return true;
652 }
653 }
654
655 return false;
656}
657
658auto get_entity_tree_node_flags(rtti::context& ctx, entt::handle entity, bool has_children) -> ImGuiTreeNodeFlags
659{
660 auto& em = ctx.get_cached<editing_manager>();
661 ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_AllowOverlap | ImGuiTreeNodeFlags_OpenOnArrow;
662
663 if(em.is_selected(entity))
664 {
665 flags |= ImGuiTreeNodeFlags_Selected;
666 }
667
668 if(!has_children)
669 {
670 flags |= ImGuiTreeNodeFlags_Leaf;
671 }
672
673 flags |= ImGuiTreeNodeFlags_DrawLinesToNodes;
674
675 return flags;
676}
677
678auto get_entity_display_label(entt::handle entity) -> std::string
679{
682
683 const auto ent = entity.entity();
684 const auto id = entt::to_integral(ent);
685
686 return icon + name +"###" + std::to_string(id);
687}
688
689void handle_entity_selection(rtti::context& ctx, imgui_panels* panels, entt::handle entity)
690{
691 auto& em = ctx.get_cached<editing_manager>();
692 auto mode = em.get_select_mode();
693 em.queue_action("Select Entity",
694 [&ctx, panels, entity, mode]() mutable
695 {
696 stop_editing_label(ctx, panels, entity);
697 auto& em = ctx.get_cached<editing_manager>();
698 em.select(entity, mode);
699 });
700}
701
702void handle_entity_keyboard_shortcuts(rtti::context& ctx, imgui_panels* panels, entt::handle entity)
703{
704 if(ImGui::IsItemKeyPressed(shortcuts::rename_item))
705 {
706 auto& em = ctx.get_cached<editing_manager>();
707 em.queue_action("Rename Entity",
708 [&ctx, panels, entity]() mutable
709 {
710 start_editing_label(ctx, panels, entity);
711 });
712 }
713
714 if(ImGui::IsItemKeyPressed(shortcuts::delete_item))
715 {
716 panels->get_scene_panel().delete_entities({entity});
717 }
718
719 if(ImGui::IsItemKeyPressed(shortcuts::focus_selected))
720 {
721 panels->get_scene_panel().focus_entities(panels->get_scene_panel().get_camera(), {entity});
722 }
723
724 if(ImGui::IsItemCombinationKeyPressed(shortcuts::duplicate_item))
725 {
726 panels->get_scene_panel().duplicate_entities({entity});
727 }
728}
729
730void handle_entity_mouse_interactions(rtti::context& ctx, imgui_panels* panels, entt::handle entity, bool is_item_clicked_middle, bool is_item_double_clicked_left)
731{
732 if(is_item_clicked_middle)
733 {
734 panels->get_scene_panel().focus_entities(panels->get_scene_panel().get_camera(), {entity});
735 }
736
737 if(is_item_double_clicked_left)
738 {
739 panels->get_scene_panel().focus_entities(panels->get_scene_panel().get_camera(), {entity});
740 }
741}
742
743void draw_entity_name_editor(rtti::context& ctx, imgui_panels* panels, entt::handle entity, const ImVec2& pos)
744{
745 auto& em = ctx.get_cached<editing_manager>();
746 if(!em.is_selected(entity) || !is_editing_label())
747 {
748 return;
749 }
750
751 if(is_just_started_editing_label())
752 {
753 ImGui::SetKeyboardFocusHere();
754 }
755
756 ImGui::SetCursorScreenPos(pos);
757 ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x);
758
759 auto edit_name = entity_panel::get_entity_name(entity);
760 auto old_name = edit_name;
761 ImGui::InputTextWidget("##rename", edit_name, false, ImGuiInputTextFlags_AutoSelectAll);
762
763 if(ImGui::IsItemDeactivatedAfterEdit())
764 {
765
766 auto& em = ctx.get_cached<editing_manager>();
767 em.push_undo_stack_enabled(true);
768 em.queue_action<entity_set_name_action_t>({},
769 entity,
770 old_name,
771 edit_name);
772 em.pop_undo_stack_enabled();
773 stop_editing_label(ctx, panels, entity);
774 }
775
776 ImGui::PopItemWidth();
777
778 if(ImGui::IsItemDeactivated())
779 {
780 stop_editing_label(ctx, panels, entity);
781 }
782}
783
784void draw_entity(rtti::context& ctx, imgui_panels* panels, entt::handle entity)
785{
786 if(!entity)
787 {
788 return;
789 }
790
791 auto& em = ctx.get_cached<editing_manager>();
792 ImGui::PushID(static_cast<int>(entity.entity()));
793
794 auto& trans_comp = entity.get<transform_component>();
795 bool has_children = !trans_comp.get_children().empty();
796
797 ImGuiTreeNodeFlags flags = get_entity_tree_node_flags(ctx, entity, has_children);
798
799 if(is_parent_of_focused(ctx, entity))
800 {
801 ImGui::SetNextItemOpen(true, 0);
802 ImGui::SetScrollHereY();
803 }
804
805 auto pos = ImGui::GetCursorScreenPos() + ImVec2(ImGui::GetTextLineHeightWithSpacing(), 0.0f);
806 ImGui::AlignTextToFramePadding();
807
808 auto label = get_entity_display_label(entity);
810
811 ImGui::PushStyleColor(ImGuiCol_Text, col);
812 ImGui::PushStyleVarX(ImGuiStyleVar_ItemInnerSpacing, 0.0f);
813 bool opened = ImGui::TreeNodeEx(label.c_str(), flags);
814 ImGui::PopStyleVar();
815
816 if(ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip))
817 {
818 const auto ent = entity.entity();
819 const auto idx = entt::to_entity(ent);
820 const auto ver = entt::to_version(ent);
821 const auto id = entt::to_integral(ent);
822
823 ImGui::SetItemTooltipEx("Id: %d\nIndex: %d\nVersion: %d", id, idx, ver);
824 }
825
826 ImGui::PopStyleColor();
827
828 if(em.is_focused(entity))
829 {
830 ImGui::SetItemFocusFrame(ImGui::GetColorU32(ImVec4(1.0f, 1.0f, 0.0f, 1.0f)));
831 }
832
833 if(!is_editing_label())
834 {
835 check_drag(ctx, panels, entity);
836 check_context_menu(ctx, panels, entity);
837 }
838
839 // Collect interaction states
840 bool is_item_focus_changed = ImGui::IsItemFocusChanged();
841 bool is_item_released_left = ImGui::IsItemReleased(ImGuiMouseButton_Left);
842 bool is_item_clicked_middle = ImGui::IsItemClicked(ImGuiMouseButton_Middle);
843 bool is_item_double_clicked_left = ImGui::IsItemDoubleClicked(ImGuiMouseButton_Left);
844 bool activity_hovered = false;
845
846 // Draw activity button
847 ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
848 ImGui::AlignedItem(1.0f,
849 ImGui::GetContentRegionAvail().x - ImGui::GetStyle().FramePadding.x,
850 ImGui::GetFrameHeight(),
851 [&]()
852 {
853 draw_activity(ctx, trans_comp);
854 activity_hovered = ImGui::IsItemHovered();
855 });
856
857 // Handle interactions (only if not hovering activity button)
858 if(!activity_hovered)
859 {
860 if(is_item_released_left || is_item_focus_changed)
861 {
862 handle_entity_selection(ctx, panels, entity);
863 }
864
865 if(em.is_selected(entity))
866 {
867 handle_entity_mouse_interactions(ctx, panels, entity, is_item_clicked_middle, is_item_double_clicked_left);
868 handle_entity_keyboard_shortcuts(ctx, panels, entity);
869 }
870 }
871
872 // Draw name editor if in editing mode
873 draw_entity_name_editor(ctx, panels, entity, pos);
874
875 // Draw children
876 if(opened)
877 {
878 if(has_children)
879 {
880 const auto& children = trans_comp.get_children();
881 for(auto& child : children)
882 {
883 if(child)
884 {
885 draw_entity(ctx, panels, child);
886 }
887 }
888 }
889
890 ImGui::TreePop();
891 }
892
893 ImGui::PopID();
894}
895
896} // namespace
897
898// ============================================================================
899// Hierarchy Panel Implementation
900// ============================================================================
901
905
909
910void hierarchy_panel::draw_prefab_mode_header(rtti::context& ctx) const
911{
912 auto& em = ctx.get_cached<editing_manager>();
913
914 if(!em.is_prefab_mode())
915 {
916 return;
917 }
918
919 ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_ButtonActive));
920 if (ImGui::Button(ICON_MDI_KEYBOARD_RETURN " Back to Scene"))
921 {
922 em.exit_prefab_mode(ctx, editing_manager::save_option::yes);
923 }
924 ImGui::PopStyleColor();
925
926 if (em.edited_prefab)
927 {
928 ImGui::SameLine();
929 ImGui::Text("Editing Prefab: %s", fs::path(em.edited_prefab.id()).filename().string().c_str());
930 }
931
932 ImGui::Separator();
933}
934
935auto hierarchy_panel::get_scene_display_name(const editing_manager& em, scene* target_scene) const -> std::string
936{
937 std::string name;
938
939 if (em.is_prefab_mode())
940 {
941 name = fs::path(em.edited_prefab.id()).filename().string();
942 if (name.empty())
943 {
944 name = "Prefab";
945 }
946 }
947 else
948 {
949 name = target_scene->source.name();
950 if (name.empty())
951 {
952 name = "Unnamed";
953 }
954 name.append(" ").append(ex::get_type<scene_prefab>());
955
956 if(em.has_unsaved_changes())
957 {
958 name.append("*");
959 }
960 }
961
962 return name;
963}
964
965void hierarchy_panel::draw_scene_hierarchy(rtti::context& ctx) const
966{
967 auto& em = ctx.get_cached<editing_manager>();
968 scene* target_scene = em.get_active_scene(ctx);
969
970 if (!target_scene)
971 {
972 return;
973 }
974
975 std::string scene_name = get_scene_display_name(em, target_scene);
976
977 ImGui::SetNextItemOpen(true, ImGuiCond_Appearing);
978 if(ImGui::CollapsingHeader(scene_name.c_str()))
979 {
981 {
982 target_scene->registry->sort<root_component>(
983 [](auto const& lhs, auto const& rhs)
984 {
985 // Return true if lhs should come before rhs
986 return lhs.order < rhs.order;
987 });
988
990 }
991
992 // lead by root_component, so that the order is determined by it.
993 target_scene->registry->view<root_component, transform_component>().each(
994 [&](auto e, auto&& root, auto&& comp)
995 {
996 draw_entity(ctx, parent_, comp.get_owner());
997 });
998 }
999
1000 handle_window_empty_click(ctx);
1001}
1002
1003void hierarchy_panel::handle_window_empty_click(rtti::context& ctx) const
1004{
1005 auto& em = ctx.get_cached<editing_manager>();
1006 if(ImGui::IsWindowHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left))
1007 {
1008 if(!ImGui::IsAnyItemHovered())
1009 {
1010 em.unselect();
1011 }
1012 }
1013}
1014
1016{
1018
1019 if(ImGui::Begin(name))
1020 {
1021 draw_prefab_mode_header(ctx);
1022
1023 ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize |
1024 ImGuiWindowFlags_NoSavedSettings;
1025
1026 if(ImGui::BeginChild("hierarchy_content", ImGui::GetContentRegionAvail(), 0, flags))
1027 {
1028 check_context_menu(ctx, parent_, {});
1029 draw_scene_hierarchy(ctx);
1030 }
1031 ImGui::EndChild();
1032
1033 check_drag(ctx, parent_, {});
1034 }
1035 ImGui::End();
1036
1037 update_editing();
1038}
1039
1040} // 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:211
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:559
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:597
static auto create_text_entity(rtti::context &ctx, scene &scn, const std::string &name) -> entt::handle
Creates a text entity.
Definition defaults.cpp:604
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:422
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:527
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:468
static auto create_camera_entity(rtti::context &ctx, scene &scn, const std::string &name) -> entt::handle
Creates a camera entity.
Definition defaults.cpp:581
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:512
static auto find_prefab_root_entity(entt::handle entity) -> entt::handle
Finds the prefab root entity by traversing up the parent hierarchy.