diff --git a/GlitchyEditor/BeefProj.toml b/GlitchyEditor/BeefProj.toml index bb200ed..2cd40b7 100644 --- a/GlitchyEditor/BeefProj.toml +++ b/GlitchyEditor/BeefProj.toml @@ -5,6 +5,7 @@ Dependencies = {corlib = "*", GlitchLog = "*", GlitchyEngine = "*"} Name = "GlitchyEditor" TargetType = "BeefGUIApplication" StartupObject = "GlitchyEngine.Program" +ProcessorMacros = ["GE_EDITOR_IMGUI_DEMO"] [Configs.Debug.Win64] PostBuildCmds = ["CopyFilesIfNewer(\"$(WorkspaceDir)/bin/vswhere.exe\", \"$(TargetDir)\")"] diff --git a/GlitchyEditor/resources/Textures/EditorIcons.dds b/GlitchyEditor/resources/Textures/EditorIcons.dds index 8d55e89..5781d7a 100644 Binary files a/GlitchyEditor/resources/Textures/EditorIcons.dds and b/GlitchyEditor/resources/Textures/EditorIcons.dds differ diff --git a/GlitchyEditor/resources/Textures/EditorIcons.psd b/GlitchyEditor/resources/Textures/EditorIcons.psd index 4711f9c..ce42cce 100644 Binary files a/GlitchyEditor/resources/Textures/EditorIcons.psd and b/GlitchyEditor/resources/Textures/EditorIcons.psd differ diff --git a/GlitchyEditor/src/EditWindows/EntityHierarchyWindow.bf b/GlitchyEditor/src/EditWindows/EntityHierarchyWindow.bf index 52e5a7b..c144528 100644 --- a/GlitchyEditor/src/EditWindows/EntityHierarchyWindow.bf +++ b/GlitchyEditor/src/EditWindows/EntityHierarchyWindow.bf @@ -6,6 +6,9 @@ using System.Collections; using GlitchyEngine; using GlitchyEngine.Core; using System.Diagnostics; +using GlitchyEngine.World.Components; +using GlitchyEngine.Renderer; +using GlitchyEngine.Math; namespace GlitchyEditor.EditWindows { @@ -362,11 +365,76 @@ namespace GlitchyEditor.EditWindows return deleted; } + private void ShowVisibilityButton(Entity entity) + { + ImGui.PushStyleVar(.WindowPadding, ImGui.Vec2(0, 2)); + ImGui.PushStyleVar(.ItemInnerSpacing, ImGui.Vec2(0, 0)); + ImGui.PushStyleColor(.Button, ImGui.Vec4(0, 0, 0, 0)); + + let colors = ImGui.GetStyle().Colors; + + ImGui.Vec4 hoveredColor = colors[(int)ImGui.Col.ButtonHovered]; + hoveredColor.w = 0.5f; + + ImGui.Vec4 activeColor = colors[(int)ImGui.Col.ButtonActive]; + activeColor.w = 0.5f; + + ImGui.PushStyleColor(.ButtonHovered, hoveredColor); + ImGui.PushStyleColor(.ButtonActive, activeColor); + + if (entity.TryGetComponent(let flags)) + { + bool hidden = flags.Flags.HasFlag(.HideInScene); + + SubTexture2D icon = hidden ? EditorIcons.Instance.Entity_Hidden : EditorIcons.Instance.Entity_Visible; + + ImGui.Vec4 tintColor = hidden ? *ImGui.GetStyleColorVec4(.TextDisabled) : *ImGui.GetStyleColorVec4(.Text); + + if (ImGui.ImageButtonEx(ImGui.GetID("visibilityToggle"), icon, .(16, 16), .Zero, .Ones, .Zero, tintColor)) + { + flags.Flags ^= .HideInScene; + } + + if (ImGui.IsItemHovered()) + { + ImGui.BeginTooltip(); + + if (hidden) + { + ImGui.TextUnformatted("Entity is hidden. Click to show the entity."); + } + else + { + ImGui.TextUnformatted("Entity is visible. Click to hide the entity."); + } + + ImGui.EndTooltip(); + } + } + + ImGui.PopStyleColor(3); + ImGui.PopStyleVar(2); + } + private void ImGuiPrintEntityTree(TreeNode tree) { + Entity entity = tree.Value; + + if (entity.EditorFlags.HasFlag(.HideInHierarchy)) + return; + + ImGui.PushID(entity.UUID.GetHashCode()); + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + + ShowVisibilityButton(entity); + + ImGui.TableNextColumn(); + String name = null; - var nameComponent = tree.Value.GetComponent(); + var nameComponent = entity.GetComponent(); if(nameComponent != null) { @@ -374,44 +442,42 @@ namespace GlitchyEditor.EditWindows } else { - name = scope:: $"Entity {(tree.Value.Handle.[Friend]Index)}"; + name = scope:: $"Entity {(entity.Handle.[Friend]Index)}"; } - ImGui.TreeNodeFlags flags = .OpenOnArrow | .DefaultOpen | .SpanAvailWidth | .OpenOnDoubleClick; + ImGui.TreeNodeFlags flags = .OpenOnArrow | .DefaultOpen | .SpanAllColumns | .OpenOnDoubleClick; if(tree.Children.Count == 0) flags |= .Leaf; - bool inSelectedList = IsEntitySelected(tree.Value); + bool inSelectedList = IsEntitySelected(entity); if(inSelectedList) flags |= .Selected; - if (_entitiesToUnfold.Contains(tree.Value)) + if (_entitiesToUnfold.Contains(entity)) { - _entitiesToUnfold.Remove(tree.Value); + _entitiesToUnfold.Remove(entity); ImGui.SetNextItemOpen(true, .None); } - bool isOpen = ImGui.TreeNodeEx((void*)(uint)tree.Value.Handle.[Friend]Index, flags, $"{name}"); + bool isOpen = ImGui.TreeNodeEx((void*)(uint)entity.Handle.[Friend]Index, flags, $"{name}"); - if (_entityToHighlight == tree.Value) + if (_entityToHighlight == entity) { ImGui.SetScrollHereY(0); _entityToHighlight = .(); } - ImGui.PushID((void*)(uint)tree.Value.Handle.[Friend]Index); - bool deleted = false; if (ImGui.BeginPopupContextItem("treeNodePopup")) { // Only select if it isn't already selected, because it otherwise clears the selection when ctrl is released - if (!IsEntitySelected(tree.Value)) + if (!IsEntitySelected(entity)) { - SelectEntity(tree.Value, !ImGui.GetIO().KeyCtrl); + SelectEntity(entity, !ImGui.GetIO().KeyCtrl); } ShowEntityContextMenu(out deleted); @@ -419,8 +485,6 @@ namespace GlitchyEditor.EditWindows ImGui.EndPopup(); } - ImGui.PopID(); - if (deleted) { if (isOpen) @@ -435,7 +499,7 @@ namespace GlitchyEditor.EditWindows { isDragged = true; - UUID id = tree.Value.UUID; + UUID id = entity.UUID; ImGui.SetDragDropPayload(.Entity, &id, sizeof(UUID)); ImGui.Text(name); @@ -443,7 +507,7 @@ namespace GlitchyEditor.EditWindows ImGui.EndDragDropSource(); } - EntityDropTarget(tree.Value); + EntityDropTarget(entity); bool clicked = ImGui.IsItemClicked() && !ImGui.IsItemToggledOpen(); bool clickedRight = ImGui.IsItemClicked(.Right); @@ -461,17 +525,19 @@ namespace GlitchyEditor.EditWindows if (clicked || clickedRight) { - lastClickedEntity = tree.Value; + lastClickedEntity = entity; } - if (!isDragged && (hovered && !ImGui.IsMouseDown(.Left) && !ImGui.IsMouseDown(.Right)) && tree.Value == lastClickedEntity) + if (!isDragged && (hovered && !ImGui.IsMouseDown(.Left) && !ImGui.IsMouseDown(.Right)) && entity == lastClickedEntity) { if (!inSelectedList) { - SelectEntity(tree.Value, !ImGui.GetIO().KeyCtrl); + SelectEntity(entity, !ImGui.GetIO().KeyCtrl); inSelectedList = true; } } + + ImGui.PopID(); } private void EntityDropTarget(Entity target) @@ -565,37 +631,41 @@ namespace GlitchyEditor.EditWindows InsertIntoTree(entity); } - - if(ImGui.TreeNodeEx("Scene", .DefaultOpen)) + + ImGui.Rect rect = .(); + rect.Min = (ImGui.Vec2)((float2)ImGui.GetWindowContentRegionMin() + (float2)ImGui.GetWindowPos()); + rect.Max = (ImGui.Vec2)((float2)rect.Min + (float2)ImGui.GetContentRegionAvail()); + + if(ImGui.BeginDragDropTargetCustom(rect, ImGui.GetID("sceneDropTarget"))) { - if(ImGui.BeginDragDropTarget()) + Payload? payload = ImGui.AcceptDragDropPayload(.Entity); + + if(payload != null) { - Payload? payload = ImGui.AcceptDragDropPayload(.Entity); + UUID movedEntityId = payload->Data; + + Entity movedEntity = _editor.CurrentScene.GetEntityByID(movedEntityId); - if(payload != null) - { - UUID movedEntityId = payload->Data; - - Entity movedEntity = _editor.CurrentScene.GetEntityByID(movedEntityId); - - // Also mark transform as dirty - var transformComponent = movedEntity.GetComponent(); - transformComponent.Parent = .InvalidEntity; - //transformComponent?.IsDirty = true; - } - - ImGui.EndDragDropTarget(); + var transformComponent = movedEntity.GetComponent(); + transformComponent.Parent = .InvalidEntity; } + ImGui.EndDragDropTarget(); + } + + if (ImGui.BeginTable("entityTable", 2, .RowBg | .NoBordersInBody | .SizingFixedFit)) + { + ImGui.TableSetupColumn("Visibility", .IndentDisable | .NoResize); + ImGui.TableSetupColumn("Entities", .IndentEnable | .WidthStretch); + for(var child in root.Children) { ImGuiPrintEntityTree(child); } - ImGui.TreePop(); + ImGui.EndTable(); } } - // Otherwise else { // Show search results as flat list diff --git a/GlitchyEditor/src/EditorIcons.bf b/GlitchyEditor/src/EditorIcons.bf index 4bc44b3..4a4f6a1 100644 --- a/GlitchyEditor/src/EditorIcons.bf +++ b/GlitchyEditor/src/EditorIcons.bf @@ -8,6 +8,8 @@ namespace GlitchyEditor { class EditorIcons : RefCounted { + private static EditorIcons _editorIcons; + AssetHandle _texture; public SubTexture2D DirectionalLight ~ _.ReleaseRef(); @@ -27,6 +29,10 @@ namespace GlitchyEditor public SubTexture2D File_Material ~ _.ReleaseRef(); public SubTexture2D File_CSharpScript ~ _.ReleaseRef(); public SubTexture2D File_Shader ~ _.ReleaseRef(); + public SubTexture2D Entity_Visible ~ _.ReleaseRef(); + public SubTexture2D Entity_Hidden ~ _.ReleaseRef(); + + public static EditorIcons Instance => _editorIcons; public SamplerState SamplerState { @@ -36,6 +42,8 @@ namespace GlitchyEditor public this(String texturePath, float2 iconSize) { + _editorIcons = this; + _texture = Content.LoadAsset(texturePath, null, true); float2 pen = .(); @@ -57,6 +65,13 @@ namespace GlitchyEditor File_Material = GetNextGridTexture(ref pen, iconSize); File_CSharpScript = GetNextGridTexture(ref pen, iconSize); File_Shader = GetNextGridTexture(ref pen, iconSize); + Entity_Visible = GetNextGridTexture(ref pen, iconSize); + Entity_Hidden = GetNextGridTexture(ref pen, iconSize); + } + + public ~this() + { + _editorIcons = null; } private SubTexture2D GetNextGridTexture(ref float2 pen, float2 iconSize) diff --git a/GlitchyEngine/BeefProj.toml b/GlitchyEngine/BeefProj.toml index 43d3136..babd331 100644 --- a/GlitchyEngine/BeefProj.toml +++ b/GlitchyEngine/BeefProj.toml @@ -3,7 +3,7 @@ Dependencies = {GlitchLog = "*", corlib = "*", DirectX = "*", FreeType = "*", cg [Project] Name = "GlitchyEngine" -ProcessorMacros = ["GE_GRAPHICS_DX11", "GE_SHADER_MATRIX_MISMATCH_IS_ERROR", "GE_SHADER_VAR_TYPE_MISMATCH_IS_ERROR", "GE_SHADER_UNUSED_VARIABLE_IS_WARNING", "GE_WINDOWS"] +ProcessorMacros = ["GE_GRAPHICS_DX11", "GE_SHADER_MATRIX_MISMATCH_IS_ERROR", "GE_SHADER_VAR_TYPE_MISMATCH_IS_ERROR", "GE_SHADER_UNUSED_VARIABLE_IS_WARNING", "GE_WINDOWS", "GE_EDITOR"] [Configs.Paranoid.Win32] PreprocessorMacros = ["DEBUG", "PARANOID", "GE_WINDOWS"] diff --git a/GlitchyEngine/src/Editor/EditorFlags.bf b/GlitchyEngine/src/Editor/EditorFlags.bf new file mode 100644 index 0000000..4efc3bb --- /dev/null +++ b/GlitchyEngine/src/Editor/EditorFlags.bf @@ -0,0 +1,8 @@ +namespace GlitchyEngine.Editor; + +enum EditorFlags +{ + Default = 0, + HideInHierarchy = 1, + HideInScene = 2, +} diff --git a/GlitchyEngine/src/ImGui/ImGuiExtension.bf b/GlitchyEngine/src/ImGui/ImGuiExtension.bf index 56b290e..2026105 100644 --- a/GlitchyEngine/src/ImGui/ImGuiExtension.bf +++ b/GlitchyEngine/src/ImGui/ImGuiExtension.bf @@ -138,7 +138,9 @@ namespace ImGui public static void TextUnformatted(StringView text) => TextUnformattedImpl(text.Ptr, text.Ptr + text.Length); public static void PushID(StringView id) => PushID(id.Ptr, id.Ptr + id.Length); - + + public static void PushID(int id) => PushID((void*)id); + /// Releases references that accumulated calls like ImGui::Image protected internal static extern void CleanupFrame(); diff --git a/GlitchyEngine/src/ImGui/ImGuiLayer.bf b/GlitchyEngine/src/ImGui/ImGuiLayer.bf index 164c1c9..0a37fe4 100644 --- a/GlitchyEngine/src/ImGui/ImGuiLayer.bf +++ b/GlitchyEngine/src/ImGui/ImGuiLayer.bf @@ -136,6 +136,8 @@ namespace GlitchyEngine.ImGui colors[(.)ImGui.Col.Tab] = ImGui.Vec4(0.473f, 0.519f, 0.580f, 1.00f); colors[(.)ImGui.Col.TabUnfocused] = ImGui.Vec4(0.255f, 0.255f, 0.255f, 1.00f); colors[(.)ImGui.Col.CheckMark] = ImGui.Vec4(0.851f, 0.863f, 0.900f, 1.00f); + colors[(.)ImGui.Col.TitleBg] = ImGui.Vec4(0.406f, 0.401f, 0.390f, 1.000f); + colors[(.)ImGui.Col.TitleBgActive] = ImGui.Vec4(0.196f, 0.192f, 0.182f, 1.000f); /*colors[(.)ImGui.Col.Text] = ImGui.Vec4(0.00f, 0.00f, 0.00f, 1.00f); colors[(.)ImGui.Col.TextDisabled] = ImGui.Vec4(0.60f, 0.60f, 0.60f, 1.00f); diff --git a/GlitchyEngine/src/Scripting/ScriptGlue.bf b/GlitchyEngine/src/Scripting/ScriptGlue.bf index 992a05a..cc94cf0 100644 --- a/GlitchyEngine/src/Scripting/ScriptGlue.bf +++ b/GlitchyEngine/src/Scripting/ScriptGlue.bf @@ -10,6 +10,8 @@ using System.Collections; using Box2D; using GlitchyEngine.Scripting; using GlitchyEngine.Serialization; +using GlitchyEngine.Editor; +using GlitchyEngine.World.Components; namespace GlitchyEngine.Scripting; @@ -187,6 +189,23 @@ static class ScriptGlue component = null; return false; } + + /// Gets the entity. Returns true, if the entity was found. + static bool TryGetEntitySafe(UUID entityId, out Entity entity) + { + entity = default; + + Result foundEntity = ScriptEngine.Context.GetEntityByID(entityId); + + if (foundEntity case .Ok(out entity)) + { + return true; + } + + Log.ClientLogger.Error($"No entity with ID {entityId} found."); + return false; + } + #region Exception Helpers @@ -461,6 +480,25 @@ static class ScriptGlue Mono.mono_free(rawName); } + + [RegisterCall("ScriptGlue::Entity_GetEditorFlags")] + static void Entity_GetEditorFlags(UUID entityId, out EditorFlags editorFlags) + { + editorFlags = .Default; +#if GE_EDITOR + if (TryGetEntitySafe(entityId, let entity)) + editorFlags = entity.EditorFlags; +#endif + } + + [RegisterCall("ScriptGlue::Entity_SetEditorFlags")] + static void Entity_SetEditorFlags(UUID entityId, EditorFlags editorFlags) + { +#if GE_EDITOR + if (TryGetEntitySafe(entityId, let entity)) + entity.EditorFlags = editorFlags; +#endif + } #endregion diff --git a/GlitchyEngine/src/World/Components/EditorFlagsComponent.bf b/GlitchyEngine/src/World/Components/EditorFlagsComponent.bf new file mode 100644 index 0000000..9dea12b --- /dev/null +++ b/GlitchyEngine/src/World/Components/EditorFlagsComponent.bf @@ -0,0 +1,7 @@ +using GlitchyEngine.Editor; +namespace GlitchyEngine.World.Components; + +struct EditorFlagsComponent +{ + public EditorFlags Flags; +} diff --git a/GlitchyEngine/src/World/Entity.bf b/GlitchyEngine/src/World/Entity.bf index 999e26d..f0b40c0 100644 --- a/GlitchyEngine/src/World/Entity.bf +++ b/GlitchyEngine/src/World/Entity.bf @@ -1,6 +1,8 @@ using System; using System.Collections; using GlitchyEngine.Core; +using GlitchyEngine.Editor; +using GlitchyEngine.World.Components; using internal GlitchyEngine.World; @@ -74,6 +76,22 @@ namespace GlitchyEngine.World public TransformComponent* Transform => GetComponent(); + public EditorFlags EditorFlags + { + get + { + if (TryGetComponent(let flagsComponent)) + return flagsComponent.Flags; + + return .Default; + } + set + { + if (TryGetComponent(let flagsComponent)) + flagsComponent.Flags = value; + } + } + public T* AddComponent(T value = T()) where T: struct, new { Log.EngineLogger.AssertDebug(!HasComponent(), scope $"Entity already has component."); diff --git a/ScriptCore/Editor/EditorFlags.cs b/ScriptCore/Editor/EditorFlags.cs new file mode 100644 index 0000000..7c2fc27 --- /dev/null +++ b/ScriptCore/Editor/EditorFlags.cs @@ -0,0 +1,20 @@ +namespace GlitchyEngine.Editor; + +/// +/// Flags to controls the visibility of the entity in the editor and how it can be interacted with for editing. +/// +public enum EditorFlags : byte +{ + /// + /// The default behaviour: The entity is visible in the hierarchy and in the scene, and can be interacted with. + /// + Default = 0, + /// + /// The entity is hidden in the hierarchy. + /// + HideInHierarchy = 1, + /// + /// The entity is hidden in the scene. + /// + HideInScene = 2, +} diff --git a/ScriptCore/Entity.cs b/ScriptCore/Entity.cs index 8d22b7a..948aad1 100644 --- a/ScriptCore/Entity.cs +++ b/ScriptCore/Entity.cs @@ -7,6 +7,7 @@ using GlitchyEngine.Core; using GlitchyEngine.Extensions; using GlitchyEngine.Physics; using System.Diagnostics.CodeAnalysis; +using GlitchyEngine.Editor; namespace GlitchyEngine; @@ -23,6 +24,19 @@ public class Entity : EngineObject get => ScriptGlue.Entity_GetName(_uuid); set => ScriptGlue.Entity_SetName(_uuid, value); } + + /// + /// Gets or sets the of the , which specify how the is displayed and interacted with in the editor. + /// + public EditorFlags EditorFlags + { + get + { + ScriptGlue.Entity_GetEditorFlags(_uuid, out EditorFlags flags); + return flags; + } + set => ScriptGlue.Entity_SetEditorFlags(_uuid, value); + } /// /// Only to be called by the engine. Don't call this constructor yourself, it will not result in a valid . diff --git a/ScriptCore/ScriptGlue.cs b/ScriptCore/ScriptGlue.cs index c7a67a6..7a26d4c 100644 --- a/ScriptCore/ScriptGlue.cs +++ b/ScriptCore/ScriptGlue.cs @@ -2,6 +2,7 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; using GlitchyEngine.Core; +using GlitchyEngine.Editor; using GlitchyEngine.Math; using GlitchyEngine.Physics; using GlitchyEngine.Serialization; @@ -64,6 +65,12 @@ internal static class ScriptGlue [MethodImpl(MethodImplOptions.InternalCall)] public static extern string Entity_SetName(UUID entityId, string name); + + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern void Entity_GetEditorFlags(UUID uuid, out EditorFlags flags); + + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern void Entity_SetEditorFlags(UUID uuid, EditorFlags editorFlags); #endregion Entity