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