feat(new_editor): add standalone add-component utility window

This commit is contained in:
2026-04-22 22:07:02 +08:00
parent 865a35e4d0
commit 3048c7cc90
37 changed files with 1237 additions and 290 deletions

View File

@@ -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
$<TARGET_FILE_DIR:XCUIEditorApp>/XCUIEditorAppCore.lib
$<TARGET_FILE_DIR:XCUIEditorApp>/XCUIEditorAppLib.lib
$<TARGET_FILE_DIR:XCUIEditorApp>/XCUIEditorHost.lib
)
if(WIN32 AND XCENGINE_ENABLE_PHYSX)
xcengine_copy_physx_runtime_dlls(XCUIEditorApp)
endif()

View File

@@ -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<EditorUtilityWindowKind> EditorContext::ConsumeOpenUtilityWindowRequest() {
return ConsumeEditorUtilityWindowRequest(m_utilityWindowRequestState);
}
void EditorContext::SetSelection(EditorSelectionState selection) {
m_selectionService.SetSelection(std::move(selection));
SyncSessionFromSelectionService();

View File

@@ -9,6 +9,7 @@
#include "State/EditorCommandFocusService.h"
#include "State/EditorSelectionService.h"
#include "State/EditorSession.h"
#include "State/EditorUtilityWindowRequestState.h"
#include <XCEditor/Foundation/UIEditorShortcutManager.h>
#include <XCEditor/Shell/UIEditorShellAsset.h>
#include <XCEditor/Shell/UIEditorShellInteraction.h>
@@ -16,6 +17,7 @@
#include <filesystem>
#include <functional>
#include <optional>
#include <string>
#include <string_view>
@@ -52,6 +54,8 @@ public:
const EditorSceneRuntime& GetSceneRuntime() const;
EditorColorPickerToolState& GetColorPickerToolState();
const EditorColorPickerToolState& GetColorPickerToolState() const;
void RequestOpenUtilityWindow(EditorUtilityWindowKind kind);
std::optional<EditorUtilityWindowKind> 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 = {};

View File

@@ -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 <XCEditor/Fields/UIEditorFieldStyle.h>
#include <algorithm>
#include <cmath>
#include <string_view>
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<std::size_t>(-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<UIInputEvent>& 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

View File

@@ -0,0 +1,57 @@
#pragma once
#include <XCEngine/UI/DrawData.h>
#include <cstddef>
#include <string>
#include <vector>
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<EntryPresentation> m_entries = {};
std::size_t m_hoveredEntryIndex = static_cast<std::size_t>(-1);
std::size_t m_pressedEntryIndex = static_cast<std::size_t>(-1);
};
} // namespace XCEngine::UI::Editor::App

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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 {

View File

@@ -2,6 +2,8 @@
#include <XCEditor/Fields/UIEditorPropertyGrid.h>
#include <XCEngine/Components/ColliderComponent.h>
#include <XCEngine/Components/GameObject.h>
#include <XCEngine/Core/Math/Color.h>
#include <XCEngine/Core/Math/Vector3.h>
@@ -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 <typename TComponent>
bool CanAddUniqueInspectorComponentToGameObject(
const ::XCEngine::Components::GameObject* gameObject) {
return gameObject != nullptr &&
gameObject->GetComponent<TComponent>() == nullptr;
}
template <typename TComponent>
std::string_view GetUniqueInspectorComponentAddDisabledReason(
const ::XCEngine::Components::GameObject* gameObject) {
if (gameObject == nullptr) {
return "Invalid";
}
return gameObject->GetComponent<TComponent>() != 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 <typename TComponent>
const TComponent* ResolveInspectorComponent(
const InspectorComponentEditorContext& context) {

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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<InspectorSectionBinding>& outSections) const {

View File

@@ -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;

View File

@@ -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,

View File

@@ -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<float>(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<UIInputEvent>& 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<UIInputEvent> 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(

View File

@@ -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

View File

@@ -0,0 +1,51 @@
#include "Platform/Win32/EditorAddComponentUtilityWindowContentController.h"
#include <XCEngine/UI/DrawData.h>
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<EditorWindowContentController>
CreateEditorAddComponentUtilityWindowContentController(
const ::XCEngine::UI::UISize& minimumOuterSize) {
return std::make_unique<EditorAddComponentUtilityWindowContentController>(
minimumOuterSize);
}
} // namespace XCEngine::UI::Editor::App

View File

@@ -0,0 +1,31 @@
#pragma once
#include "Features/Inspector/AddComponentPanel.h"
#include "Platform/Win32/EditorStandaloneUtilityWindowContentController.h"
#include <memory>
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<EditorWindowContentController>
CreateEditorAddComponentUtilityWindowContentController(
const ::XCEngine::UI::UISize& minimumOuterSize);
} // namespace XCEngine::UI::Editor::App

View File

@@ -1,78 +1,38 @@
#include "Platform/Win32/EditorColorPickerUtilityWindowContentController.h"
#include <XCEditor/Docking/UIEditorDockHostTransfer.h>
#include <XCEngine/UI/DrawData.h>
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<EditorWindowContentController>
CreateEditorColorPickerUtilityWindowContentController(
const ::XCEngine::UI::UISize& minimumOuterSize) {

View File

@@ -1,71 +1,27 @@
#pragma once
#include "Features/ColorPicker/ColorPickerPanel.h"
#include "Platform/Win32/EditorWindowContentController.h"
#include <XCEditor/Shell/UIEditorShellInteraction.h>
#include <XCEngine/UI/Types.h>
#include "Platform/Win32/EditorStandaloneUtilityWindowContentController.h"
#include <memory>
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<EditorWindowContentController>

View File

@@ -0,0 +1,154 @@
#include "Platform/Win32/EditorStandaloneUtilityWindowContentController.h"
#include <XCEditor/Docking/UIEditorDockHostTransfer.h>
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

View File

@@ -0,0 +1,85 @@
#pragma once
#include "Platform/Win32/EditorWindowContentController.h"
#include <XCEditor/Shell/UIEditorShellInteraction.h>
#include <XCEngine/UI/Types.h>
#include <memory>
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

View File

@@ -7,6 +7,7 @@ namespace XCEngine::UI::Editor::App {
enum class EditorUtilityWindowKind : std::uint8_t {
None = 0,
ColorPicker,
AddComponent,
};
} // namespace XCEngine::UI::Editor::App

View File

@@ -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<EditorWindowContentController> CreateEditorUtilityWindowContentC
case EditorUtilityWindowKind::ColorPicker:
return CreateEditorColorPickerUtilityWindowContentController(
descriptor->minimumOuterSize);
case EditorUtilityWindowKind::AddComponent:
return CreateEditorAddComponentUtilityWindowContentController(
descriptor->minimumOuterSize);
case EditorUtilityWindowKind::None:
default:
return nullptr;

View File

@@ -80,10 +80,11 @@ EditorWindowFrameTransferRequests EditorWindowFrameOrchestrator::UpdateAndAppend
EditorWindowFrameTransferRequests transferRequests =
BuildShellTransferRequests(globalTabDragActive, dockHostInteractionState, shellFrame);
POINT screenPoint = {};
if (ConsumeEditorColorPickerOpenUtilityWindowRequest(
editorContext.GetColorPickerToolState())) {
if (const std::optional<EditorUtilityWindowKind> requestedKind =
editorContext.ConsumeOpenUtilityWindowRequest();
requestedKind.has_value()) {
transferRequests.openUtilityWindow = EditorWindowOpenUtilityWindowRequest{
.kind = EditorUtilityWindowKind::ColorPicker,
.kind = *requestedKind,
.useCursorPlacement = GetCursorPos(&screenPoint) != FALSE,
};
if (transferRequests.openUtilityWindow->useCursorPlacement) {

View File

@@ -1,5 +1,6 @@
#include "Scene/EditorSceneRuntime.h"
#include <XCEngine/Components/ComponentFactoryRegistry.h>
#include <XCEngine/Components/CameraComponent.h>
#include <XCEngine/Components/Component.h>
#include <XCEngine/Components/GameObject.h>
@@ -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<GameObject::ID> 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 =

View File

@@ -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(

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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<EditorUtilityWindowKind> ConsumeEditorUtilityWindowRequest(
EditorUtilityWindowRequestState& state) {
const std::optional<EditorUtilityWindowKind> requestedKind = state.pendingKind;
state.pendingKind.reset();
return requestedKind;
}
} // namespace XCEngine::UI::Editor::App

View File

@@ -0,0 +1,20 @@
#pragma once
#include "Platform/Win32/EditorUtilityWindowKind.h"
#include <optional>
namespace XCEngine::UI::Editor::App {
struct EditorUtilityWindowRequestState {
std::optional<EditorUtilityWindowKind> pendingKind = {};
};
void ResetEditorUtilityWindowRequestState(EditorUtilityWindowRequestState& state);
void RequestEditorUtilityWindow(
EditorUtilityWindowRequestState& state,
EditorUtilityWindowKind kind);
std::optional<EditorUtilityWindowKind> ConsumeEditorUtilityWindowRequest(
EditorUtilityWindowRequestState& state);
} // namespace XCEngine::UI::Editor::App