feat(new_editor): add standalone add-component utility window
This commit is contained in:
304
new_editor/app/Features/Inspector/AddComponentPanel.cpp
Normal file
304
new_editor/app/Features/Inspector/AddComponentPanel.cpp
Normal 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
|
||||
Reference in New Issue
Block a user