diff --git a/new_editor/CMakeLists.txt b/new_editor/CMakeLists.txt index 1c3ca7b9..2c9f6b54 100644 --- a/new_editor/CMakeLists.txt +++ b/new_editor/CMakeLists.txt @@ -185,29 +185,6 @@ set(XCUI_EDITOR_HOST_RENDERING_SOURCES app/Rendering/D3D12/D3D12WindowRenderLoop.cpp ) -add_library(XCUIEditorHost STATIC - ${XCUI_EDITOR_HOST_PLATFORM_SOURCES} - ${XCUI_EDITOR_HOST_RENDERING_SOURCES} -) - -target_include_directories(XCUIEditorHost - PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/app - ${CMAKE_CURRENT_SOURCE_DIR}/include - ${CMAKE_SOURCE_DIR}/engine/include -) - -xcui_editor_apply_common_target_settings(XCUIEditorHost PRIVATE) - -target_link_libraries(XCUIEditorHost PRIVATE - XCEngine - d3d12.lib - d3dcompiler.lib - dwrite.lib - dxgi.lib - windowscodecs.lib -) - if(XCENGINE_BUILD_XCUI_EDITOR_APP) set(XCUI_EDITOR_APP_BOOTSTRAP_SOURCES app/Bootstrap/EditorApp.rc @@ -217,6 +194,7 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) set(XCUI_EDITOR_APP_STATE_SOURCES app/State/EditorColorPickerToolState.cpp + app/State/EditorUtilityWindowRequestState.cpp app/State/EditorSession.cpp ) @@ -241,6 +219,7 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) app/Features/ColorPicker/ColorPickerPanel.cpp app/Features/Hierarchy/HierarchyModel.cpp app/Features/Hierarchy/HierarchyPanel.cpp + app/Features/Inspector/AddComponentPanel.cpp app/Features/Inspector/InspectorPanel.cpp app/Features/Inspector/InspectorPresentationModel.cpp app/Features/Inspector/InspectorSubject.cpp @@ -279,8 +258,10 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) set(XCUI_EDITOR_APP_PLATFORM_SOURCES app/Platform/Win32/EditorWindow.cpp + app/Platform/Win32/EditorAddComponentUtilityWindowContentController.cpp app/Platform/Win32/EditorColorPickerUtilityWindowContentController.cpp app/Platform/Win32/EditorFloatingWindowPlacement.cpp + app/Platform/Win32/EditorStandaloneUtilityWindowContentController.cpp app/Platform/Win32/EditorWindowChromeController.cpp app/Platform/Win32/EditorWindowFrameDriver.cpp app/Platform/Win32/EditorWindowFrameOrchestrator.cpp @@ -306,56 +287,23 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) ${XCUI_EDITOR_APP_SUPPORT_SOURCES} ) - add_library(XCUIEditorAppCore STATIC + set(XCUI_EDITOR_APP_INTERNAL_SOURCES + ${XCUI_EDITOR_HOST_PLATFORM_SOURCES} + ${XCUI_EDITOR_HOST_RENDERING_SOURCES} ${XCUI_EDITOR_APP_CORE_SOURCES} - ) - - target_include_directories(XCUIEditorAppCore PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/app - ${CMAKE_CURRENT_SOURCE_DIR}/include - ${CMAKE_SOURCE_DIR}/engine/include - ${CMAKE_SOURCE_DIR}/engine/third_party/stb - ) - - target_compile_definitions(XCUIEditorAppCore PRIVATE - XCUIEDITOR_REPO_ROOT="${XCUIEDITOR_REPO_ROOT_PATH}" - ) - - xcui_editor_apply_common_target_settings(XCUIEditorAppCore PRIVATE) - - target_link_libraries(XCUIEditorAppCore PRIVATE - XCUIEditorLib - XCEngineRenderingEditorSupport - ) - - add_library(XCUIEditorAppLib STATIC ${XCUI_EDITOR_APP_PLATFORM_SOURCES} ) - target_include_directories(XCUIEditorAppLib PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/app - ${CMAKE_CURRENT_SOURCE_DIR}/include - ${CMAKE_SOURCE_DIR}/engine/include - ) - - target_compile_definitions(XCUIEditorAppLib PRIVATE - XCUIEDITOR_REPO_ROOT="${XCUIEDITOR_REPO_ROOT_PATH}" - ) - - xcui_editor_apply_common_target_settings(XCUIEditorAppLib PRIVATE) - - target_link_libraries(XCUIEditorAppLib PRIVATE - XCUIEditorAppCore - XCUIEditorLib - XCUIEditorHost - ) - add_executable(XCUIEditorApp WIN32 ${XCUI_EDITOR_APP_BOOTSTRAP_SOURCES} + ${XCUI_EDITOR_APP_INTERNAL_SOURCES} ) target_include_directories(XCUIEditorApp PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/app + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_SOURCE_DIR}/engine/include + ${CMAKE_SOURCE_DIR}/engine/third_party/stb ) target_compile_definitions(XCUIEditorApp PRIVATE @@ -365,10 +313,9 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) xcui_editor_apply_common_target_settings(XCUIEditorApp PRIVATE) target_link_libraries(XCUIEditorApp PRIVATE - XCUIEditorAppLib - XCUIEditorAppCore XCUIEditorLib - XCUIEditorHost + XCEngine + XCEngineRenderingEditorSupport d3d12.lib d3dcompiler.lib dwrite.lib @@ -388,6 +335,13 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) ) endif() + add_custom_command(TARGET XCUIEditorApp POST_BUILD + COMMAND ${CMAKE_COMMAND} -E rm -f + $/XCUIEditorAppCore.lib + $/XCUIEditorAppLib.lib + $/XCUIEditorHost.lib + ) + if(WIN32 AND XCENGINE_ENABLE_PHYSX) xcengine_copy_physx_runtime_dlls(XCUIEditorApp) endif() diff --git a/new_editor/app/Composition/EditorContext.cpp b/new_editor/app/Composition/EditorContext.cpp index 2d70b5f1..43112a47 100644 --- a/new_editor/app/Composition/EditorContext.cpp +++ b/new_editor/app/Composition/EditorContext.cpp @@ -71,6 +71,7 @@ bool EditorContext::Initialize(const std::filesystem::path& repoRoot) { AppendUIEditorRuntimeTrace("startup", "EditorSceneRuntime::Initialize end"); m_sceneRuntime.BindSelectionService(&m_selectionService); ResetEditorColorPickerToolState(m_colorPickerToolState); + ResetEditorUtilityWindowRequestState(m_utilityWindowRequestState); SyncSessionFromSelectionService(); m_hostCommandBridge.BindSession(m_session); m_hostCommandBridge.BindCommandFocusService(m_commandFocusService); @@ -166,6 +167,14 @@ const EditorColorPickerToolState& EditorContext::GetColorPickerToolState() const return m_colorPickerToolState; } +void EditorContext::RequestOpenUtilityWindow(EditorUtilityWindowKind kind) { + RequestEditorUtilityWindow(m_utilityWindowRequestState, kind); +} + +std::optional EditorContext::ConsumeOpenUtilityWindowRequest() { + return ConsumeEditorUtilityWindowRequest(m_utilityWindowRequestState); +} + void EditorContext::SetSelection(EditorSelectionState selection) { m_selectionService.SetSelection(std::move(selection)); SyncSessionFromSelectionService(); diff --git a/new_editor/app/Composition/EditorContext.h b/new_editor/app/Composition/EditorContext.h index fb1f1679..c178256d 100644 --- a/new_editor/app/Composition/EditorContext.h +++ b/new_editor/app/Composition/EditorContext.h @@ -9,6 +9,7 @@ #include "State/EditorCommandFocusService.h" #include "State/EditorSelectionService.h" #include "State/EditorSession.h" +#include "State/EditorUtilityWindowRequestState.h" #include #include #include @@ -16,6 +17,7 @@ #include #include +#include #include #include @@ -52,6 +54,8 @@ public: const EditorSceneRuntime& GetSceneRuntime() const; EditorColorPickerToolState& GetColorPickerToolState(); const EditorColorPickerToolState& GetColorPickerToolState() const; + void RequestOpenUtilityWindow(EditorUtilityWindowKind kind); + std::optional ConsumeOpenUtilityWindowRequest(); void SetSelection(EditorSelectionState selection); void ClearSelection(); void SyncSessionFromSelectionService(); @@ -89,6 +93,7 @@ private: EditorProjectRuntime m_projectRuntime = {}; EditorSceneRuntime m_sceneRuntime = {}; EditorColorPickerToolState m_colorPickerToolState = {}; + EditorUtilityWindowRequestState m_utilityWindowRequestState = {}; EditorHostCommandBridge m_hostCommandBridge = {}; Ports::SystemInteractionPort* m_systemInteractionHost = nullptr; std::string m_lastStatus = {}; diff --git a/new_editor/app/Features/Inspector/AddComponentPanel.cpp b/new_editor/app/Features/Inspector/AddComponentPanel.cpp new file mode 100644 index 00000000..feb19981 --- /dev/null +++ b/new_editor/app/Features/Inspector/AddComponentPanel.cpp @@ -0,0 +1,304 @@ +#include "Features/Inspector/AddComponentPanel.h" + +#include "Composition/EditorContext.h" +#include "Features/Inspector/Components/IInspectorComponentEditor.h" +#include "Features/Inspector/Components/InspectorComponentEditorRegistry.h" + +#include + +#include +#include +#include + +namespace XCEngine::UI::Editor::App { + +namespace { + +using ::XCEngine::UI::UIColor; +using ::XCEngine::UI::UIDrawList; +using ::XCEngine::UI::UIInputEvent; +using ::XCEngine::UI::UIInputEventType; +using ::XCEngine::UI::UIPoint; +using ::XCEngine::UI::UIPointerButton; +using ::XCEngine::UI::UIRect; + +constexpr float kPanelPadding = 8.0f; +constexpr float kHeaderGap = 8.0f; +constexpr float kCaptionHeight = 16.0f; +constexpr float kTargetHeight = 16.0f; +constexpr float kCaptionFontSize = 11.0f; +constexpr float kTargetFontSize = 12.0f; +constexpr float kEntryHeight = 30.0f; +constexpr float kEntryGap = 4.0f; +constexpr float kEntryTextPadding = 8.0f; +constexpr float kReasonFontSize = 10.0f; +constexpr std::size_t kInvalidEntryIndex = static_cast(-1); + +bool ContainsPoint(const UIRect& rect, const UIPoint& point) { + return point.x >= rect.x && + point.x <= rect.x + rect.width && + point.y >= rect.y && + point.y <= rect.y + rect.height; +} + +float ResolveTextTop(float rectY, float rectHeight, float fontSize) { + const float lineHeight = fontSize * 1.6f; + return rectY + std::floor((rectHeight - lineHeight) * 0.5f); +} + +UIColor ResolveEntryFillColor( + bool enabled, + bool hovered, + bool pressed, + const Widgets::UIEditorPropertyGridPalette& palette) { + if (!enabled) { + return palette.valueBoxReadOnlyColor; + } + + if (pressed) { + return palette.valueBoxEditingColor; + } + + if (hovered) { + return palette.valueBoxHoverColor; + } + + return palette.valueBoxColor; +} + +} // namespace + +void AddComponentPanel::ResetPanelState() { + m_visible = false; + m_hasTarget = false; + m_bounds = {}; + m_targetDisplayName.clear(); + m_entries.clear(); + ResetInteractionState(); +} + +void AddComponentPanel::ResetInteractionState() { + m_hoveredEntryIndex = kInvalidEntryIndex; + m_pressedEntryIndex = kInvalidEntryIndex; +} + +void AddComponentPanel::RebuildEntries( + const ::XCEngine::Components::GameObject* gameObject) { + const auto& editors = InspectorComponentEditorRegistry::Get().GetEditors(); + m_entries.clear(); + m_entries.reserve(editors.size()); + + const float entryX = m_bounds.x + kPanelPadding; + const float entryWidth = (std::max)(m_bounds.width - kPanelPadding * 2.0f, 0.0f); + float nextY = + m_bounds.y + + kPanelPadding + + kCaptionHeight + + kTargetHeight + + kHeaderGap; + + for (const auto& editorPtr : editors) { + const IInspectorComponentEditor* editor = editorPtr.get(); + if (editor == nullptr || !editor->ShowInAddComponentMenu()) { + continue; + } + + EntryPresentation entry = {}; + entry.componentTypeName = std::string(editor->GetComponentTypeName()); + entry.displayName = std::string(editor->GetDisplayName()); + entry.enabled = editor->CanAddTo(gameObject); + entry.disabledReason = std::string(editor->GetAddDisabledReason(gameObject)); + entry.rect = UIRect(entryX, nextY, entryWidth, kEntryHeight); + m_entries.push_back(std::move(entry)); + nextY += kEntryHeight + kEntryGap; + } + + if (m_hoveredEntryIndex >= m_entries.size()) { + m_hoveredEntryIndex = kInvalidEntryIndex; + } + if (m_pressedEntryIndex >= m_entries.size()) { + m_pressedEntryIndex = kInvalidEntryIndex; + } +} + +bool AddComponentPanel::TryActivateEntry( + EditorContext& context, + std::size_t entryIndex) { + if (entryIndex >= m_entries.size()) { + return false; + } + + const EntryPresentation& entry = m_entries[entryIndex]; + if (!entry.enabled) { + return false; + } + + if (!context.GetSceneRuntime().AddComponentToSelectedGameObject(entry.componentTypeName)) { + return false; + } + + m_hasTarget = context.GetSceneRuntime().GetSelectedGameObject() != nullptr; + m_targetDisplayName = context.GetSceneRuntime().GetSelectedDisplayName(); + RebuildEntries(context.GetSceneRuntime().GetSelectedGameObject()); + return true; +} + +std::size_t AddComponentPanel::HitTestEntry(const UIPoint& point) const { + for (std::size_t index = 0u; index < m_entries.size(); ++index) { + if (ContainsPoint(m_entries[index].rect, point)) { + return index; + } + } + + return kInvalidEntryIndex; +} + +void AddComponentPanel::Update( + EditorContext& context, + const AddComponentPanelHostContext& hostContext, + const std::vector& inputEvents) { + if (!hostContext.mounted) { + ResetPanelState(); + return; + } + + m_visible = true; + m_bounds = hostContext.bounds; + m_hasTarget = context.GetSceneRuntime().GetSelectedGameObject() != nullptr; + m_targetDisplayName = context.GetSceneRuntime().GetSelectedDisplayName(); + RebuildEntries(context.GetSceneRuntime().GetSelectedGameObject()); + + if (hostContext.focusLost) { + ResetInteractionState(); + } + + for (const UIInputEvent& event : inputEvents) { + switch (event.type) { + case UIInputEventType::PointerMove: + m_hoveredEntryIndex = HitTestEntry(event.position); + break; + + case UIInputEventType::PointerLeave: + case UIInputEventType::FocusLost: + ResetInteractionState(); + break; + + case UIInputEventType::PointerButtonDown: + if (event.pointerButton != UIPointerButton::Left) { + break; + } + + m_hoveredEntryIndex = HitTestEntry(event.position); + m_pressedEntryIndex = + m_hoveredEntryIndex < m_entries.size() && + m_entries[m_hoveredEntryIndex].enabled + ? m_hoveredEntryIndex + : kInvalidEntryIndex; + break; + + case UIInputEventType::PointerButtonUp: + if (event.pointerButton != UIPointerButton::Left) { + break; + } + + m_hoveredEntryIndex = HitTestEntry(event.position); + if (m_pressedEntryIndex != kInvalidEntryIndex && + m_pressedEntryIndex == m_hoveredEntryIndex) { + TryActivateEntry(context, m_pressedEntryIndex); + } + m_pressedEntryIndex = kInvalidEntryIndex; + break; + + default: + break; + } + } +} + +void AddComponentPanel::Append(UIDrawList& drawList) const { + if (!m_visible || m_bounds.width <= 0.0f || m_bounds.height <= 0.0f) { + return; + } + + const Widgets::UIEditorPropertyGridPalette& palette = + ::XCEngine::UI::Editor::GetUIEditorFixedPropertyGridPalette(); + const Widgets::UIEditorPropertyGridMetrics& metrics = + ::XCEngine::UI::Editor::GetUIEditorFixedPropertyGridMetrics(); + + drawList.AddFilledRect(m_bounds, palette.surfaceColor); + drawList.AddText( + UIPoint( + m_bounds.x + kPanelPadding, + ResolveTextTop(m_bounds.y + kPanelPadding, kCaptionHeight, kCaptionFontSize)), + "Target", + palette.labelTextColor, + kCaptionFontSize); + + const std::string targetText = m_hasTarget + ? m_targetDisplayName + : std::string("No scene object selected."); + drawList.AddText( + UIPoint( + m_bounds.x + kPanelPadding, + ResolveTextTop( + m_bounds.y + kPanelPadding + kCaptionHeight, + kTargetHeight, + kTargetFontSize)), + targetText, + m_hasTarget ? palette.valueTextColor : palette.readOnlyValueTextColor, + kTargetFontSize); + + if (m_entries.empty()) { + drawList.AddText( + UIPoint( + m_bounds.x + kPanelPadding, + ResolveTextTop( + m_bounds.y + kPanelPadding + kCaptionHeight + kTargetHeight + kHeaderGap, + kTargetHeight, + kTargetFontSize)), + "No registered component editors.", + palette.readOnlyValueTextColor, + kTargetFontSize); + return; + } + + drawList.PushClipRect(m_bounds); + for (std::size_t index = 0u; index < m_entries.size(); ++index) { + const EntryPresentation& entry = m_entries[index]; + const bool hovered = index == m_hoveredEntryIndex; + const bool pressed = index == m_pressedEntryIndex; + drawList.AddFilledRect( + entry.rect, + ResolveEntryFillColor(entry.enabled, hovered, pressed, palette), + metrics.valueBoxRounding); + drawList.AddRectOutline( + entry.rect, + palette.valueBoxBorderColor, + metrics.borderThickness, + metrics.valueBoxRounding); + + const UIColor nameColor = + entry.enabled ? palette.valueTextColor : palette.readOnlyValueTextColor; + const float nameY = entry.disabledReason.empty() + ? ResolveTextTop(entry.rect.y, entry.rect.height, kTargetFontSize) + : entry.rect.y + 2.0f; + drawList.AddText( + UIPoint(entry.rect.x + kEntryTextPadding, nameY), + entry.displayName, + nameColor, + kTargetFontSize); + + if (!entry.disabledReason.empty()) { + drawList.AddText( + UIPoint( + entry.rect.x + kEntryTextPadding, + entry.rect.y + entry.rect.height - 14.0f), + entry.disabledReason, + palette.labelTextColor, + kReasonFontSize); + } + } + drawList.PopClipRect(); +} + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Features/Inspector/AddComponentPanel.h b/new_editor/app/Features/Inspector/AddComponentPanel.h new file mode 100644 index 00000000..79d6a24e --- /dev/null +++ b/new_editor/app/Features/Inspector/AddComponentPanel.h @@ -0,0 +1,57 @@ +#pragma once + +#include + +#include +#include +#include + +namespace XCEngine::Components { +class GameObject; +} + +namespace XCEngine::UI::Editor::App { + +class EditorContext; + +struct AddComponentPanelHostContext { + bool mounted = false; + ::XCEngine::UI::UIRect bounds = {}; + bool focused = false; + bool focusGained = false; + bool focusLost = false; +}; + +class AddComponentPanel { +public: + void ResetInteractionState(); + void Update( + EditorContext& context, + const AddComponentPanelHostContext& hostContext, + const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents); + void Append(::XCEngine::UI::UIDrawList& drawList) const; + +private: + struct EntryPresentation { + std::string componentTypeName = {}; + std::string displayName = {}; + std::string disabledReason = {}; + ::XCEngine::UI::UIRect rect = {}; + bool enabled = false; + }; + + void ResetPanelState(); + void RebuildEntries(const ::XCEngine::Components::GameObject* gameObject); + bool TryActivateEntry(EditorContext& context, std::size_t entryIndex); + std::size_t HitTestEntry(const ::XCEngine::UI::UIPoint& point) const; + + bool m_visible = false; + bool m_hasTarget = false; + ::XCEngine::UI::UIRect m_bounds = {}; + std::string m_targetDisplayName = {}; + std::vector m_entries = {}; + std::size_t m_hoveredEntryIndex = static_cast(-1); + std::size_t m_pressedEntryIndex = static_cast(-1); +}; + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Features/Inspector/Components/AudioListenerInspectorComponentEditor.h b/new_editor/app/Features/Inspector/Components/AudioListenerInspectorComponentEditor.h index 57e8f1d8..bd1ac7c9 100644 --- a/new_editor/app/Features/Inspector/Components/AudioListenerInspectorComponentEditor.h +++ b/new_editor/app/Features/Inspector/Components/AudioListenerInspectorComponentEditor.h @@ -16,6 +16,18 @@ public: return "Audio Listener"; } + bool CanAddTo( + const ::XCEngine::Components::GameObject* gameObject) const override { + return CanAddUniqueInspectorComponentToGameObject< + ::XCEngine::Components::AudioListenerComponent>(gameObject); + } + + std::string_view GetAddDisabledReason( + const ::XCEngine::Components::GameObject* gameObject) const override { + return GetUniqueInspectorComponentAddDisabledReason< + ::XCEngine::Components::AudioListenerComponent>(gameObject); + } + protected: void BuildBindingSections( const InspectorComponentEditorContext& context, diff --git a/new_editor/app/Features/Inspector/Components/AudioSourceInspectorComponentEditor.h b/new_editor/app/Features/Inspector/Components/AudioSourceInspectorComponentEditor.h index 7a1eb202..7a55fd11 100644 --- a/new_editor/app/Features/Inspector/Components/AudioSourceInspectorComponentEditor.h +++ b/new_editor/app/Features/Inspector/Components/AudioSourceInspectorComponentEditor.h @@ -17,6 +17,16 @@ public: return "Audio Source"; } + bool CanAddTo( + const ::XCEngine::Components::GameObject* gameObject) const override { + return CanAddMultipleInspectorComponentsToGameObject(gameObject); + } + + std::string_view GetAddDisabledReason( + const ::XCEngine::Components::GameObject* gameObject) const override { + return GetInvalidInspectorAddDisabledReason(gameObject); + } + protected: void BuildBindingSections( const InspectorComponentEditorContext& context, diff --git a/new_editor/app/Features/Inspector/Components/BoxColliderInspectorComponentEditor.h b/new_editor/app/Features/Inspector/Components/BoxColliderInspectorComponentEditor.h index 7605d6a6..7782a545 100644 --- a/new_editor/app/Features/Inspector/Components/BoxColliderInspectorComponentEditor.h +++ b/new_editor/app/Features/Inspector/Components/BoxColliderInspectorComponentEditor.h @@ -16,6 +16,16 @@ public: return "Box Collider"; } + bool CanAddTo( + const ::XCEngine::Components::GameObject* gameObject) const override { + return CanAddSingleColliderInspectorComponentToGameObject(gameObject); + } + + std::string_view GetAddDisabledReason( + const ::XCEngine::Components::GameObject* gameObject) const override { + return GetSingleColliderInspectorAddDisabledReason(gameObject); + } + protected: void BuildBindingSections( const InspectorComponentEditorContext& context, diff --git a/new_editor/app/Features/Inspector/Components/CameraInspectorComponentEditor.h b/new_editor/app/Features/Inspector/Components/CameraInspectorComponentEditor.h index 7763d9f8..5ddccceb 100644 --- a/new_editor/app/Features/Inspector/Components/CameraInspectorComponentEditor.h +++ b/new_editor/app/Features/Inspector/Components/CameraInspectorComponentEditor.h @@ -16,6 +16,18 @@ public: return "Camera"; } + bool CanAddTo( + const ::XCEngine::Components::GameObject* gameObject) const override { + return CanAddUniqueInspectorComponentToGameObject< + ::XCEngine::Components::CameraComponent>(gameObject); + } + + std::string_view GetAddDisabledReason( + const ::XCEngine::Components::GameObject* gameObject) const override { + return GetUniqueInspectorComponentAddDisabledReason< + ::XCEngine::Components::CameraComponent>(gameObject); + } + protected: void BuildBindingSections( const InspectorComponentEditorContext& context, diff --git a/new_editor/app/Features/Inspector/Components/CapsuleColliderInspectorComponentEditor.h b/new_editor/app/Features/Inspector/Components/CapsuleColliderInspectorComponentEditor.h index c3362a8b..39011682 100644 --- a/new_editor/app/Features/Inspector/Components/CapsuleColliderInspectorComponentEditor.h +++ b/new_editor/app/Features/Inspector/Components/CapsuleColliderInspectorComponentEditor.h @@ -16,6 +16,16 @@ public: return "Capsule Collider"; } + bool CanAddTo( + const ::XCEngine::Components::GameObject* gameObject) const override { + return CanAddSingleColliderInspectorComponentToGameObject(gameObject); + } + + std::string_view GetAddDisabledReason( + const ::XCEngine::Components::GameObject* gameObject) const override { + return GetSingleColliderInspectorAddDisabledReason(gameObject); + } + protected: void BuildBindingSections( const InspectorComponentEditorContext& context, diff --git a/new_editor/app/Features/Inspector/Components/IInspectorComponentEditor.h b/new_editor/app/Features/Inspector/Components/IInspectorComponentEditor.h index 462a4996..96fddbaa 100644 --- a/new_editor/app/Features/Inspector/Components/IInspectorComponentEditor.h +++ b/new_editor/app/Features/Inspector/Components/IInspectorComponentEditor.h @@ -45,6 +45,22 @@ public: const InspectorComponentEditorContext& context, const Widgets::UIEditorPropertyGridField& field) const = 0; + virtual bool ShowInAddComponentMenu() const { + return true; + } + + virtual bool CanAddTo( + const ::XCEngine::Components::GameObject* gameObject) const { + (void)gameObject; + return false; + } + + virtual std::string_view GetAddDisabledReason( + const ::XCEngine::Components::GameObject* gameObject) const { + (void)gameObject; + return {}; + } + virtual void AppendStructureSignature( const InspectorComponentEditorContext& context, std::string& signature) const { diff --git a/new_editor/app/Features/Inspector/Components/InspectorComponentEditorUtils.h b/new_editor/app/Features/Inspector/Components/InspectorComponentEditorUtils.h index 37ddd990..a5b0f668 100644 --- a/new_editor/app/Features/Inspector/Components/InspectorComponentEditorUtils.h +++ b/new_editor/app/Features/Inspector/Components/InspectorComponentEditorUtils.h @@ -2,6 +2,8 @@ #include +#include +#include #include #include @@ -310,6 +312,66 @@ inline void AppendInspectorStructureToken( signature.push_back('|'); } +inline bool CanAddMultipleInspectorComponentsToGameObject( + const ::XCEngine::Components::GameObject* gameObject) { + return gameObject != nullptr; +} + +inline std::string_view GetInvalidInspectorAddDisabledReason( + const ::XCEngine::Components::GameObject* gameObject) { + return gameObject == nullptr + ? std::string_view("Invalid") + : std::string_view{}; +} + +inline bool CanAddBuiltInInspectorComponentToGameObject( + const ::XCEngine::Components::GameObject* gameObject) { + (void)gameObject; + return false; +} + +inline std::string_view GetBuiltInInspectorAddDisabledReason( + const ::XCEngine::Components::GameObject* gameObject) { + (void)gameObject; + return "Built-in"; +} + +template +bool CanAddUniqueInspectorComponentToGameObject( + const ::XCEngine::Components::GameObject* gameObject) { + return gameObject != nullptr && + gameObject->GetComponent() == nullptr; +} + +template +std::string_view GetUniqueInspectorComponentAddDisabledReason( + const ::XCEngine::Components::GameObject* gameObject) { + if (gameObject == nullptr) { + return "Invalid"; + } + + return gameObject->GetComponent() != nullptr + ? std::string_view("Already Added") + : std::string_view{}; +} + +inline bool CanAddSingleColliderInspectorComponentToGameObject( + const ::XCEngine::Components::GameObject* gameObject) { + return gameObject != nullptr && + gameObject->GetComponent<::XCEngine::Components::ColliderComponent>() == nullptr; +} + +inline std::string_view GetSingleColliderInspectorAddDisabledReason( + const ::XCEngine::Components::GameObject* gameObject) { + if (gameObject == nullptr) { + return "Invalid"; + } + + return gameObject->GetComponent<::XCEngine::Components::ColliderComponent>() != nullptr + ? std::string_view("Only One Collider Supported") + : std::string_view{}; +} + template const TComponent* ResolveInspectorComponent( const InspectorComponentEditorContext& context) { diff --git a/new_editor/app/Features/Inspector/Components/LightInspectorComponentEditor.h b/new_editor/app/Features/Inspector/Components/LightInspectorComponentEditor.h index 6ff4e183..d6807441 100644 --- a/new_editor/app/Features/Inspector/Components/LightInspectorComponentEditor.h +++ b/new_editor/app/Features/Inspector/Components/LightInspectorComponentEditor.h @@ -16,6 +16,18 @@ public: return "Light"; } + bool CanAddTo( + const ::XCEngine::Components::GameObject* gameObject) const override { + return CanAddUniqueInspectorComponentToGameObject< + ::XCEngine::Components::LightComponent>(gameObject); + } + + std::string_view GetAddDisabledReason( + const ::XCEngine::Components::GameObject* gameObject) const override { + return GetUniqueInspectorComponentAddDisabledReason< + ::XCEngine::Components::LightComponent>(gameObject); + } + protected: void BuildBindingSections( const InspectorComponentEditorContext& context, diff --git a/new_editor/app/Features/Inspector/Components/MeshFilterInspectorComponentEditor.h b/new_editor/app/Features/Inspector/Components/MeshFilterInspectorComponentEditor.h index cf998659..8ebb7ec5 100644 --- a/new_editor/app/Features/Inspector/Components/MeshFilterInspectorComponentEditor.h +++ b/new_editor/app/Features/Inspector/Components/MeshFilterInspectorComponentEditor.h @@ -16,6 +16,18 @@ public: return "Mesh Filter"; } + bool CanAddTo( + const ::XCEngine::Components::GameObject* gameObject) const override { + return CanAddUniqueInspectorComponentToGameObject< + ::XCEngine::Components::MeshFilterComponent>(gameObject); + } + + std::string_view GetAddDisabledReason( + const ::XCEngine::Components::GameObject* gameObject) const override { + return GetUniqueInspectorComponentAddDisabledReason< + ::XCEngine::Components::MeshFilterComponent>(gameObject); + } + protected: void BuildBindingSections( const InspectorComponentEditorContext& context, diff --git a/new_editor/app/Features/Inspector/Components/MeshRendererInspectorComponentEditor.h b/new_editor/app/Features/Inspector/Components/MeshRendererInspectorComponentEditor.h index 00c12f03..72f855cd 100644 --- a/new_editor/app/Features/Inspector/Components/MeshRendererInspectorComponentEditor.h +++ b/new_editor/app/Features/Inspector/Components/MeshRendererInspectorComponentEditor.h @@ -20,6 +20,18 @@ public: return "Mesh Renderer"; } + bool CanAddTo( + const ::XCEngine::Components::GameObject* gameObject) const override { + return CanAddUniqueInspectorComponentToGameObject< + ::XCEngine::Components::MeshRendererComponent>(gameObject); + } + + std::string_view GetAddDisabledReason( + const ::XCEngine::Components::GameObject* gameObject) const override { + return GetUniqueInspectorComponentAddDisabledReason< + ::XCEngine::Components::MeshRendererComponent>(gameObject); + } + protected: void BuildBindingSections( const InspectorComponentEditorContext& context, diff --git a/new_editor/app/Features/Inspector/Components/RigidbodyInspectorComponentEditor.h b/new_editor/app/Features/Inspector/Components/RigidbodyInspectorComponentEditor.h index 36c11b30..e9271c65 100644 --- a/new_editor/app/Features/Inspector/Components/RigidbodyInspectorComponentEditor.h +++ b/new_editor/app/Features/Inspector/Components/RigidbodyInspectorComponentEditor.h @@ -16,6 +16,18 @@ public: return "Rigidbody"; } + bool CanAddTo( + const ::XCEngine::Components::GameObject* gameObject) const override { + return CanAddUniqueInspectorComponentToGameObject< + ::XCEngine::Components::RigidbodyComponent>(gameObject); + } + + std::string_view GetAddDisabledReason( + const ::XCEngine::Components::GameObject* gameObject) const override { + return GetUniqueInspectorComponentAddDisabledReason< + ::XCEngine::Components::RigidbodyComponent>(gameObject); + } + protected: void BuildBindingSections( const InspectorComponentEditorContext& context, diff --git a/new_editor/app/Features/Inspector/Components/SphereColliderInspectorComponentEditor.h b/new_editor/app/Features/Inspector/Components/SphereColliderInspectorComponentEditor.h index 0390f160..32ed501f 100644 --- a/new_editor/app/Features/Inspector/Components/SphereColliderInspectorComponentEditor.h +++ b/new_editor/app/Features/Inspector/Components/SphereColliderInspectorComponentEditor.h @@ -16,6 +16,16 @@ public: return "Sphere Collider"; } + bool CanAddTo( + const ::XCEngine::Components::GameObject* gameObject) const override { + return CanAddSingleColliderInspectorComponentToGameObject(gameObject); + } + + std::string_view GetAddDisabledReason( + const ::XCEngine::Components::GameObject* gameObject) const override { + return GetSingleColliderInspectorAddDisabledReason(gameObject); + } + protected: void BuildBindingSections( const InspectorComponentEditorContext& context, diff --git a/new_editor/app/Features/Inspector/Components/TransformInspectorComponentEditor.cpp b/new_editor/app/Features/Inspector/Components/TransformInspectorComponentEditor.cpp index 5dad9a4d..a29f5fc7 100644 --- a/new_editor/app/Features/Inspector/Components/TransformInspectorComponentEditor.cpp +++ b/new_editor/app/Features/Inspector/Components/TransformInspectorComponentEditor.cpp @@ -12,6 +12,16 @@ std::string_view TransformInspectorComponentEditor::GetDisplayName() const { return "Transform"; } +bool TransformInspectorComponentEditor::CanAddTo( + const ::XCEngine::Components::GameObject* gameObject) const { + return CanAddBuiltInInspectorComponentToGameObject(gameObject); +} + +std::string_view TransformInspectorComponentEditor::GetAddDisabledReason( + const ::XCEngine::Components::GameObject* gameObject) const { + return GetBuiltInInspectorAddDisabledReason(gameObject); +} + void TransformInspectorComponentEditor::BuildBindingSections( const InspectorComponentEditorContext& context, std::vector& outSections) const { diff --git a/new_editor/app/Features/Inspector/Components/TransformInspectorComponentEditor.h b/new_editor/app/Features/Inspector/Components/TransformInspectorComponentEditor.h index 1d6c9bf7..d5cebc65 100644 --- a/new_editor/app/Features/Inspector/Components/TransformInspectorComponentEditor.h +++ b/new_editor/app/Features/Inspector/Components/TransformInspectorComponentEditor.h @@ -9,6 +9,10 @@ public: std::string_view GetComponentTypeName() const override; std::string_view GetDisplayName() const override; + bool CanAddTo( + const ::XCEngine::Components::GameObject* gameObject) const override; + std::string_view GetAddDisabledReason( + const ::XCEngine::Components::GameObject* gameObject) const override; bool CanRemove( const InspectorComponentEditorContext& context) const override; diff --git a/new_editor/app/Features/Inspector/Components/VolumeRendererInspectorComponentEditor.h b/new_editor/app/Features/Inspector/Components/VolumeRendererInspectorComponentEditor.h index 876f0752..1c7c3f19 100644 --- a/new_editor/app/Features/Inspector/Components/VolumeRendererInspectorComponentEditor.h +++ b/new_editor/app/Features/Inspector/Components/VolumeRendererInspectorComponentEditor.h @@ -16,6 +16,18 @@ public: return "Volume Renderer"; } + bool CanAddTo( + const ::XCEngine::Components::GameObject* gameObject) const override { + return CanAddUniqueInspectorComponentToGameObject< + ::XCEngine::Components::VolumeRendererComponent>(gameObject); + } + + std::string_view GetAddDisabledReason( + const ::XCEngine::Components::GameObject* gameObject) const override { + return GetUniqueInspectorComponentAddDisabledReason< + ::XCEngine::Components::VolumeRendererComponent>(gameObject); + } + protected: void BuildBindingSections( const InspectorComponentEditorContext& context, diff --git a/new_editor/app/Features/Inspector/InspectorPanel.cpp b/new_editor/app/Features/Inspector/InspectorPanel.cpp index a457e3a3..e2850c7b 100644 --- a/new_editor/app/Features/Inspector/InspectorPanel.cpp +++ b/new_editor/app/Features/Inspector/InspectorPanel.cpp @@ -22,7 +22,9 @@ using ::XCEngine::UI::UIColor; using ::XCEngine::UI::UIDrawList; using ::XCEngine::UI::UIInputEvent; using ::XCEngine::UI::UIPoint; +using ::XCEngine::UI::UIPointerButton; using ::XCEngine::UI::UIRect; +using ::XCEngine::UI::UIInputEventType; constexpr float kPanelPadding = 10.0f; constexpr float kTitleHeight = 18.0f; @@ -30,6 +32,9 @@ constexpr float kSubtitleHeight = 16.0f; constexpr float kHeaderGap = 10.0f; constexpr float kTitleFontSize = 13.0f; constexpr float kSubtitleFontSize = 11.0f; +constexpr float kAddComponentButtonHeight = 24.0f; +constexpr float kAddComponentButtonTopGap = 10.0f; +constexpr float kAddComponentButtonFontSize = 12.0f; constexpr UIColor kTitleColor(0.930f, 0.930f, 0.930f, 1.0f); constexpr UIColor kSubtitleColor(0.660f, 0.660f, 0.660f, 1.0f); constexpr UIColor kSurfaceColor(0.10f, 0.10f, 0.10f, 1.0f); @@ -68,6 +73,27 @@ float ResolveTextTop(float rectY, float rectHeight, float fontSize) { return rectY + std::floor((rectHeight - lineHeight) * 0.5f); } +float ResolveCenteredTextX( + const UIRect& rect, + std::string_view text, + float fontSize) { + const float estimatedTextWidth = + static_cast(text.size()) * fontSize * 0.56f; + return rect.x + std::floor((std::max)(rect.width - estimatedTextWidth, 0.0f) * 0.5f); +} + +UIColor ResolveAddComponentButtonFillColor(bool hovered, bool pressed) { + const Widgets::UIEditorPropertyGridPalette& palette = + ::XCEngine::UI::Editor::GetUIEditorFixedPropertyGridPalette(); + if (pressed) { + return palette.valueBoxEditingColor; + } + if (hovered) { + return palette.valueBoxHoverColor; + } + return palette.valueBoxColor; +} + UIEditorHostCommandEvaluationResult BuildEvaluationResult( bool executable, std::string message) { @@ -115,6 +141,7 @@ void InspectorPanel::ResetInteractionState() { m_interactionState = {}; m_gridFrame = {}; m_lastAppliedColorPickerRevision = 0u; + ResetAddComponentButtonState(); } void InspectorPanel::SyncExpansionState(bool subjectChanged) { @@ -182,6 +209,10 @@ float InspectorPanel::ResolveHeaderHeight() const { return kTitleHeight + kSubtitleHeight + kHeaderGap; } +bool InspectorPanel::ShouldShowAddComponentButton() const { + return m_subject.kind == InspectorSubjectKind::SceneObject; +} + UIRect InspectorPanel::BuildGridBounds() const { const float horizontalPadding = ResolveInspectorHorizontalPadding(m_presentation); @@ -196,6 +227,33 @@ UIRect InspectorPanel::BuildGridBounds() const { return UIRect(x, y, width, height); } +UIRect InspectorPanel::BuildAddComponentButtonRect() const { + if (!ShouldShowAddComponentButton()) { + return {}; + } + + const float horizontalPadding = + ResolveInspectorHorizontalPadding(m_presentation); + const float bottomPadding = ResolveInspectorBottomPadding(m_presentation); + float buttonTop = BuildGridBounds().y; + if (!m_gridFrame.layout.fieldRowRects.empty()) { + const UIRect& lastFieldRect = m_gridFrame.layout.fieldRowRects.back(); + buttonTop = lastFieldRect.y + lastFieldRect.height + kAddComponentButtonTopGap; + } else if (!m_gridFrame.layout.sectionHeaderRects.empty()) { + const UIRect& lastSectionRect = m_gridFrame.layout.sectionHeaderRects.back(); + buttonTop = lastSectionRect.y + lastSectionRect.height + kAddComponentButtonTopGap; + } + + const float maxVisibleTop = + m_bounds.y + m_bounds.height - bottomPadding - kAddComponentButtonHeight; + buttonTop = (std::min)(buttonTop, maxVisibleTop); + return UIRect( + m_bounds.x + horizontalPadding, + buttonTop, + (std::max)(m_bounds.width - horizontalPadding * 2.0f, 0.0f), + kAddComponentButtonHeight); +} + const InspectorPresentationComponentBinding* InspectorPanel::FindSelectedComponentBinding() const { if (!m_fieldSelection.HasSelection()) { return nullptr; @@ -399,6 +457,66 @@ void InspectorPanel::RequestColorPicker( field->fieldId, field->colorValue.value, field->colorValue.showAlpha); + context.RequestOpenUtilityWindow(EditorUtilityWindowKind::ColorPicker); +} + +void InspectorPanel::ResetAddComponentButtonState() { + m_addComponentButtonHovered = false; + m_addComponentButtonPressed = false; +} + +void InspectorPanel::UpdateAddComponentButton( + EditorContext& context, + const std::vector& inputEvents) { + if (!ShouldShowAddComponentButton()) { + ResetAddComponentButtonState(); + return; + } + + const UIRect buttonRect = BuildAddComponentButtonRect(); + if (buttonRect.width <= 0.0f || buttonRect.height <= 0.0f) { + ResetAddComponentButtonState(); + return; + } + + for (const UIInputEvent& event : inputEvents) { + switch (event.type) { + case UIInputEventType::PointerMove: + m_addComponentButtonHovered = ContainsPoint(buttonRect, event.position); + break; + + case UIInputEventType::PointerLeave: + case UIInputEventType::FocusLost: + ResetAddComponentButtonState(); + break; + + case UIInputEventType::PointerButtonDown: + if (event.pointerButton != UIPointerButton::Left) { + break; + } + + m_addComponentButtonHovered = ContainsPoint(buttonRect, event.position); + m_addComponentButtonPressed = m_addComponentButtonHovered; + break; + + case UIInputEventType::PointerButtonUp: + if (event.pointerButton != UIPointerButton::Left) { + break; + } + + if (m_addComponentButtonPressed && + ContainsPoint(buttonRect, event.position)) { + context.RequestOpenUtilityWindow(EditorUtilityWindowKind::AddComponent); + } + + m_addComponentButtonHovered = ContainsPoint(buttonRect, event.position); + m_addComponentButtonPressed = false; + break; + + default: + break; + } + } } bool InspectorPanel::ApplyChangedField(std::string_view fieldId) { @@ -479,11 +597,6 @@ void InspectorPanel::Update( RefreshPresentation(context, subjectChanged); ApplyColorPickerToolValue(context); - if (m_presentation.sections.empty()) { - m_gridFrame = {}; - return; - } - const std::vector filteredEvents = BuildUIEditorPanelInputEvents( m_bounds, @@ -507,28 +620,32 @@ void InspectorPanel::Update( filteredEvents, m_bounds, dispatchEntry.allowInteraction); - m_gridFrame = UpdateUIEditorPropertyGridInteraction( - m_interactionState, - m_fieldSelection, - m_sectionExpansion, - m_propertyEditModel, - BuildGridBounds(), - m_presentation.sections, - filteredEvents, - ::XCEngine::UI::Editor::GetUIEditorFixedPropertyGridMetrics()); - if (m_gridFrame.result.pickerRequested && - !m_gridFrame.result.requestedFieldId.empty()) { - RequestColorPicker(context, m_gridFrame.result.requestedFieldId); - } + m_gridFrame = {}; + if (!m_presentation.sections.empty()) { + m_gridFrame = UpdateUIEditorPropertyGridInteraction( + m_interactionState, + m_fieldSelection, + m_sectionExpansion, + m_propertyEditModel, + BuildGridBounds(), + m_presentation.sections, + filteredEvents, + ::XCEngine::UI::Editor::GetUIEditorFixedPropertyGridMetrics()); + if (m_gridFrame.result.pickerRequested && + !m_gridFrame.result.requestedFieldId.empty()) { + RequestColorPicker(context, m_gridFrame.result.requestedFieldId); + } - if (m_gridFrame.result.fieldValueChanged && - !m_gridFrame.result.changedFieldId.empty()) { - if (ApplyChangedField(m_gridFrame.result.changedFieldId)) { - RefreshPresentation(context, false); - } else { - ForceResyncPresentation(context); + if (m_gridFrame.result.fieldValueChanged && + !m_gridFrame.result.changedFieldId.empty()) { + if (ApplyChangedField(m_gridFrame.result.changedFieldId)) { + RefreshPresentation(context, false); + } else { + ForceResyncPresentation(context); + } } } + UpdateAddComponentButton(context, filteredEvents); } void InspectorPanel::Append(UIDrawList& drawList) const { @@ -565,20 +682,50 @@ void InspectorPanel::Append(UIDrawList& drawList) const { kSubtitleFontSize); } - if (m_presentation.sections.empty()) { + if (!m_presentation.sections.empty()) { + Widgets::AppendUIEditorPropertyGrid( + drawList, + BuildGridBounds(), + m_presentation.sections, + m_fieldSelection, + m_sectionExpansion, + m_propertyEditModel, + m_interactionState.propertyGridState, + ::XCEngine::UI::Editor::GetUIEditorFixedPropertyGridPalette(), + ::XCEngine::UI::Editor::GetUIEditorFixedPropertyGridMetrics()); + } + + if (!ShouldShowAddComponentButton()) { return; } - Widgets::AppendUIEditorPropertyGrid( - drawList, - BuildGridBounds(), - m_presentation.sections, - m_fieldSelection, - m_sectionExpansion, - m_propertyEditModel, - m_interactionState.propertyGridState, - ::XCEngine::UI::Editor::GetUIEditorFixedPropertyGridPalette(), - ::XCEngine::UI::Editor::GetUIEditorFixedPropertyGridMetrics()); + const UIRect buttonRect = BuildAddComponentButtonRect(); + if (buttonRect.width <= 0.0f || buttonRect.height <= 0.0f) { + return; + } + + const Widgets::UIEditorPropertyGridPalette& palette = + ::XCEngine::UI::Editor::GetUIEditorFixedPropertyGridPalette(); + const Widgets::UIEditorPropertyGridMetrics& metrics = + ::XCEngine::UI::Editor::GetUIEditorFixedPropertyGridMetrics(); + drawList.AddFilledRect( + buttonRect, + ResolveAddComponentButtonFillColor( + m_addComponentButtonHovered, + m_addComponentButtonPressed), + metrics.valueBoxRounding); + drawList.AddRectOutline( + buttonRect, + palette.valueBoxBorderColor, + metrics.borderThickness, + metrics.valueBoxRounding); + drawList.AddText( + UIPoint( + ResolveCenteredTextX(buttonRect, "Add Component", kAddComponentButtonFontSize), + ResolveTextTop(buttonRect.y, buttonRect.height, kAddComponentButtonFontSize)), + "Add Component", + palette.valueTextColor, + kAddComponentButtonFontSize); } UIEditorHostCommandEvaluationResult InspectorPanel::EvaluateEditCommand( diff --git a/new_editor/app/Features/Inspector/InspectorPanel.h b/new_editor/app/Features/Inspector/InspectorPanel.h index 91be4ac5..09264835 100644 --- a/new_editor/app/Features/Inspector/InspectorPanel.h +++ b/new_editor/app/Features/Inspector/InspectorPanel.h @@ -44,7 +44,9 @@ private: void SyncSelectionState(); std::string BuildSubjectKey() const; float ResolveHeaderHeight() const; + bool ShouldShowAddComponentButton() const; ::XCEngine::UI::UIRect BuildGridBounds() const; + ::XCEngine::UI::UIRect BuildAddComponentButtonRect() const; const InspectorPresentationComponentBinding* FindSelectedComponentBinding() const; const Widgets::UIEditorPropertyGridField* FindField(std::string_view fieldId) const; Widgets::UIEditorPropertyGridField* FindMutableField(std::string_view fieldId); @@ -54,6 +56,10 @@ private: void ForceResyncPresentation(EditorContext& context); bool ApplyColorPickerToolValue(EditorContext& context); void RequestColorPicker(EditorContext& context, std::string_view fieldId); + void ResetAddComponentButtonState(); + void UpdateAddComponentButton( + EditorContext& context, + const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents); bool ApplyChangedField(std::string_view fieldId); EditorCommandFocusService* m_commandFocusService = nullptr; @@ -73,6 +79,8 @@ private: std::uint64_t m_lastProjectSelectionStamp = 0u; std::uint64_t m_lastSceneInspectorRevision = 0u; std::uint64_t m_lastAppliedColorPickerRevision = 0u; + bool m_addComponentButtonHovered = false; + bool m_addComponentButtonPressed = false; }; } // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorAddComponentUtilityWindowContentController.cpp b/new_editor/app/Platform/Win32/EditorAddComponentUtilityWindowContentController.cpp new file mode 100644 index 00000000..4d1d8a8f --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorAddComponentUtilityWindowContentController.cpp @@ -0,0 +1,51 @@ +#include "Platform/Win32/EditorAddComponentUtilityWindowContentController.h" + +#include + +namespace XCEngine::UI::Editor::App { + +EditorAddComponentUtilityWindowContentController:: + EditorAddComponentUtilityWindowContentController( + const ::XCEngine::UI::UISize& minimumOuterSize) + : EditorStandaloneUtilityWindowContentController(minimumOuterSize) {} + +EditorAddComponentUtilityWindowContentController:: + ~EditorAddComponentUtilityWindowContentController() = default; + +void EditorAddComponentUtilityWindowContentController::OnShutdown() { + m_addComponentPanel.ResetInteractionState(); +} + +void EditorAddComponentUtilityWindowContentController::OnResetInteractionState() { + m_addComponentPanel.ResetInteractionState(); +} + +EditorWindowFrameTransferRequests +EditorAddComponentUtilityWindowContentController::UpdateStandaloneContent( + const EditorStandaloneUtilityWindowFrameContext& context, + ::XCEngine::UI::UIDrawData& drawData) { + m_addComponentPanel.Update( + context.editorContext, + AddComponentPanelHostContext{ + .mounted = true, + .bounds = context.bounds, + .focused = context.focused, + .focusGained = context.focusGained, + .focusLost = context.focusLost, + }, + context.inputEvents); + + ::XCEngine::UI::UIDrawList& drawList = + drawData.EmplaceDrawList("XCEditorUtility.AddComponent"); + m_addComponentPanel.Append(drawList); + return {}; +} + +std::unique_ptr +CreateEditorAddComponentUtilityWindowContentController( + const ::XCEngine::UI::UISize& minimumOuterSize) { + return std::make_unique( + minimumOuterSize); +} + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorAddComponentUtilityWindowContentController.h b/new_editor/app/Platform/Win32/EditorAddComponentUtilityWindowContentController.h new file mode 100644 index 00000000..582b4d1d --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorAddComponentUtilityWindowContentController.h @@ -0,0 +1,31 @@ +#pragma once + +#include "Features/Inspector/AddComponentPanel.h" +#include "Platform/Win32/EditorStandaloneUtilityWindowContentController.h" + +#include + +namespace XCEngine::UI::Editor::App { + +class EditorAddComponentUtilityWindowContentController final + : public EditorStandaloneUtilityWindowContentController { +public: + explicit EditorAddComponentUtilityWindowContentController( + const ::XCEngine::UI::UISize& minimumOuterSize); + ~EditorAddComponentUtilityWindowContentController() override; + +private: + void OnShutdown() override; + void OnResetInteractionState() override; + EditorWindowFrameTransferRequests UpdateStandaloneContent( + const EditorStandaloneUtilityWindowFrameContext& context, + ::XCEngine::UI::UIDrawData& drawData) override; + + AddComponentPanel m_addComponentPanel = {}; +}; + +std::unique_ptr +CreateEditorAddComponentUtilityWindowContentController( + const ::XCEngine::UI::UISize& minimumOuterSize); + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorColorPickerUtilityWindowContentController.cpp b/new_editor/app/Platform/Win32/EditorColorPickerUtilityWindowContentController.cpp index 46c9e8cb..ed65b7e7 100644 --- a/new_editor/app/Platform/Win32/EditorColorPickerUtilityWindowContentController.cpp +++ b/new_editor/app/Platform/Win32/EditorColorPickerUtilityWindowContentController.cpp @@ -1,78 +1,38 @@ #include "Platform/Win32/EditorColorPickerUtilityWindowContentController.h" -#include - #include namespace XCEngine::UI::Editor::App { -using ::XCEngine::UI::UIInputEvent; -using ::XCEngine::UI::UIInputEventType; - EditorColorPickerUtilityWindowContentController:: EditorColorPickerUtilityWindowContentController( const ::XCEngine::UI::UISize& minimumOuterSize) - : m_minimumOuterSize(minimumOuterSize) {} + : EditorStandaloneUtilityWindowContentController(minimumOuterSize) {} EditorColorPickerUtilityWindowContentController:: ~EditorColorPickerUtilityWindowContentController() = default; -const UIEditorWorkspaceController* -EditorColorPickerUtilityWindowContentController::TryGetWorkspaceController() const { - return nullptr; -} - -UIEditorWorkspaceController* -EditorColorPickerUtilityWindowContentController::TryGetMutableWorkspaceController() { - return nullptr; -} - -void EditorColorPickerUtilityWindowContentController::ReplaceWorkspaceController( - UIEditorWorkspaceController) {} - -void EditorColorPickerUtilityWindowContentController::Initialize( - const EditorWindowContentInitializationContext&) {} - -void EditorColorPickerUtilityWindowContentController::Shutdown() { - m_colorPickerPanel.ResetInteractionState(); - m_shellInteractionState = {}; - m_shellFrame = {}; - m_windowFocused = false; -} - -void EditorColorPickerUtilityWindowContentController::ResetInteractionState() { +void EditorColorPickerUtilityWindowContentController::OnShutdown() { m_colorPickerPanel.ResetInteractionState(); } -void EditorColorPickerUtilityWindowContentController::SetViewportSurfacePresentationEnabled(bool) {} +void EditorColorPickerUtilityWindowContentController::OnResetInteractionState() { + m_colorPickerPanel.ResetInteractionState(); +} EditorWindowFrameTransferRequests -EditorColorPickerUtilityWindowContentController::UpdateAndAppend( - const EditorWindowContentFrameContext& context, +EditorColorPickerUtilityWindowContentController::UpdateStandaloneContent( + const EditorStandaloneUtilityWindowFrameContext& context, ::XCEngine::UI::UIDrawData& drawData) { - bool focusGained = false; - bool focusLost = false; - for (const UIInputEvent& event : context.inputEvents) { - if (event.type == UIInputEventType::FocusGained) { - m_windowFocused = true; - focusGained = true; - } else if (event.type == UIInputEventType::FocusLost) { - m_windowFocused = false; - focusLost = true; - } - } - - m_shellInteractionState.focused = m_windowFocused; - m_shellFrame.focused = m_windowFocused; m_colorPickerPanel.Update( context.editorContext, ColorPickerPanelHostContext{ .mounted = true, .bounds = context.bounds, .allowInteraction = true, - .focused = m_windowFocused, - .focusGained = focusGained, - .focusLost = focusLost, + .focused = context.focused, + .focusGained = context.focusGained, + .focusLost = context.focusLost, }, context.inputEvents); @@ -82,79 +42,6 @@ EditorColorPickerUtilityWindowContentController::UpdateAndAppend( return {}; } -void EditorColorPickerUtilityWindowContentController::RenderRequestedViewports( - const ::XCEngine::Rendering::RenderContext&) {} - -const UIEditorShellInteractionFrame& -EditorColorPickerUtilityWindowContentController::GetShellFrame() const { - return m_shellFrame; -} - -const UIEditorShellInteractionState& -EditorColorPickerUtilityWindowContentController::GetShellInteractionState() const { - return m_shellInteractionState; -} - -void EditorColorPickerUtilityWindowContentController::SetExternalDockHostDropPreview( - const Widgets::UIEditorDockHostDropPreviewState&) {} - -void EditorColorPickerUtilityWindowContentController::ClearExternalDockHostDropPreview() {} - -bool EditorColorPickerUtilityWindowContentController::TryResolveDockTabDragHotspot( - std::string_view, - std::string_view, - const ::XCEngine::UI::UIPoint&, - ::XCEngine::UI::UIPoint&) const { - return false; -} - -UIEditorDockHostTabDropTarget -EditorColorPickerUtilityWindowContentController::ResolveDockTabDropTarget( - const ::XCEngine::UI::UIPoint&) const { - return {}; -} - -bool EditorColorPickerUtilityWindowContentController::HasHostedContentCapture() const { - return false; -} - -bool EditorColorPickerUtilityWindowContentController::HasShellInteractiveCapture() const { - return false; -} - -bool EditorColorPickerUtilityWindowContentController::HasInteractiveCapture() const { - return false; -} - -EditorWindowContentCursorKind -EditorColorPickerUtilityWindowContentController::GetHostedContentCursorKind() const { - return EditorWindowContentCursorKind::Arrow; -} - -EditorWindowContentCursorKind -EditorColorPickerUtilityWindowContentController::GetDockCursorKind() const { - return EditorWindowContentCursorKind::Arrow; -} - -::XCEngine::UI::UISize -EditorColorPickerUtilityWindowContentController::ResolveMinimumOuterSize() const { - return m_minimumOuterSize; -} - -bool EditorColorPickerUtilityWindowContentController::ShouldUseDetachedTitleBarTabStrip() const { - return false; -} - -std::string EditorColorPickerUtilityWindowContentController::ResolveTabStripTitleText( - std::string_view fallbackTitle) const { - return std::string(fallbackTitle); -} - -std::string EditorColorPickerUtilityWindowContentController::ResolveDetachedWindowTitleText( - std::string_view fallbackWindowTitle) const { - return std::string(fallbackWindowTitle); -} - std::unique_ptr CreateEditorColorPickerUtilityWindowContentController( const ::XCEngine::UI::UISize& minimumOuterSize) { diff --git a/new_editor/app/Platform/Win32/EditorColorPickerUtilityWindowContentController.h b/new_editor/app/Platform/Win32/EditorColorPickerUtilityWindowContentController.h index 0334e097..3793666b 100644 --- a/new_editor/app/Platform/Win32/EditorColorPickerUtilityWindowContentController.h +++ b/new_editor/app/Platform/Win32/EditorColorPickerUtilityWindowContentController.h @@ -1,71 +1,27 @@ #pragma once #include "Features/ColorPicker/ColorPickerPanel.h" -#include "Platform/Win32/EditorWindowContentController.h" - -#include - -#include +#include "Platform/Win32/EditorStandaloneUtilityWindowContentController.h" #include namespace XCEngine::UI::Editor::App { class EditorColorPickerUtilityWindowContentController final - : public EditorWindowContentController { + : public EditorStandaloneUtilityWindowContentController { public: explicit EditorColorPickerUtilityWindowContentController( const ::XCEngine::UI::UISize& minimumOuterSize); ~EditorColorPickerUtilityWindowContentController() override; - const UIEditorWorkspaceController* TryGetWorkspaceController() const override; - UIEditorWorkspaceController* TryGetMutableWorkspaceController() override; - void ReplaceWorkspaceController(UIEditorWorkspaceController workspaceController) override; - - void Initialize(const EditorWindowContentInitializationContext& context) override; - void Shutdown() override; - void ResetInteractionState() override; - void SetViewportSurfacePresentationEnabled(bool enabled) override; - - EditorWindowFrameTransferRequests UpdateAndAppend( - const EditorWindowContentFrameContext& context, - ::XCEngine::UI::UIDrawData& drawData) override; - void RenderRequestedViewports( - const ::XCEngine::Rendering::RenderContext& renderContext) override; - - const UIEditorShellInteractionFrame& GetShellFrame() const override; - const UIEditorShellInteractionState& GetShellInteractionState() const override; - - void SetExternalDockHostDropPreview( - const Widgets::UIEditorDockHostDropPreviewState& preview) override; - void ClearExternalDockHostDropPreview() override; - - bool TryResolveDockTabDragHotspot( - std::string_view nodeId, - std::string_view panelId, - const ::XCEngine::UI::UIPoint& point, - ::XCEngine::UI::UIPoint& outHotspot) const override; - UIEditorDockHostTabDropTarget ResolveDockTabDropTarget( - const ::XCEngine::UI::UIPoint& point) const override; - - bool HasHostedContentCapture() const override; - bool HasShellInteractiveCapture() const override; - bool HasInteractiveCapture() const override; - EditorWindowContentCursorKind GetHostedContentCursorKind() const override; - EditorWindowContentCursorKind GetDockCursorKind() const override; - - ::XCEngine::UI::UISize ResolveMinimumOuterSize() const override; - bool ShouldUseDetachedTitleBarTabStrip() const override; - std::string ResolveTabStripTitleText(std::string_view fallbackTitle) const override; - std::string ResolveDetachedWindowTitleText( - std::string_view fallbackWindowTitle) const override; - private: - ::XCEngine::UI::UISize m_minimumOuterSize = {}; + void OnShutdown() override; + void OnResetInteractionState() override; + EditorWindowFrameTransferRequests UpdateStandaloneContent( + const EditorStandaloneUtilityWindowFrameContext& context, + ::XCEngine::UI::UIDrawData& drawData) override; + ColorPickerPanel m_colorPickerPanel = {}; - UIEditorShellInteractionState m_shellInteractionState = {}; - UIEditorShellInteractionFrame m_shellFrame = {}; - bool m_windowFocused = true; }; std::unique_ptr diff --git a/new_editor/app/Platform/Win32/EditorStandaloneUtilityWindowContentController.cpp b/new_editor/app/Platform/Win32/EditorStandaloneUtilityWindowContentController.cpp new file mode 100644 index 00000000..e61e0b48 --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorStandaloneUtilityWindowContentController.cpp @@ -0,0 +1,154 @@ +#include "Platform/Win32/EditorStandaloneUtilityWindowContentController.h" + +#include + +namespace XCEngine::UI::Editor::App { + +using ::XCEngine::UI::UIInputEvent; +using ::XCEngine::UI::UIInputEventType; + +EditorStandaloneUtilityWindowContentController:: + EditorStandaloneUtilityWindowContentController( + const ::XCEngine::UI::UISize& minimumOuterSize) + : m_minimumOuterSize(minimumOuterSize) {} + +EditorStandaloneUtilityWindowContentController:: + ~EditorStandaloneUtilityWindowContentController() = default; + +const UIEditorWorkspaceController* +EditorStandaloneUtilityWindowContentController::TryGetWorkspaceController() const { + return nullptr; +} + +UIEditorWorkspaceController* +EditorStandaloneUtilityWindowContentController::TryGetMutableWorkspaceController() { + return nullptr; +} + +void EditorStandaloneUtilityWindowContentController::ReplaceWorkspaceController( + UIEditorWorkspaceController) {} + +void EditorStandaloneUtilityWindowContentController::Initialize( + const EditorWindowContentInitializationContext&) {} + +void EditorStandaloneUtilityWindowContentController::Shutdown() { + OnShutdown(); + m_shellInteractionState = {}; + m_shellFrame = {}; + m_windowFocused = false; +} + +void EditorStandaloneUtilityWindowContentController::ResetInteractionState() { + OnResetInteractionState(); +} + +void EditorStandaloneUtilityWindowContentController::SetViewportSurfacePresentationEnabled(bool) {} + +EditorWindowFrameTransferRequests +EditorStandaloneUtilityWindowContentController::UpdateAndAppend( + const EditorWindowContentFrameContext& context, + ::XCEngine::UI::UIDrawData& drawData) { + bool focusGained = false; + bool focusLost = false; + for (const UIInputEvent& event : context.inputEvents) { + if (event.type == UIInputEventType::FocusGained) { + m_windowFocused = true; + focusGained = true; + } else if (event.type == UIInputEventType::FocusLost) { + m_windowFocused = false; + focusLost = true; + } + } + + m_shellInteractionState.focused = m_windowFocused; + m_shellFrame.focused = m_windowFocused; + return UpdateStandaloneContent( + EditorStandaloneUtilityWindowFrameContext{ + .editorContext = context.editorContext, + .bounds = context.bounds, + .inputEvents = context.inputEvents, + .focused = m_windowFocused, + .focusGained = focusGained, + .focusLost = focusLost, + }, + drawData); +} + +void EditorStandaloneUtilityWindowContentController::RenderRequestedViewports( + const ::XCEngine::Rendering::RenderContext&) {} + +const UIEditorShellInteractionFrame& +EditorStandaloneUtilityWindowContentController::GetShellFrame() const { + return m_shellFrame; +} + +const UIEditorShellInteractionState& +EditorStandaloneUtilityWindowContentController::GetShellInteractionState() const { + return m_shellInteractionState; +} + +void EditorStandaloneUtilityWindowContentController::SetExternalDockHostDropPreview( + const Widgets::UIEditorDockHostDropPreviewState&) {} + +void EditorStandaloneUtilityWindowContentController::ClearExternalDockHostDropPreview() {} + +bool EditorStandaloneUtilityWindowContentController::TryResolveDockTabDragHotspot( + std::string_view, + std::string_view, + const ::XCEngine::UI::UIPoint&, + ::XCEngine::UI::UIPoint&) const { + return false; +} + +UIEditorDockHostTabDropTarget +EditorStandaloneUtilityWindowContentController::ResolveDockTabDropTarget( + const ::XCEngine::UI::UIPoint&) const { + return {}; +} + +bool EditorStandaloneUtilityWindowContentController::HasHostedContentCapture() const { + return false; +} + +bool EditorStandaloneUtilityWindowContentController::HasShellInteractiveCapture() const { + return false; +} + +bool EditorStandaloneUtilityWindowContentController::HasInteractiveCapture() const { + return false; +} + +EditorWindowContentCursorKind +EditorStandaloneUtilityWindowContentController::GetHostedContentCursorKind() const { + return EditorWindowContentCursorKind::Arrow; +} + +EditorWindowContentCursorKind +EditorStandaloneUtilityWindowContentController::GetDockCursorKind() const { + return EditorWindowContentCursorKind::Arrow; +} + +::XCEngine::UI::UISize +EditorStandaloneUtilityWindowContentController::ResolveMinimumOuterSize() const { + return m_minimumOuterSize; +} + +bool EditorStandaloneUtilityWindowContentController::ShouldUseDetachedTitleBarTabStrip() const { + return false; +} + +std::string EditorStandaloneUtilityWindowContentController::ResolveTabStripTitleText( + std::string_view fallbackTitle) const { + return std::string(fallbackTitle); +} + +std::string EditorStandaloneUtilityWindowContentController::ResolveDetachedWindowTitleText( + std::string_view fallbackWindowTitle) const { + return std::string(fallbackWindowTitle); +} + +void EditorStandaloneUtilityWindowContentController::OnShutdown() {} + +void EditorStandaloneUtilityWindowContentController::OnResetInteractionState() {} + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorStandaloneUtilityWindowContentController.h b/new_editor/app/Platform/Win32/EditorStandaloneUtilityWindowContentController.h new file mode 100644 index 00000000..65ec1339 --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorStandaloneUtilityWindowContentController.h @@ -0,0 +1,85 @@ +#pragma once + +#include "Platform/Win32/EditorWindowContentController.h" + +#include + +#include + +#include + +namespace XCEngine::UI::Editor::App { + +struct EditorStandaloneUtilityWindowFrameContext { + EditorContext& editorContext; + const ::XCEngine::UI::UIRect& bounds; + const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents; + bool focused = false; + bool focusGained = false; + bool focusLost = false; +}; + +class EditorStandaloneUtilityWindowContentController + : public EditorWindowContentController { +public: + explicit EditorStandaloneUtilityWindowContentController( + const ::XCEngine::UI::UISize& minimumOuterSize); + ~EditorStandaloneUtilityWindowContentController() override; + + const UIEditorWorkspaceController* TryGetWorkspaceController() const override; + UIEditorWorkspaceController* TryGetMutableWorkspaceController() override; + void ReplaceWorkspaceController(UIEditorWorkspaceController workspaceController) override; + + void Initialize(const EditorWindowContentInitializationContext& context) override; + void Shutdown() override; + void ResetInteractionState() override; + void SetViewportSurfacePresentationEnabled(bool enabled) override; + + EditorWindowFrameTransferRequests UpdateAndAppend( + const EditorWindowContentFrameContext& context, + ::XCEngine::UI::UIDrawData& drawData) override; + void RenderRequestedViewports( + const ::XCEngine::Rendering::RenderContext& renderContext) override; + + const UIEditorShellInteractionFrame& GetShellFrame() const override; + const UIEditorShellInteractionState& GetShellInteractionState() const override; + + void SetExternalDockHostDropPreview( + const Widgets::UIEditorDockHostDropPreviewState& preview) override; + void ClearExternalDockHostDropPreview() override; + + bool TryResolveDockTabDragHotspot( + std::string_view nodeId, + std::string_view panelId, + const ::XCEngine::UI::UIPoint& point, + ::XCEngine::UI::UIPoint& outHotspot) const override; + UIEditorDockHostTabDropTarget ResolveDockTabDropTarget( + const ::XCEngine::UI::UIPoint& point) const override; + + bool HasHostedContentCapture() const override; + bool HasShellInteractiveCapture() const override; + bool HasInteractiveCapture() const override; + EditorWindowContentCursorKind GetHostedContentCursorKind() const override; + EditorWindowContentCursorKind GetDockCursorKind() const override; + + ::XCEngine::UI::UISize ResolveMinimumOuterSize() const override; + bool ShouldUseDetachedTitleBarTabStrip() const override; + std::string ResolveTabStripTitleText(std::string_view fallbackTitle) const override; + std::string ResolveDetachedWindowTitleText( + std::string_view fallbackWindowTitle) const override; + +protected: + virtual void OnShutdown(); + virtual void OnResetInteractionState(); + virtual EditorWindowFrameTransferRequests UpdateStandaloneContent( + const EditorStandaloneUtilityWindowFrameContext& context, + ::XCEngine::UI::UIDrawData& drawData) = 0; + +private: + ::XCEngine::UI::UISize m_minimumOuterSize = {}; + UIEditorShellInteractionState m_shellInteractionState = {}; + UIEditorShellInteractionFrame m_shellFrame = {}; + bool m_windowFocused = true; +}; + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorUtilityWindowKind.h b/new_editor/app/Platform/Win32/EditorUtilityWindowKind.h index a94033c8..cb8e3542 100644 --- a/new_editor/app/Platform/Win32/EditorUtilityWindowKind.h +++ b/new_editor/app/Platform/Win32/EditorUtilityWindowKind.h @@ -7,6 +7,7 @@ namespace XCEngine::UI::Editor::App { enum class EditorUtilityWindowKind : std::uint8_t { None = 0, ColorPicker, + AddComponent, }; } // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorUtilityWindowRegistry.cpp b/new_editor/app/Platform/Win32/EditorUtilityWindowRegistry.cpp index d557eb65..3f92c2e5 100644 --- a/new_editor/app/Platform/Win32/EditorUtilityWindowRegistry.cpp +++ b/new_editor/app/Platform/Win32/EditorUtilityWindowRegistry.cpp @@ -1,5 +1,6 @@ #include "Platform/Win32/EditorUtilityWindowRegistry.h" +#include "Platform/Win32/EditorAddComponentUtilityWindowContentController.h" #include "Platform/Win32/EditorColorPickerUtilityWindowContentController.h" namespace XCEngine::UI::Editor::App { @@ -16,6 +17,14 @@ constexpr EditorUtilityWindowDescriptor kColorPickerUtilityWindowDescriptor = { .minimumOuterSize = UISize(320.0f, 460.0f), }; +constexpr EditorUtilityWindowDescriptor kAddComponentUtilityWindowDescriptor = { + .kind = EditorUtilityWindowKind::AddComponent, + .windowId = "utility.add-component", + .title = L"Add Component", + .preferredOuterSize = UISize(352.0f, 500.0f), + .minimumOuterSize = UISize(320.0f, 460.0f), +}; + } const EditorUtilityWindowDescriptor* ResolveEditorUtilityWindowDescriptor( @@ -23,6 +32,8 @@ const EditorUtilityWindowDescriptor* ResolveEditorUtilityWindowDescriptor( switch (kind) { case EditorUtilityWindowKind::ColorPicker: return &kColorPickerUtilityWindowDescriptor; + case EditorUtilityWindowKind::AddComponent: + return &kAddComponentUtilityWindowDescriptor; case EditorUtilityWindowKind::None: default: return nullptr; @@ -41,6 +52,9 @@ std::unique_ptr CreateEditorUtilityWindowContentC case EditorUtilityWindowKind::ColorPicker: return CreateEditorColorPickerUtilityWindowContentController( descriptor->minimumOuterSize); + case EditorUtilityWindowKind::AddComponent: + return CreateEditorAddComponentUtilityWindowContentController( + descriptor->minimumOuterSize); case EditorUtilityWindowKind::None: default: return nullptr; diff --git a/new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp b/new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp index 6f9d495e..c03af281 100644 --- a/new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp @@ -80,10 +80,11 @@ EditorWindowFrameTransferRequests EditorWindowFrameOrchestrator::UpdateAndAppend EditorWindowFrameTransferRequests transferRequests = BuildShellTransferRequests(globalTabDragActive, dockHostInteractionState, shellFrame); POINT screenPoint = {}; - if (ConsumeEditorColorPickerOpenUtilityWindowRequest( - editorContext.GetColorPickerToolState())) { + if (const std::optional requestedKind = + editorContext.ConsumeOpenUtilityWindowRequest(); + requestedKind.has_value()) { transferRequests.openUtilityWindow = EditorWindowOpenUtilityWindowRequest{ - .kind = EditorUtilityWindowKind::ColorPicker, + .kind = *requestedKind, .useCursorPlacement = GetCursorPos(&screenPoint) != FALSE, }; if (transferRequests.openUtilityWindow->useCursorPlacement) { diff --git a/new_editor/app/Scene/EditorSceneRuntime.cpp b/new_editor/app/Scene/EditorSceneRuntime.cpp index 7c9815d9..f6085b74 100644 --- a/new_editor/app/Scene/EditorSceneRuntime.cpp +++ b/new_editor/app/Scene/EditorSceneRuntime.cpp @@ -1,5 +1,6 @@ #include "Scene/EditorSceneRuntime.h" +#include #include #include #include @@ -472,6 +473,36 @@ bool EditorSceneRuntime::MoveGameObjectToRoot(std::string_view itemId) { return moved; } +bool EditorSceneRuntime::AddComponentToSelectedGameObject( + std::string_view componentTypeName) { + if (componentTypeName.empty()) { + return false; + } + + const std::optional selectedId = GetSelectedGameObjectId(); + Scene* scene = GetActiveScene(); + if (!selectedId.has_value() || scene == nullptr) { + return false; + } + + GameObject* gameObject = scene->FindByID(selectedId.value()); + if (gameObject == nullptr) { + return false; + } + + Component* addedComponent = + ::XCEngine::Components::ComponentFactoryRegistry::Get().CreateComponent( + gameObject, + std::string(componentTypeName)); + if (addedComponent == nullptr) { + return false; + } + + IncrementInspectorRevision(); + RefreshScene(); + return true; +} + bool EditorSceneRuntime::CanRemoveSelectedComponent( std::string_view componentId) const { const EditorSceneComponentDescriptor descriptor = diff --git a/new_editor/app/Scene/EditorSceneRuntime.h b/new_editor/app/Scene/EditorSceneRuntime.h index 7441bc5c..ddd613ed 100644 --- a/new_editor/app/Scene/EditorSceneRuntime.h +++ b/new_editor/app/Scene/EditorSceneRuntime.h @@ -94,6 +94,7 @@ public: std::string_view itemId, std::string_view parentItemId); bool MoveGameObjectToRoot(std::string_view itemId); + bool AddComponentToSelectedGameObject(std::string_view componentTypeName); bool CanRemoveSelectedComponent(std::string_view componentId) const; bool RemoveSelectedComponent(std::string_view componentId); bool SetSelectedTransformLocalPosition( diff --git a/new_editor/app/State/EditorColorPickerToolState.cpp b/new_editor/app/State/EditorColorPickerToolState.cpp index 03189bf0..3f093623 100644 --- a/new_editor/app/State/EditorColorPickerToolState.cpp +++ b/new_editor/app/State/EditorColorPickerToolState.cpp @@ -29,20 +29,12 @@ void OpenEditorColorPickerToolForInspectorField( bool showAlpha) { state.active = true; state.showAlpha = showAlpha; - state.requestOpenUtilityWindow = true; state.color = color; state.inspectorTarget.subjectKey = std::string(subjectKey); state.inspectorTarget.fieldId = std::string(fieldId); ++state.revision; } -bool ConsumeEditorColorPickerOpenUtilityWindowRequest( - EditorColorPickerToolState& state) { - const bool requested = state.requestOpenUtilityWindow; - state.requestOpenUtilityWindow = false; - return requested; -} - bool UpdateEditorColorPickerToolColor( EditorColorPickerToolState& state, const ::XCEngine::UI::UIColor& color) { diff --git a/new_editor/app/State/EditorColorPickerToolState.h b/new_editor/app/State/EditorColorPickerToolState.h index 9f73d84f..b9160786 100644 --- a/new_editor/app/State/EditorColorPickerToolState.h +++ b/new_editor/app/State/EditorColorPickerToolState.h @@ -16,7 +16,6 @@ struct EditorColorPickerInspectorTarget { struct EditorColorPickerToolState { bool active = false; bool showAlpha = true; - bool requestOpenUtilityWindow = false; ::XCEngine::UI::UIColor color = {}; EditorColorPickerInspectorTarget inspectorTarget = {}; std::uint64_t revision = 0u; @@ -29,8 +28,6 @@ void OpenEditorColorPickerToolForInspectorField( std::string_view fieldId, const ::XCEngine::UI::UIColor& color, bool showAlpha); -bool ConsumeEditorColorPickerOpenUtilityWindowRequest( - EditorColorPickerToolState& state); bool UpdateEditorColorPickerToolColor( EditorColorPickerToolState& state, const ::XCEngine::UI::UIColor& color); diff --git a/new_editor/app/State/EditorUtilityWindowRequestState.cpp b/new_editor/app/State/EditorUtilityWindowRequestState.cpp new file mode 100644 index 00000000..23dc9d7b --- /dev/null +++ b/new_editor/app/State/EditorUtilityWindowRequestState.cpp @@ -0,0 +1,26 @@ +#include "State/EditorUtilityWindowRequestState.h" + +namespace XCEngine::UI::Editor::App { + +void ResetEditorUtilityWindowRequestState(EditorUtilityWindowRequestState& state) { + state = {}; +} + +void RequestEditorUtilityWindow( + EditorUtilityWindowRequestState& state, + EditorUtilityWindowKind kind) { + if (kind == EditorUtilityWindowKind::None) { + return; + } + + state.pendingKind = kind; +} + +std::optional ConsumeEditorUtilityWindowRequest( + EditorUtilityWindowRequestState& state) { + const std::optional requestedKind = state.pendingKind; + state.pendingKind.reset(); + return requestedKind; +} + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/State/EditorUtilityWindowRequestState.h b/new_editor/app/State/EditorUtilityWindowRequestState.h new file mode 100644 index 00000000..a55398af --- /dev/null +++ b/new_editor/app/State/EditorUtilityWindowRequestState.h @@ -0,0 +1,20 @@ +#pragma once + +#include "Platform/Win32/EditorUtilityWindowKind.h" + +#include + +namespace XCEngine::UI::Editor::App { + +struct EditorUtilityWindowRequestState { + std::optional pendingKind = {}; +}; + +void ResetEditorUtilityWindowRequestState(EditorUtilityWindowRequestState& state); +void RequestEditorUtilityWindow( + EditorUtilityWindowRequestState& state, + EditorUtilityWindowKind kind); +std::optional ConsumeEditorUtilityWindowRequest( + EditorUtilityWindowRequestState& state); + +} // namespace XCEngine::UI::Editor::App