Files
XCEngine/editor/app/Services/Scene/EditorSceneRuntime.cpp

816 lines
23 KiB
C++
Raw Normal View History

#include "Scene/EditorSceneRuntime.h"
2026-04-29 04:05:54 +08:00
#include <XCEngine/Scene/Scene.h>
#include <cmath>
namespace XCEngine::UI::Editor::App {
namespace {
using ::XCEngine::Math::Quaternion;
using ::XCEngine::Math::Vector3;
bool NearlyEqual(float lhs, float rhs, float epsilon = 0.0001f) {
return std::abs(lhs - rhs) <= epsilon;
}
bool NearlyEqual(const Vector3& lhs, const Vector3& rhs, float epsilon = 0.0001f) {
return NearlyEqual(lhs.x, rhs.x, epsilon) &&
NearlyEqual(lhs.y, rhs.y, epsilon) &&
NearlyEqual(lhs.z, rhs.z, epsilon);
}
bool NearlyEqual(const Quaternion& lhs, const Quaternion& rhs, float epsilon = 0.0001f) {
return std::abs(lhs.Dot(rhs)) >= 1.0f - epsilon;
}
bool TransformSnapshotsMatch(
const SceneTransformSnapshot& lhs,
const SceneTransformSnapshot& rhs) {
return lhs.IsValid() &&
rhs.IsValid() &&
lhs.targetId == rhs.targetId &&
NearlyEqual(lhs.position, rhs.position) &&
NearlyEqual(lhs.rotation, rhs.rotation) &&
NearlyEqual(lhs.scale, rhs.scale);
}
} // namespace
std::string_view GetSceneToolModeName(SceneToolMode mode) {
switch (mode) {
case SceneToolMode::View:
return "View";
case SceneToolMode::Translate:
2026-04-18 23:56:17 +08:00
return "Move";
case SceneToolMode::Rotate:
return "Rotate";
case SceneToolMode::Scale:
return "Scale";
2026-04-18 23:56:17 +08:00
case SceneToolMode::Transform:
return "Transform";
default:
return "Unknown";
}
}
std::string_view GetSceneToolSpaceModeName(SceneToolSpaceMode mode) {
switch (mode) {
case SceneToolSpaceMode::World:
return "World";
case SceneToolSpaceMode::Local:
return "Local";
default:
return "Unknown";
}
}
std::string_view GetSceneToolPivotModeName(SceneToolPivotMode mode) {
switch (mode) {
case SceneToolPivotMode::Pivot:
return "Pivot";
case SceneToolPivotMode::Center:
return "Center";
default:
return "Unknown";
}
}
std::string_view GetSceneToolHandleName(SceneToolHandle handle) {
switch (handle) {
case SceneToolHandle::AxisX:
return "AxisX";
case SceneToolHandle::AxisY:
return "AxisY";
case SceneToolHandle::AxisZ:
return "AxisZ";
case SceneToolHandle::None:
default:
return "None";
}
}
std::string_view GetSceneToolInteractionLockName(SceneToolInteractionLock lock) {
switch (lock) {
case SceneToolInteractionLock::TransformDrag:
return "TransformDrag";
case SceneToolInteractionLock::None:
default:
return "None";
}
}
void EditorSceneRuntime::Reset() {
m_projectRoot.clear();
m_ownedSelectionService.ClearSelection();
m_selectionService = &m_ownedSelectionService;
ResetTransformEditHistory();
m_inspectorRevision = 0u;
2026-04-29 01:24:21 +08:00
m_sceneContentRevision = 0u;
}
void EditorSceneRuntime::SetBackend(std::unique_ptr<EditorSceneBackend> backend) {
m_backend = std::move(backend);
Reset();
}
EditorStartupSceneResult EditorSceneRuntime::Initialize(
const std::filesystem::path& projectRoot) {
Reset();
if (m_backend == nullptr) {
return {};
}
m_projectRoot = projectRoot;
const EditorStartupSceneResult startupScene =
m_backend->EnsureStartupScene(projectRoot);
RefreshScene();
return startupScene;
}
void EditorSceneRuntime::BindSelectionService(
EditorSelectionService* selectionService) {
m_selectionService =
selectionService != nullptr ? selectionService : &m_ownedSelectionService;
}
void EditorSceneRuntime::RefreshScene() {
if (HasHierarchySelection()) {
if (!HasValidSelection()) {
ClearSelection();
} else {
RevalidateSelection();
}
}
}
void EditorSceneRuntime::EnsureSceneSelection() {
if (HasValidSelection() ||
SelectionService().HasSelectionKind(EditorSelectionKind::ProjectItem)) {
return;
}
SelectFirstAvailableGameObject();
}
2026-04-28 17:53:36 +08:00
EditorSceneHierarchySnapshot EditorSceneRuntime::BuildHierarchySnapshot() const {
return m_backend != nullptr
? m_backend->BuildHierarchySnapshot()
: EditorSceneHierarchySnapshot{};
}
2026-04-29 01:24:21 +08:00
std::vector<EditorSceneViewportIconSnapshot>
EditorSceneRuntime::BuildSceneViewportIconSnapshots() const {
return m_backend != nullptr
? m_backend->BuildViewportIconSnapshots()
: std::vector<EditorSceneViewportIconSnapshot>{};
}
std::optional<EditorSceneViewportSelectionSnapshot>
EditorSceneRuntime::BuildSceneViewportSelectionSnapshot() const {
const std::optional<EditorSceneObjectId> selectedId = GetSelectedObjectId();
if (!selectedId.has_value() || m_backend == nullptr) {
return std::nullopt;
}
2026-04-29 01:24:21 +08:00
return m_backend->BuildViewportSelectionSnapshot(selectedId.value());
}
std::vector<EditorSceneViewportHelperSnapshot>
EditorSceneRuntime::BuildSelectedViewportHelperSnapshots() const {
const std::optional<EditorSceneObjectId> selectedId = GetSelectedObjectId();
if (!selectedId.has_value() || m_backend == nullptr) {
return {};
}
return m_backend->BuildViewportHelperSnapshots(selectedId.value());
}
bool EditorSceneRuntime::HasSceneSelection() const {
return HasValidSelection();
}
2026-04-29 01:24:21 +08:00
std::optional<EditorSceneObjectId> EditorSceneRuntime::GetSelectedObjectId() const {
if (!HasHierarchySelection()) {
return std::nullopt;
}
return ParseEditorGameObjectItemId(SelectionService().GetSelection().itemId);
}
std::string EditorSceneRuntime::GetSelectedItemId() const {
2026-04-29 01:24:21 +08:00
const std::optional<EditorSceneObjectId> selectedId = GetSelectedObjectId();
return selectedId.has_value()
? MakeEditorGameObjectItemId(selectedId.value())
: std::string();
}
std::string EditorSceneRuntime::GetSelectedDisplayName() const {
2026-04-29 01:24:21 +08:00
const std::optional<EditorSceneObjectSnapshot> snapshot =
GetSelectedObjectSnapshot();
return snapshot.has_value()
? snapshot->displayName
: std::string();
}
2026-04-29 01:24:21 +08:00
std::optional<EditorSceneObjectSnapshot>
EditorSceneRuntime::GetSelectedObjectSnapshot() const {
const std::string itemId = GetSelectedItemId();
return itemId.empty()
? std::nullopt
: GetObjectSnapshot(itemId);
}
std::vector<EditorSceneComponentDescriptor> EditorSceneRuntime::GetSelectedComponents() const {
const std::string selectedItemId = GetSelectedItemId();
return m_backend != nullptr && !selectedItemId.empty()
? m_backend->GetComponents(selectedItemId)
: std::vector<EditorSceneComponentDescriptor>{};
}
std::uint64_t EditorSceneRuntime::GetSelectionStamp() const {
return SelectionService().GetStamp();
}
std::uint64_t EditorSceneRuntime::GetInspectorRevision() const {
return m_inspectorRevision;
}
2026-04-29 04:05:54 +08:00
std::uint64_t EditorSceneRuntime::GetSceneContentRevision() const {
return m_sceneContentRevision;
}
bool EditorSceneRuntime::SetSelection(std::string_view itemId) {
2026-04-29 01:24:21 +08:00
const std::optional<EditorSceneObjectId> gameObjectId =
ParseEditorGameObjectItemId(itemId);
if (!gameObjectId.has_value()) {
return false;
}
return SetSelection(gameObjectId.value());
}
2026-04-29 01:24:21 +08:00
bool EditorSceneRuntime::SetSelection(EditorSceneObjectId id) {
if (id == kInvalidEditorSceneObjectId) {
return false;
}
2026-04-29 01:24:21 +08:00
const std::optional<EditorSceneObjectSnapshot> snapshot =
GetObjectSnapshot(MakeEditorGameObjectItemId(id));
if (!snapshot.has_value()) {
return false;
}
2026-04-29 01:24:21 +08:00
const std::optional<EditorSceneObjectId> previousId = GetSelectedObjectId();
const bool changed =
!previousId.has_value() ||
previousId.value() != id ||
!HasHierarchySelection();
SelectionService().SetHierarchySelection(
MakeEditorGameObjectItemId(id),
2026-04-29 01:24:21 +08:00
snapshot->displayName);
return changed;
}
void EditorSceneRuntime::ClearSelection() {
SelectionService().ClearSelection();
}
2026-04-29 04:05:54 +08:00
bool EditorSceneRuntime::NewScene(std::string_view sceneName) {
if (m_backend == nullptr || !m_backend->NewScene(sceneName)) {
return false;
}
ResetTransformEditHistory();
SelectionService().ClearSelection();
IncrementInspectorRevision();
IncrementSceneContentRevision();
RefreshScene();
EnsureSceneSelection();
return true;
}
bool EditorSceneRuntime::OpenSceneAsset(const std::filesystem::path& scenePath) {
if (m_backend == nullptr || !m_backend->OpenSceneAsset(scenePath)) {
return false;
}
ResetTransformEditHistory();
SelectionService().ClearSelection();
IncrementInspectorRevision();
2026-04-29 01:24:21 +08:00
IncrementSceneContentRevision();
RefreshScene();
EnsureSceneSelection();
return true;
}
2026-04-29 04:05:54 +08:00
bool EditorSceneRuntime::SaveScene(const std::filesystem::path& scenePath) {
return m_backend != nullptr && m_backend->SaveActiveScene(scenePath);
}
::XCEngine::Components::Scene* EditorSceneRuntime::GetActiveScene() const {
return m_backend != nullptr ? m_backend->GetActiveScene() : nullptr;
}
std::string EditorSceneRuntime::GetActiveSceneName() const {
if (const ::XCEngine::Components::Scene* activeScene = GetActiveScene();
activeScene != nullptr) {
return activeScene->GetName();
}
return {};
}
2026-04-29 04:33:30 +08:00
std::unique_ptr<EditorScenePlaySession> EditorSceneRuntime::BeginPlaySession() {
return m_backend != nullptr
? m_backend->BeginPlaySession()
: nullptr;
}
bool EditorSceneRuntime::RenameGameObject(
std::string_view itemId,
std::string_view newName) {
const bool renamed =
m_backend != nullptr &&
m_backend->RenameGameObject(itemId, newName);
if (renamed) {
IncrementInspectorRevision();
2026-04-29 01:24:21 +08:00
IncrementSceneContentRevision();
}
RefreshScene();
return renamed;
}
bool EditorSceneRuntime::DeleteGameObject(std::string_view itemId) {
ResetTransformEditHistory();
const bool deleted =
m_backend != nullptr &&
m_backend->DeleteGameObject(itemId);
if (deleted) {
IncrementInspectorRevision();
2026-04-29 01:24:21 +08:00
IncrementSceneContentRevision();
}
RefreshScene();
EnsureSceneSelection();
return deleted;
}
std::string EditorSceneRuntime::DuplicateGameObject(std::string_view itemId) {
ResetTransformEditHistory();
const std::string duplicatedItemId =
m_backend != nullptr
? m_backend->DuplicateGameObject(itemId)
: std::string();
if (!duplicatedItemId.empty()) {
IncrementInspectorRevision();
2026-04-29 01:24:21 +08:00
IncrementSceneContentRevision();
SetSelection(duplicatedItemId);
} else {
RefreshScene();
}
return duplicatedItemId;
}
bool EditorSceneRuntime::ReparentGameObject(
std::string_view itemId,
std::string_view parentItemId) {
ResetTransformEditHistory();
const bool reparented =
m_backend != nullptr &&
m_backend->ReparentGameObject(itemId, parentItemId);
if (reparented) {
IncrementInspectorRevision();
2026-04-29 01:24:21 +08:00
IncrementSceneContentRevision();
}
RefreshScene();
return reparented;
}
bool EditorSceneRuntime::MoveGameObjectBefore(
std::string_view itemId,
std::string_view targetItemId) {
ResetTransformEditHistory();
const bool moved =
m_backend != nullptr &&
m_backend->MoveGameObjectBefore(itemId, targetItemId);
if (moved) {
IncrementInspectorRevision();
2026-04-29 01:24:21 +08:00
IncrementSceneContentRevision();
}
RefreshScene();
return moved;
}
bool EditorSceneRuntime::MoveGameObjectAfter(
std::string_view itemId,
std::string_view targetItemId) {
ResetTransformEditHistory();
const bool moved =
m_backend != nullptr &&
m_backend->MoveGameObjectAfter(itemId, targetItemId);
if (moved) {
IncrementInspectorRevision();
2026-04-29 01:24:21 +08:00
IncrementSceneContentRevision();
}
RefreshScene();
return moved;
}
bool EditorSceneRuntime::MoveGameObjectToRoot(std::string_view itemId) {
ResetTransformEditHistory();
const bool moved =
m_backend != nullptr &&
m_backend->MoveGameObjectToRoot(itemId);
if (moved) {
IncrementInspectorRevision();
2026-04-29 01:24:21 +08:00
IncrementSceneContentRevision();
}
RefreshScene();
return moved;
}
bool EditorSceneRuntime::AddComponentToSelectedGameObject(
std::string_view componentTypeName) {
2026-04-25 16:46:01 +08:00
if (componentTypeName.empty()) {
return false;
}
2026-04-28 17:53:36 +08:00
const std::string selectedItemId = GetSelectedItemId();
if (selectedItemId.empty() ||
m_backend == nullptr ||
!m_backend->AddComponent(selectedItemId, componentTypeName)) {
return false;
}
IncrementInspectorRevision();
2026-04-29 01:24:21 +08:00
IncrementSceneContentRevision();
RefreshScene();
return true;
}
bool EditorSceneRuntime::CanRemoveSelectedComponent(
std::string_view componentId) const {
const EditorSceneComponentDescriptor descriptor =
ResolveSelectedComponentDescriptor(componentId);
return descriptor.IsValid() && descriptor.removable;
}
bool EditorSceneRuntime::RemoveSelectedComponent(std::string_view componentId) {
const EditorSceneComponentDescriptor descriptor =
ResolveSelectedComponentDescriptor(componentId);
if (!descriptor.IsValid() || !descriptor.removable) {
return false;
}
ResetTransformEditHistory();
const bool removed =
m_backend != nullptr &&
m_backend->RemoveComponent(GetSelectedItemId(), componentId);
if (removed) {
IncrementInspectorRevision();
2026-04-29 01:24:21 +08:00
IncrementSceneContentRevision();
}
RefreshScene();
return removed;
}
bool EditorSceneRuntime::SetSelectedTransformLocalPosition(
std::string_view componentId,
const Vector3& position) {
const EditorSceneComponentDescriptor descriptor =
ResolveSelectedComponentDescriptor(componentId);
if (m_backend == nullptr || descriptor.typeName != "Transform") {
return false;
}
SceneTransformSnapshot beforeSnapshot = {};
if (!CaptureSelectedTransformSnapshot(beforeSnapshot)) {
return false;
}
if (!m_backend->SetTransformLocalPosition(
GetSelectedItemId(),
componentId,
position)) {
return false;
}
SceneTransformSnapshot afterSnapshot = {};
CaptureSelectedTransformSnapshot(afterSnapshot);
if (!TransformSnapshotsMatch(beforeSnapshot, afterSnapshot)) {
RecordTransformEdit(beforeSnapshot, afterSnapshot);
IncrementInspectorRevision();
2026-04-29 01:24:21 +08:00
IncrementSceneContentRevision();
}
return true;
}
bool EditorSceneRuntime::SetSelectedTransformLocalEulerAngles(
std::string_view componentId,
const Vector3& eulerAngles) {
const EditorSceneComponentDescriptor descriptor =
ResolveSelectedComponentDescriptor(componentId);
if (m_backend == nullptr || descriptor.typeName != "Transform") {
return false;
}
SceneTransformSnapshot beforeSnapshot = {};
if (!CaptureSelectedTransformSnapshot(beforeSnapshot)) {
return false;
}
if (!m_backend->SetTransformLocalEulerAngles(
GetSelectedItemId(),
componentId,
eulerAngles)) {
return false;
}
SceneTransformSnapshot afterSnapshot = {};
CaptureSelectedTransformSnapshot(afterSnapshot);
if (!TransformSnapshotsMatch(beforeSnapshot, afterSnapshot)) {
RecordTransformEdit(beforeSnapshot, afterSnapshot);
IncrementInspectorRevision();
2026-04-29 01:24:21 +08:00
IncrementSceneContentRevision();
}
return true;
}
bool EditorSceneRuntime::SetSelectedTransformLocalScale(
std::string_view componentId,
const Vector3& scale) {
const EditorSceneComponentDescriptor descriptor =
ResolveSelectedComponentDescriptor(componentId);
if (m_backend == nullptr || descriptor.typeName != "Transform") {
return false;
}
SceneTransformSnapshot beforeSnapshot = {};
if (!CaptureSelectedTransformSnapshot(beforeSnapshot)) {
return false;
}
if (!m_backend->SetTransformLocalScale(
GetSelectedItemId(),
componentId,
scale)) {
return false;
}
SceneTransformSnapshot afterSnapshot = {};
CaptureSelectedTransformSnapshot(afterSnapshot);
if (!TransformSnapshotsMatch(beforeSnapshot, afterSnapshot)) {
RecordTransformEdit(beforeSnapshot, afterSnapshot);
IncrementInspectorRevision();
2026-04-29 01:24:21 +08:00
IncrementSceneContentRevision();
}
return true;
}
bool EditorSceneRuntime::ApplySelectedComponentMutation(
2026-04-29 01:24:21 +08:00
const EditorSceneComponentMutation& mutation) {
if (m_backend == nullptr ||
2026-04-29 01:24:21 +08:00
!mutation.IsValid() ||
!ResolveSelectedComponentDescriptor(mutation.componentId).IsValid()) {
return false;
}
2026-04-29 01:24:21 +08:00
if (!m_backend->ApplyComponentMutation(
GetSelectedItemId(),
mutation)) {
return false;
}
IncrementInspectorRevision();
2026-04-29 01:24:21 +08:00
IncrementSceneContentRevision();
return true;
}
bool EditorSceneRuntime::CaptureSelectedTransformSnapshot(
SceneTransformSnapshot& outSnapshot) const {
2026-04-29 01:24:21 +08:00
const std::optional<EditorSceneObjectId> selectedId = GetSelectedObjectId();
if (!selectedId.has_value() || m_backend == nullptr) {
outSnapshot = {};
return false;
}
outSnapshot = {};
outSnapshot.targetId = selectedId.value();
if (!m_backend->QueryWorldTransform(
selectedId.value(),
outSnapshot.position,
outSnapshot.rotation,
outSnapshot.scale)) {
outSnapshot = {};
return false;
}
outSnapshot.valid = true;
return true;
}
bool EditorSceneRuntime::ApplyTransformSnapshot(
const SceneTransformSnapshot& snapshot) {
if (!snapshot.IsValid()) {
return false;
}
if (m_backend == nullptr ||
!m_backend->SetWorldTransform(
snapshot.targetId,
snapshot.position,
snapshot.rotation,
snapshot.scale)) {
return false;
}
IncrementInspectorRevision();
2026-04-29 01:24:21 +08:00
IncrementSceneContentRevision();
return true;
}
bool EditorSceneRuntime::RecordTransformEdit(
const SceneTransformSnapshot& before,
const SceneTransformSnapshot& after) {
if (!before.IsValid() ||
!after.IsValid() ||
before.targetId != after.targetId ||
TransformSnapshotsMatch(before, after)) {
return false;
}
m_transformUndoStack.push_back({ before, after });
m_transformRedoStack.clear();
return true;
}
2026-04-28 17:53:36 +08:00
bool EditorSceneRuntime::ApplyTransformToolWorldPreview(
EditorSceneObjectId targetId,
const Vector3& position,
const Quaternion& rotation) {
if (targetId == kInvalidEditorSceneObjectId) {
return false;
}
const EditorSceneObjectId activeTargetId =
GetSelectedObjectId().value_or(kInvalidEditorSceneObjectId);
if (activeTargetId == kInvalidEditorSceneObjectId ||
targetId != activeTargetId) {
2026-04-28 17:53:36 +08:00
return false;
}
if (m_backend == nullptr ||
!m_backend->SetWorldPositionRotation(targetId, position, rotation)) {
return false;
}
IncrementInspectorRevision();
2026-04-29 01:24:21 +08:00
IncrementSceneContentRevision();
return true;
2026-04-28 17:53:36 +08:00
}
bool EditorSceneRuntime::ApplyTransformToolLocalScalePreview(
EditorSceneObjectId targetId,
const Vector3& localScale) {
if (targetId == kInvalidEditorSceneObjectId) {
return false;
}
const EditorSceneObjectId activeTargetId =
GetSelectedObjectId().value_or(kInvalidEditorSceneObjectId);
if (activeTargetId == kInvalidEditorSceneObjectId ||
targetId != activeTargetId) {
2026-04-28 17:53:36 +08:00
return false;
}
if (m_backend == nullptr ||
!m_backend->SetObjectLocalScale(targetId, localScale)) {
2026-04-28 17:53:36 +08:00
return false;
}
IncrementInspectorRevision();
2026-04-29 01:24:21 +08:00
IncrementSceneContentRevision();
2026-04-28 17:53:36 +08:00
return true;
}
bool EditorSceneRuntime::CanUndoTransformEdit() const {
return !m_transformUndoStack.empty();
}
bool EditorSceneRuntime::CanRedoTransformEdit() const {
return !m_transformRedoStack.empty();
}
bool EditorSceneRuntime::UndoTransformEdit() {
if (m_transformUndoStack.empty()) {
return false;
}
const TransformEditTransaction transaction = m_transformUndoStack.back();
if (!ApplyTransformSnapshot(transaction.before)) {
return false;
}
m_transformUndoStack.pop_back();
m_transformRedoStack.push_back(transaction);
SetSelection(transaction.before.targetId);
return true;
}
bool EditorSceneRuntime::RedoTransformEdit() {
if (m_transformRedoStack.empty()) {
return false;
}
const TransformEditTransaction transaction = m_transformRedoStack.back();
if (!ApplyTransformSnapshot(transaction.after)) {
return false;
}
m_transformRedoStack.pop_back();
m_transformUndoStack.push_back(transaction);
SetSelection(transaction.after.targetId);
return true;
}
void EditorSceneRuntime::NotifyExternalInspectorStateChanged() {
IncrementInspectorRevision();
}
EditorSelectionService& EditorSceneRuntime::SelectionService() {
return *m_selectionService;
}
const EditorSelectionService& EditorSceneRuntime::SelectionService() const {
return *m_selectionService;
}
bool EditorSceneRuntime::HasHierarchySelection() const {
return SelectionService().HasSelectionKind(EditorSelectionKind::HierarchyNode);
}
void EditorSceneRuntime::RevalidateSelection() {
2026-04-29 01:24:21 +08:00
const std::optional<EditorSceneObjectSnapshot> snapshot =
GetSelectedObjectSnapshot();
if (!snapshot.has_value()) {
return;
}
SelectionService().SetHierarchySelection(
2026-04-29 01:24:21 +08:00
snapshot->itemId,
snapshot->displayName);
}
bool EditorSceneRuntime::HasValidSelection() const {
2026-04-29 01:24:21 +08:00
return GetSelectedObjectSnapshot().has_value();
}
std::optional<EditorSceneObjectSnapshot> EditorSceneRuntime::GetObjectSnapshot(
std::string_view itemId) const {
return m_backend != nullptr
? m_backend->GetObjectSnapshot(itemId)
: std::nullopt;
}
EditorSceneComponentDescriptor EditorSceneRuntime::ResolveSelectedComponentDescriptor(
std::string_view componentId) const {
for (const EditorSceneComponentDescriptor& descriptor :
GetSelectedComponents()) {
if (descriptor.componentId == componentId) {
return descriptor;
}
}
return {};
}
bool EditorSceneRuntime::SelectFirstAvailableGameObject() {
2026-04-29 01:24:21 +08:00
const EditorSceneHierarchySnapshot snapshot = BuildHierarchySnapshot();
for (const EditorSceneHierarchyNode& root : snapshot.roots) {
if (root.itemId.empty()) {
continue;
}
2026-04-29 01:24:21 +08:00
return SetSelection(root.itemId);
}
if (HasHierarchySelection()) {
ClearSelection();
}
return false;
}
void EditorSceneRuntime::ResetTransformEditHistory() {
m_transformUndoStack.clear();
m_transformRedoStack.clear();
}
void EditorSceneRuntime::IncrementInspectorRevision() {
++m_inspectorRevision;
}
2026-04-29 01:24:21 +08:00
void EditorSceneRuntime::IncrementSceneContentRevision() {
++m_sceneContentRevision;
}
} // namespace XCEngine::UI::Editor::App