Files
XCEngine/new_editor/app/Features/Inspector/AddComponentPanel.cpp

305 lines
9.6 KiB
C++

#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