docs: 更新 containers 和 threading 模块文档
- containers: 更新 string 类的多个方法文档 - threading: 更新 mutex 和 task-group 方法文档
This commit is contained in:
@@ -22,7 +22,7 @@ ConstIterator end() const;
|
|||||||
**示例:**
|
**示例:**
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
#include <XCEngine/Containers/Array.h>
|
#include <XCEngine/Core/Containers/Array.h>
|
||||||
|
|
||||||
XCEngine::Containers::Array<int> arr = {10, 20, 30, 40, 50};
|
XCEngine::Containers::Array<int> arr = {10, 20, 30, 40, 50};
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ Array(std::initializer_list<T> init);
|
|||||||
**示例:**
|
**示例:**
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
#include <XCEngine/Containers/Array.h>
|
#include <XCEngine/Core/Containers/Array.h>
|
||||||
|
|
||||||
using namespace XCEngine::Containers;
|
using namespace XCEngine::Containers;
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ void Clear();
|
|||||||
|
|
||||||
**示例:**
|
**示例:**
|
||||||
```cpp
|
```cpp
|
||||||
#include "XCEngine/Containers/String.h"
|
#include "XCEngine/Core/Containers/String.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ String(String&& other) noexcept;
|
|||||||
|
|
||||||
**示例:**
|
**示例:**
|
||||||
```cpp
|
```cpp
|
||||||
#include "XCEngine/Containers/String.h"
|
#include "XCEngine/Core/Containers/String.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const char* CStr() const;
|
|||||||
|
|
||||||
**示例:**
|
**示例:**
|
||||||
```cpp
|
```cpp
|
||||||
#include "XCEngine/Containers/String.h"
|
#include "XCEngine/Core/Containers/String.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
**示例:**
|
**示例:**
|
||||||
```cpp
|
```cpp
|
||||||
#include "XCEngine/Containers/String.h"
|
#include "XCEngine/Core/Containers/String.h"
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ bool EndsWith(const char* suffix) const;
|
|||||||
|
|
||||||
**示例:**
|
**示例:**
|
||||||
```cpp
|
```cpp
|
||||||
#include "XCEngine/Containers/String.h"
|
#include "XCEngine/Core/Containers/String.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ SizeType Find(const char* str, SizeType pos = 0) const;
|
|||||||
|
|
||||||
**示例:**
|
**示例:**
|
||||||
```cpp
|
```cpp
|
||||||
#include "XCEngine/Containers/String.h"
|
#include "XCEngine/Core/Containers/String.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ String& operator=(const char* str);
|
|||||||
|
|
||||||
**示例:**
|
**示例:**
|
||||||
```cpp
|
```cpp
|
||||||
#include "XCEngine/Containers/String.h"
|
#include "XCEngine/Core/Containers/String.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ inline bool operator!=(const String& lhs, const String& rhs);
|
|||||||
|
|
||||||
**示例:**
|
**示例:**
|
||||||
```cpp
|
```cpp
|
||||||
#include "XCEngine/Containers/String.h"
|
#include "XCEngine/Core/Containers/String.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ String& operator+=(char c);
|
|||||||
|
|
||||||
**示例:**
|
**示例:**
|
||||||
```cpp
|
```cpp
|
||||||
#include "XCEngine/Containers/String.h"
|
#include "XCEngine/Core/Containers/String.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ inline String operator+(const String& lhs, const String& rhs);
|
|||||||
|
|
||||||
**示例:**
|
**示例:**
|
||||||
```cpp
|
```cpp
|
||||||
#include "XCEngine/Containers/String.h"
|
#include "XCEngine/Core/Containers/String.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const char& operator[](SizeType index) const;
|
|||||||
|
|
||||||
**示例:**
|
**示例:**
|
||||||
```cpp
|
```cpp
|
||||||
#include "XCEngine/Containers/String.h"
|
#include "XCEngine/Core/Containers/String.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ bool Write(const Containers::String& str);
|
|||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
#include <XCEngine/Core/FileWriter.h>
|
#include <XCEngine/Core/FileWriter.h>
|
||||||
#include <XCEngine/Containers/String.h>
|
#include <XCEngine/Core/Containers/String.h>
|
||||||
|
|
||||||
using namespace XCEngine::Core;
|
using namespace XCEngine::Core;
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ struct MeshSection {
|
|||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
#include "XCEngine/Resources/Mesh.h"
|
#include "XCEngine/Resources/Mesh.h"
|
||||||
#include "XCEngine/Containers/Array.h"
|
#include "XCEngine/Core/Containers/Array.h"
|
||||||
|
|
||||||
using namespace XCEngine;
|
using namespace XCEngine;
|
||||||
using namespace Resources;
|
using namespace Resources;
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ void lock() const;
|
|||||||
#include "XCEngine/Threading/Mutex.h"
|
#include "XCEngine/Threading/Mutex.h"
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
XCEngine::XCEngine::Threading::Mutex mtx;
|
XCEngine::Threading::Mutex mtx;
|
||||||
int counter = 0;
|
int counter = 0;
|
||||||
|
|
||||||
void Increment() {
|
void Increment() {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ bool try_lock() const;
|
|||||||
#include "XCEngine/Threading/Mutex.h"
|
#include "XCEngine/Threading/Mutex.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
XCEngine::XCEngine::Threading::Mutex mtx;
|
XCEngine::Threading::Mutex mtx;
|
||||||
volatile bool updated = false;
|
volatile bool updated = false;
|
||||||
|
|
||||||
void TryUpdate() {
|
void TryUpdate() {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ void unlock() const;
|
|||||||
```cpp
|
```cpp
|
||||||
#include "XCEngine/Threading/Mutex.h"
|
#include "XCEngine/Threading/Mutex.h"
|
||||||
|
|
||||||
XCEngine::XCEngine::Threading::Mutex mtx;
|
XCEngine::Threading::Mutex mtx;
|
||||||
std::vector<int> data;
|
std::vector<int> data;
|
||||||
|
|
||||||
void SafePush(int value) {
|
void SafePush(int value) {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ TaskGroup();
|
|||||||
|
|
||||||
**注意:**
|
**注意:**
|
||||||
- 构造后的任务组不包含任何任务。
|
- 构造后的任务组不包含任何任务。
|
||||||
- 任务组创建后需要通过 TaskSystem::CreateTaskGroup() 实际创建。
|
- TaskGroup 可以独立使用,也可以通过 TaskSystem::CreateTaskGroup() 创建(后者会将任务组注册到任务系统管理)。
|
||||||
|
|
||||||
**示例:**
|
**示例:**
|
||||||
|
|
||||||
|
|||||||
@@ -29,4 +29,4 @@ task->Release(); // 引用计数 = 0,任务被 delete
|
|||||||
## 相关文档
|
## 相关文档
|
||||||
|
|
||||||
- [ITask 总览](task.md) - 返回类总览
|
- [ITask 总览](task.md) - 返回类总览
|
||||||
- [AddRef](../../core/refcounted/addref.md) - 增加引用计数
|
- [AddRef](../../core/refcounted/AddRef.md) - 增加引用计数
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ add_executable(${PROJECT_NAME} WIN32
|
|||||||
src/main.cpp
|
src/main.cpp
|
||||||
src/Application.cpp
|
src/Application.cpp
|
||||||
src/Theme.cpp
|
src/Theme.cpp
|
||||||
|
src/Core/UndoManager.cpp
|
||||||
src/Managers/SceneManager.cpp
|
src/Managers/SceneManager.cpp
|
||||||
src/Managers/ProjectManager.cpp
|
src/Managers/ProjectManager.cpp
|
||||||
src/Core/EditorConsoleSink.cpp
|
src/Core/EditorConsoleSink.cpp
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public:
|
|||||||
return dynamic_cast<::XCEngine::Components::CameraComponent*>(component) != nullptr;
|
return dynamic_cast<::XCEngine::Components::CameraComponent*>(component) != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Render(::XCEngine::Components::Component* component) override {
|
bool Render(::XCEngine::Components::Component* component, IUndoManager* undoManager) override {
|
||||||
auto* camera = dynamic_cast<::XCEngine::Components::CameraComponent*>(component);
|
auto* camera = dynamic_cast<::XCEngine::Components::CameraComponent*>(component);
|
||||||
if (!camera) {
|
if (!camera) {
|
||||||
return false;
|
return false;
|
||||||
@@ -28,6 +28,9 @@ public:
|
|||||||
const char* projectionLabels[] = { "Perspective", "Orthographic" };
|
const char* projectionLabels[] = { "Perspective", "Orthographic" };
|
||||||
bool changed = false;
|
bool changed = false;
|
||||||
if (ImGui::Combo("Projection", &projectionType, projectionLabels, 2)) {
|
if (ImGui::Combo("Projection", &projectionType, projectionLabels, 2)) {
|
||||||
|
if (undoManager) {
|
||||||
|
undoManager->BeginInteractiveChange("Modify Camera");
|
||||||
|
}
|
||||||
camera->SetProjectionType(static_cast<::XCEngine::Components::CameraProjectionType>(projectionType));
|
camera->SetProjectionType(static_cast<::XCEngine::Components::CameraProjectionType>(projectionType));
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
@@ -35,12 +38,18 @@ public:
|
|||||||
if (camera->GetProjectionType() == ::XCEngine::Components::CameraProjectionType::Perspective) {
|
if (camera->GetProjectionType() == ::XCEngine::Components::CameraProjectionType::Perspective) {
|
||||||
float fieldOfView = camera->GetFieldOfView();
|
float fieldOfView = camera->GetFieldOfView();
|
||||||
if (UI::DrawSliderFloat("Field Of View", fieldOfView, 1.0f, 179.0f, 100.0f, "%.1f")) {
|
if (UI::DrawSliderFloat("Field Of View", fieldOfView, 1.0f, 179.0f, 100.0f, "%.1f")) {
|
||||||
|
if (undoManager) {
|
||||||
|
undoManager->BeginInteractiveChange("Modify Camera");
|
||||||
|
}
|
||||||
camera->SetFieldOfView(fieldOfView);
|
camera->SetFieldOfView(fieldOfView);
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
float orthographicSize = camera->GetOrthographicSize();
|
float orthographicSize = camera->GetOrthographicSize();
|
||||||
if (UI::DrawFloat("Orthographic Size", orthographicSize, 100.0f, 0.1f, 0.001f)) {
|
if (UI::DrawFloat("Orthographic Size", orthographicSize, 100.0f, 0.1f, 0.001f)) {
|
||||||
|
if (undoManager) {
|
||||||
|
undoManager->BeginInteractiveChange("Modify Camera");
|
||||||
|
}
|
||||||
camera->SetOrthographicSize(orthographicSize);
|
camera->SetOrthographicSize(orthographicSize);
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
@@ -48,24 +57,36 @@ public:
|
|||||||
|
|
||||||
float nearClip = camera->GetNearClipPlane();
|
float nearClip = camera->GetNearClipPlane();
|
||||||
if (UI::DrawFloat("Near Clip", nearClip, 100.0f, 0.01f, 0.001f)) {
|
if (UI::DrawFloat("Near Clip", nearClip, 100.0f, 0.01f, 0.001f)) {
|
||||||
|
if (undoManager) {
|
||||||
|
undoManager->BeginInteractiveChange("Modify Camera");
|
||||||
|
}
|
||||||
camera->SetNearClipPlane(nearClip);
|
camera->SetNearClipPlane(nearClip);
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
float farClip = camera->GetFarClipPlane();
|
float farClip = camera->GetFarClipPlane();
|
||||||
if (UI::DrawFloat("Far Clip", farClip, 100.0f, 0.1f, nearClip + 0.001f)) {
|
if (UI::DrawFloat("Far Clip", farClip, 100.0f, 0.1f, nearClip + 0.001f)) {
|
||||||
|
if (undoManager) {
|
||||||
|
undoManager->BeginInteractiveChange("Modify Camera");
|
||||||
|
}
|
||||||
camera->SetFarClipPlane(farClip);
|
camera->SetFarClipPlane(farClip);
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
float depth = camera->GetDepth();
|
float depth = camera->GetDepth();
|
||||||
if (UI::DrawFloat("Depth", depth, 100.0f, 0.1f)) {
|
if (UI::DrawFloat("Depth", depth, 100.0f, 0.1f)) {
|
||||||
|
if (undoManager) {
|
||||||
|
undoManager->BeginInteractiveChange("Modify Camera");
|
||||||
|
}
|
||||||
camera->SetDepth(depth);
|
camera->SetDepth(depth);
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool primary = camera->IsPrimary();
|
bool primary = camera->IsPrimary();
|
||||||
if (UI::DrawBool("Primary", primary)) {
|
if (UI::DrawBool("Primary", primary)) {
|
||||||
|
if (undoManager) {
|
||||||
|
undoManager->BeginInteractiveChange("Modify Camera");
|
||||||
|
}
|
||||||
camera->SetPrimary(primary);
|
camera->SetPrimary(primary);
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
@@ -77,6 +98,9 @@ public:
|
|||||||
camera->GetClearColor().a
|
camera->GetClearColor().a
|
||||||
};
|
};
|
||||||
if (UI::DrawColor4("Clear Color", clearColor)) {
|
if (UI::DrawColor4("Clear Color", clearColor)) {
|
||||||
|
if (undoManager) {
|
||||||
|
undoManager->BeginInteractiveChange("Modify Camera");
|
||||||
|
}
|
||||||
camera->SetClearColor(::XCEngine::Math::Color(clearColor[0], clearColor[1], clearColor[2], clearColor[3]));
|
camera->SetClearColor(::XCEngine::Math::Color(clearColor[0], clearColor[1], clearColor[2], clearColor[3]));
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,15 @@
|
|||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
namespace Editor {
|
namespace Editor {
|
||||||
|
|
||||||
|
class IUndoManager;
|
||||||
|
|
||||||
class IComponentEditor {
|
class IComponentEditor {
|
||||||
public:
|
public:
|
||||||
virtual ~IComponentEditor() = default;
|
virtual ~IComponentEditor() = default;
|
||||||
|
|
||||||
virtual const char* GetDisplayName() const = 0;
|
virtual const char* GetDisplayName() const = 0;
|
||||||
virtual bool CanEdit(::XCEngine::Components::Component* component) const = 0;
|
virtual bool CanEdit(::XCEngine::Components::Component* component) const = 0;
|
||||||
virtual bool Render(::XCEngine::Components::Component* component) = 0;
|
virtual bool Render(::XCEngine::Components::Component* component, IUndoManager* undoManager) = 0;
|
||||||
|
|
||||||
virtual bool ShowInAddComponentMenu() const { return true; }
|
virtual bool ShowInAddComponentMenu() const { return true; }
|
||||||
virtual bool CanAddTo(::XCEngine::Components::GameObject* gameObject) const { return false; }
|
virtual bool CanAddTo(::XCEngine::Components::GameObject* gameObject) const { return false; }
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public:
|
|||||||
return dynamic_cast<::XCEngine::Components::LightComponent*>(component) != nullptr;
|
return dynamic_cast<::XCEngine::Components::LightComponent*>(component) != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Render(::XCEngine::Components::Component* component) override {
|
bool Render(::XCEngine::Components::Component* component, IUndoManager* undoManager) override {
|
||||||
auto* light = dynamic_cast<::XCEngine::Components::LightComponent*>(component);
|
auto* light = dynamic_cast<::XCEngine::Components::LightComponent*>(component);
|
||||||
if (!light) {
|
if (!light) {
|
||||||
return false;
|
return false;
|
||||||
@@ -28,6 +28,9 @@ public:
|
|||||||
const char* lightTypeLabels[] = { "Directional", "Point", "Spot" };
|
const char* lightTypeLabels[] = { "Directional", "Point", "Spot" };
|
||||||
bool changed = false;
|
bool changed = false;
|
||||||
if (ImGui::Combo("Type", &lightType, lightTypeLabels, 3)) {
|
if (ImGui::Combo("Type", &lightType, lightTypeLabels, 3)) {
|
||||||
|
if (undoManager) {
|
||||||
|
undoManager->BeginInteractiveChange("Modify Light");
|
||||||
|
}
|
||||||
light->SetLightType(static_cast<::XCEngine::Components::LightType>(lightType));
|
light->SetLightType(static_cast<::XCEngine::Components::LightType>(lightType));
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
@@ -39,12 +42,18 @@ public:
|
|||||||
light->GetColor().a
|
light->GetColor().a
|
||||||
};
|
};
|
||||||
if (UI::DrawColor4("Color", color)) {
|
if (UI::DrawColor4("Color", color)) {
|
||||||
|
if (undoManager) {
|
||||||
|
undoManager->BeginInteractiveChange("Modify Light");
|
||||||
|
}
|
||||||
light->SetColor(::XCEngine::Math::Color(color[0], color[1], color[2], color[3]));
|
light->SetColor(::XCEngine::Math::Color(color[0], color[1], color[2], color[3]));
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
float intensity = light->GetIntensity();
|
float intensity = light->GetIntensity();
|
||||||
if (UI::DrawFloat("Intensity", intensity, 100.0f, 0.1f, 0.0f)) {
|
if (UI::DrawFloat("Intensity", intensity, 100.0f, 0.1f, 0.0f)) {
|
||||||
|
if (undoManager) {
|
||||||
|
undoManager->BeginInteractiveChange("Modify Light");
|
||||||
|
}
|
||||||
light->SetIntensity(intensity);
|
light->SetIntensity(intensity);
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
@@ -52,6 +61,9 @@ public:
|
|||||||
if (light->GetLightType() != ::XCEngine::Components::LightType::Directional) {
|
if (light->GetLightType() != ::XCEngine::Components::LightType::Directional) {
|
||||||
float range = light->GetRange();
|
float range = light->GetRange();
|
||||||
if (UI::DrawFloat("Range", range, 100.0f, 0.1f, 0.001f)) {
|
if (UI::DrawFloat("Range", range, 100.0f, 0.1f, 0.001f)) {
|
||||||
|
if (undoManager) {
|
||||||
|
undoManager->BeginInteractiveChange("Modify Light");
|
||||||
|
}
|
||||||
light->SetRange(range);
|
light->SetRange(range);
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
@@ -60,6 +72,9 @@ public:
|
|||||||
if (light->GetLightType() == ::XCEngine::Components::LightType::Spot) {
|
if (light->GetLightType() == ::XCEngine::Components::LightType::Spot) {
|
||||||
float spotAngle = light->GetSpotAngle();
|
float spotAngle = light->GetSpotAngle();
|
||||||
if (UI::DrawSliderFloat("Spot Angle", spotAngle, 1.0f, 179.0f, 100.0f, "%.1f")) {
|
if (UI::DrawSliderFloat("Spot Angle", spotAngle, 1.0f, 179.0f, 100.0f, "%.1f")) {
|
||||||
|
if (undoManager) {
|
||||||
|
undoManager->BeginInteractiveChange("Modify Light");
|
||||||
|
}
|
||||||
light->SetSpotAngle(spotAngle);
|
light->SetSpotAngle(spotAngle);
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
@@ -67,6 +82,9 @@ public:
|
|||||||
|
|
||||||
bool castsShadows = light->GetCastsShadows();
|
bool castsShadows = light->GetCastsShadows();
|
||||||
if (UI::DrawBool("Cast Shadows", castsShadows)) {
|
if (UI::DrawBool("Cast Shadows", castsShadows)) {
|
||||||
|
if (undoManager) {
|
||||||
|
undoManager->BeginInteractiveChange("Modify Light");
|
||||||
|
}
|
||||||
light->SetCastsShadows(castsShadows);
|
light->SetCastsShadows(castsShadows);
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public:
|
|||||||
return dynamic_cast<::XCEngine::Components::TransformComponent*>(component) != nullptr;
|
return dynamic_cast<::XCEngine::Components::TransformComponent*>(component) != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Render(::XCEngine::Components::Component* component) override {
|
bool Render(::XCEngine::Components::Component* component, IUndoManager* undoManager) override {
|
||||||
auto* transform = dynamic_cast<::XCEngine::Components::TransformComponent*>(component);
|
auto* transform = dynamic_cast<::XCEngine::Components::TransformComponent*>(component);
|
||||||
if (!transform) {
|
if (!transform) {
|
||||||
return false;
|
return false;
|
||||||
@@ -30,16 +30,25 @@ public:
|
|||||||
bool changed = false;
|
bool changed = false;
|
||||||
|
|
||||||
if (UI::DrawVec3("Position", position, 0.0f, 80.0f, 0.1f)) {
|
if (UI::DrawVec3("Position", position, 0.0f, 80.0f, 0.1f)) {
|
||||||
|
if (undoManager) {
|
||||||
|
undoManager->BeginInteractiveChange("Modify Transform");
|
||||||
|
}
|
||||||
transform->SetLocalPosition(position);
|
transform->SetLocalPosition(position);
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (UI::DrawVec3("Rotation", rotation, 0.0f, 80.0f, 1.0f)) {
|
if (UI::DrawVec3("Rotation", rotation, 0.0f, 80.0f, 1.0f)) {
|
||||||
|
if (undoManager) {
|
||||||
|
undoManager->BeginInteractiveChange("Modify Transform");
|
||||||
|
}
|
||||||
transform->SetLocalEulerAngles(rotation);
|
transform->SetLocalEulerAngles(rotation);
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (UI::DrawVec3("Scale", scale, 1.0f, 80.0f, 0.1f)) {
|
if (UI::DrawVec3("Scale", scale, 1.0f, 80.0f, 0.1f)) {
|
||||||
|
if (undoManager) {
|
||||||
|
undoManager->BeginInteractiveChange("Modify Transform");
|
||||||
|
}
|
||||||
transform->SetLocalScale(scale);
|
transform->SetLocalScale(scale);
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
#include "SelectionManager.h"
|
#include "SelectionManager.h"
|
||||||
#include "IProjectManager.h"
|
#include "IProjectManager.h"
|
||||||
#include "ISceneManager.h"
|
#include "ISceneManager.h"
|
||||||
|
#include "IUndoManager.h"
|
||||||
|
#include "UndoManager.h"
|
||||||
#include "Managers/SceneManager.h"
|
#include "Managers/SceneManager.h"
|
||||||
#include "Managers/ProjectManager.h"
|
#include "Managers/ProjectManager.h"
|
||||||
#include "EditorEvents.h"
|
#include "EditorEvents.h"
|
||||||
@@ -20,6 +22,7 @@ public:
|
|||||||
: m_eventBus(std::make_unique<EventBus>())
|
: m_eventBus(std::make_unique<EventBus>())
|
||||||
, m_selectionManager(std::make_unique<SelectionManager>(*m_eventBus))
|
, m_selectionManager(std::make_unique<SelectionManager>(*m_eventBus))
|
||||||
, m_sceneManager(std::make_unique<SceneManager>(m_eventBus.get()))
|
, m_sceneManager(std::make_unique<SceneManager>(m_eventBus.get()))
|
||||||
|
, m_undoManager(std::make_unique<UndoManager>(*m_sceneManager, *m_selectionManager))
|
||||||
, m_projectManager(std::make_unique<ProjectManager>()) {
|
, m_projectManager(std::make_unique<ProjectManager>()) {
|
||||||
|
|
||||||
m_entityDeletedHandlerId = m_eventBus->Subscribe<EntityDeletedEvent>([this](const EntityDeletedEvent& event) {
|
m_entityDeletedHandlerId = m_eventBus->Subscribe<EntityDeletedEvent>([this](const EntityDeletedEvent& event) {
|
||||||
@@ -49,6 +52,10 @@ public:
|
|||||||
return *m_projectManager;
|
return *m_projectManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IUndoManager& GetUndoManager() override {
|
||||||
|
return *m_undoManager;
|
||||||
|
}
|
||||||
|
|
||||||
void SetProjectPath(const std::string& path) override {
|
void SetProjectPath(const std::string& path) override {
|
||||||
m_projectPath = path;
|
m_projectPath = path;
|
||||||
}
|
}
|
||||||
@@ -61,6 +68,7 @@ private:
|
|||||||
std::unique_ptr<EventBus> m_eventBus;
|
std::unique_ptr<EventBus> m_eventBus;
|
||||||
std::unique_ptr<SelectionManager> m_selectionManager;
|
std::unique_ptr<SelectionManager> m_selectionManager;
|
||||||
std::unique_ptr<SceneManager> m_sceneManager;
|
std::unique_ptr<SceneManager> m_sceneManager;
|
||||||
|
std::unique_ptr<UndoManager> m_undoManager;
|
||||||
std::unique_ptr<ProjectManager> m_projectManager;
|
std::unique_ptr<ProjectManager> m_projectManager;
|
||||||
std::string m_projectPath;
|
std::string m_projectPath;
|
||||||
uint64_t m_entityDeletedHandlerId;
|
uint64_t m_entityDeletedHandlerId;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ class EventBus;
|
|||||||
class ISelectionManager;
|
class ISelectionManager;
|
||||||
class IProjectManager;
|
class IProjectManager;
|
||||||
class ISceneManager;
|
class ISceneManager;
|
||||||
|
class IUndoManager;
|
||||||
|
|
||||||
class IEditorContext {
|
class IEditorContext {
|
||||||
public:
|
public:
|
||||||
@@ -19,6 +20,7 @@ public:
|
|||||||
virtual ISelectionManager& GetSelectionManager() = 0;
|
virtual ISelectionManager& GetSelectionManager() = 0;
|
||||||
virtual ISceneManager& GetSceneManager() = 0;
|
virtual ISceneManager& GetSceneManager() = 0;
|
||||||
virtual IProjectManager& GetProjectManager() = 0;
|
virtual IProjectManager& GetProjectManager() = 0;
|
||||||
|
virtual IUndoManager& GetUndoManager() = 0;
|
||||||
|
|
||||||
virtual void SetProjectPath(const std::string& path) = 0;
|
virtual void SetProjectPath(const std::string& path) = 0;
|
||||||
virtual const std::string& GetProjectPath() const = 0;
|
virtual const std::string& GetProjectPath() const = 0;
|
||||||
|
|||||||
40
editor/src/Core/IUndoManager.h
Normal file
40
editor/src/Core/IUndoManager.h
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "SceneSnapshot.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Editor {
|
||||||
|
|
||||||
|
struct UndoStateSnapshot {
|
||||||
|
SceneSnapshot scene;
|
||||||
|
std::vector<uint64_t> selectionIds;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IUndoManager {
|
||||||
|
public:
|
||||||
|
virtual ~IUndoManager() = default;
|
||||||
|
|
||||||
|
virtual void ClearHistory() = 0;
|
||||||
|
virtual bool CanUndo() const = 0;
|
||||||
|
virtual bool CanRedo() const = 0;
|
||||||
|
virtual const std::string& GetUndoLabel() const = 0;
|
||||||
|
virtual const std::string& GetRedoLabel() const = 0;
|
||||||
|
|
||||||
|
virtual void Undo() = 0;
|
||||||
|
virtual void Redo() = 0;
|
||||||
|
|
||||||
|
virtual UndoStateSnapshot CaptureCurrentState() const = 0;
|
||||||
|
virtual void PushCommand(const std::string& label, UndoStateSnapshot before, UndoStateSnapshot after) = 0;
|
||||||
|
|
||||||
|
virtual void BeginInteractiveChange(const std::string& label) = 0;
|
||||||
|
virtual bool HasPendingInteractiveChange() const = 0;
|
||||||
|
virtual void FinalizeInteractiveChange() = 0;
|
||||||
|
virtual void CancelInteractiveChange() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Editor
|
||||||
|
} // namespace XCEngine
|
||||||
16
editor/src/Core/SceneSnapshot.h
Normal file
16
editor/src/Core/SceneSnapshot.h
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Editor {
|
||||||
|
|
||||||
|
struct SceneSnapshot {
|
||||||
|
bool hasScene = false;
|
||||||
|
std::string sceneData;
|
||||||
|
std::string scenePath;
|
||||||
|
bool dirty = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Editor
|
||||||
|
} // namespace XCEngine
|
||||||
165
editor/src/Core/UndoManager.cpp
Normal file
165
editor/src/Core/UndoManager.cpp
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
#include "Core/UndoManager.h"
|
||||||
|
|
||||||
|
#include "Core/ISelectionManager.h"
|
||||||
|
#include "Managers/SceneManager.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Editor {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr size_t kMaxUndoHistory = 128;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
UndoManager::UndoManager(SceneManager& sceneManager, ISelectionManager& selectionManager)
|
||||||
|
: m_sceneManager(sceneManager)
|
||||||
|
, m_selectionManager(selectionManager) {}
|
||||||
|
|
||||||
|
void UndoManager::ClearHistory() {
|
||||||
|
m_history.clear();
|
||||||
|
m_nextIndex = 0;
|
||||||
|
m_pendingInteractiveChange.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UndoManager::CanUndo() const {
|
||||||
|
return m_nextIndex > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UndoManager::CanRedo() const {
|
||||||
|
return m_nextIndex < m_history.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& UndoManager::GetUndoLabel() const {
|
||||||
|
return CanUndo() ? m_history[m_nextIndex - 1].label : m_emptyLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& UndoManager::GetRedoLabel() const {
|
||||||
|
return CanRedo() ? m_history[m_nextIndex].label : m_emptyLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UndoManager::Undo() {
|
||||||
|
if (HasPendingInteractiveChange()) {
|
||||||
|
FinalizeInteractiveChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CanUndo()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
--m_nextIndex;
|
||||||
|
ApplyState(m_history[m_nextIndex].before);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UndoManager::Redo() {
|
||||||
|
if (HasPendingInteractiveChange()) {
|
||||||
|
FinalizeInteractiveChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CanRedo()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplyState(m_history[m_nextIndex].after);
|
||||||
|
++m_nextIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
UndoStateSnapshot UndoManager::CaptureCurrentState() const {
|
||||||
|
UndoStateSnapshot snapshot;
|
||||||
|
snapshot.scene = m_sceneManager.CaptureSceneSnapshot();
|
||||||
|
|
||||||
|
if (!snapshot.scene.hasScene) {
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint64_t entityId : m_selectionManager.GetSelectedEntities()) {
|
||||||
|
if (m_sceneManager.GetEntity(entityId)) {
|
||||||
|
snapshot.selectionIds.push_back(entityId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UndoManager::PushCommand(const std::string& label, UndoStateSnapshot before, UndoStateSnapshot after) {
|
||||||
|
if (AreStatesEqual(before, after)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_nextIndex < m_history.size()) {
|
||||||
|
m_history.erase(m_history.begin() + static_cast<std::ptrdiff_t>(m_nextIndex), m_history.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
m_history.push_back(CommandEntry{ label, std::move(before), std::move(after) });
|
||||||
|
if (m_history.size() > kMaxUndoHistory) {
|
||||||
|
m_history.erase(m_history.begin());
|
||||||
|
if (m_nextIndex > 0) {
|
||||||
|
--m_nextIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_nextIndex = m_history.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UndoManager::BeginInteractiveChange(const std::string& label) {
|
||||||
|
if (m_pendingInteractiveChange.has_value()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pendingInteractiveChange = PendingInteractiveChange{ label, CaptureCurrentState() };
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UndoManager::HasPendingInteractiveChange() const {
|
||||||
|
return m_pendingInteractiveChange.has_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UndoManager::FinalizeInteractiveChange() {
|
||||||
|
if (!m_pendingInteractiveChange.has_value()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PushCommand(
|
||||||
|
m_pendingInteractiveChange->label,
|
||||||
|
std::move(m_pendingInteractiveChange->before),
|
||||||
|
CaptureCurrentState());
|
||||||
|
m_pendingInteractiveChange.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UndoManager::CancelInteractiveChange() {
|
||||||
|
m_pendingInteractiveChange.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UndoManager::ApplyState(const UndoStateSnapshot& state) {
|
||||||
|
if (!m_sceneManager.RestoreSceneSnapshot(state.scene)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint64_t> validSelection;
|
||||||
|
validSelection.reserve(state.selectionIds.size());
|
||||||
|
for (uint64_t entityId : state.selectionIds) {
|
||||||
|
if (m_sceneManager.GetEntity(entityId)) {
|
||||||
|
validSelection.push_back(entityId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validSelection.empty()) {
|
||||||
|
m_selectionManager.ClearSelection();
|
||||||
|
} else {
|
||||||
|
m_selectionManager.SetSelectedEntities(validSelection);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UndoManager::AreStatesEqual(const UndoStateSnapshot& lhs, const UndoStateSnapshot& rhs) {
|
||||||
|
return lhs.scene.hasScene == rhs.scene.hasScene &&
|
||||||
|
lhs.scene.scenePath == rhs.scene.scenePath &&
|
||||||
|
lhs.scene.sceneData == rhs.scene.sceneData &&
|
||||||
|
lhs.scene.dirty == rhs.scene.dirty &&
|
||||||
|
lhs.selectionIds == rhs.selectionIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Editor
|
||||||
|
} // namespace XCEngine
|
||||||
60
editor/src/Core/UndoManager.h
Normal file
60
editor/src/Core/UndoManager.h
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "IUndoManager.h"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Editor {
|
||||||
|
|
||||||
|
class ISelectionManager;
|
||||||
|
class SceneManager;
|
||||||
|
|
||||||
|
class UndoManager : public IUndoManager {
|
||||||
|
public:
|
||||||
|
UndoManager(SceneManager& sceneManager, ISelectionManager& selectionManager);
|
||||||
|
|
||||||
|
void ClearHistory() override;
|
||||||
|
bool CanUndo() const override;
|
||||||
|
bool CanRedo() const override;
|
||||||
|
const std::string& GetUndoLabel() const override;
|
||||||
|
const std::string& GetRedoLabel() const override;
|
||||||
|
|
||||||
|
void Undo() override;
|
||||||
|
void Redo() override;
|
||||||
|
|
||||||
|
UndoStateSnapshot CaptureCurrentState() const override;
|
||||||
|
void PushCommand(const std::string& label, UndoStateSnapshot before, UndoStateSnapshot after) override;
|
||||||
|
|
||||||
|
void BeginInteractiveChange(const std::string& label) override;
|
||||||
|
bool HasPendingInteractiveChange() const override;
|
||||||
|
void FinalizeInteractiveChange() override;
|
||||||
|
void CancelInteractiveChange() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct CommandEntry {
|
||||||
|
std::string label;
|
||||||
|
UndoStateSnapshot before;
|
||||||
|
UndoStateSnapshot after;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PendingInteractiveChange {
|
||||||
|
std::string label;
|
||||||
|
UndoStateSnapshot before;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool ApplyState(const UndoStateSnapshot& state);
|
||||||
|
static bool AreStatesEqual(const UndoStateSnapshot& lhs, const UndoStateSnapshot& rhs);
|
||||||
|
|
||||||
|
SceneManager& m_sceneManager;
|
||||||
|
ISelectionManager& m_selectionManager;
|
||||||
|
std::vector<CommandEntry> m_history;
|
||||||
|
size_t m_nextIndex = 0;
|
||||||
|
std::optional<PendingInteractiveChange> m_pendingInteractiveChange;
|
||||||
|
std::string m_emptyLabel;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Editor
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "panels/ProjectPanel.h"
|
#include "panels/ProjectPanel.h"
|
||||||
#include "Core/IEditorContext.h"
|
#include "Core/IEditorContext.h"
|
||||||
#include "Core/EditorContext.h"
|
#include "Core/EditorContext.h"
|
||||||
|
#include "Core/IUndoManager.h"
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include <imgui_internal.h>
|
#include <imgui_internal.h>
|
||||||
@@ -45,6 +46,7 @@ void EditorLayer::onAttach() {
|
|||||||
m_projectPanel->Initialize(m_context->GetProjectPath());
|
m_projectPanel->Initialize(m_context->GetProjectPath());
|
||||||
m_context->GetSceneManager().LoadStartupScene(m_context->GetProjectPath());
|
m_context->GetSceneManager().LoadStartupScene(m_context->GetProjectPath());
|
||||||
m_context->GetProjectManager().RefreshCurrentFolder();
|
m_context->GetProjectManager().RefreshCurrentFolder();
|
||||||
|
m_context->GetUndoManager().ClearHistory();
|
||||||
|
|
||||||
m_menuBar->OnAttach();
|
m_menuBar->OnAttach();
|
||||||
m_hierarchyPanel->OnAttach();
|
m_hierarchyPanel->OnAttach();
|
||||||
|
|||||||
@@ -172,6 +172,7 @@ void SceneManager::CopyEntity(::XCEngine::Components::GameObject::ID id) {
|
|||||||
|
|
||||||
const auto newEntityId = PasteEntityRecursive(*m_clipboard, parent);
|
const auto newEntityId = PasteEntityRecursive(*m_clipboard, parent);
|
||||||
SyncRootEntities();
|
SyncRootEntities();
|
||||||
|
SetSceneDirty(true);
|
||||||
|
|
||||||
OnEntityCreated.Invoke(newEntityId);
|
OnEntityCreated.Invoke(newEntityId);
|
||||||
OnSceneChanged.Invoke();
|
OnSceneChanged.Invoke();
|
||||||
@@ -184,6 +185,53 @@ void SceneManager::CopyEntity(::XCEngine::Components::GameObject::ID id) {
|
|||||||
return newEntityId;
|
return newEntityId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SceneSnapshot SceneManager::CaptureSceneSnapshot() const {
|
||||||
|
SceneSnapshot snapshot;
|
||||||
|
if (!m_scene) {
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshot.hasScene = true;
|
||||||
|
snapshot.sceneData = m_scene->SerializeToString();
|
||||||
|
snapshot.scenePath = m_currentScenePath;
|
||||||
|
snapshot.dirty = m_isSceneDirty;
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SceneManager::RestoreSceneSnapshot(const SceneSnapshot& snapshot) {
|
||||||
|
if (!snapshot.hasScene) {
|
||||||
|
m_scene.reset();
|
||||||
|
m_rootEntities.clear();
|
||||||
|
m_clipboard.reset();
|
||||||
|
m_currentScenePath.clear();
|
||||||
|
m_currentSceneName = "Untitled Scene";
|
||||||
|
SetSceneDirty(snapshot.dirty);
|
||||||
|
|
||||||
|
OnSceneChanged.Invoke();
|
||||||
|
if (m_eventBus) {
|
||||||
|
m_eventBus->Publish(SceneChangedEvent{});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto scene = std::make_unique<::XCEngine::Components::Scene>();
|
||||||
|
scene->DeserializeFromString(snapshot.sceneData);
|
||||||
|
|
||||||
|
m_scene = std::move(scene);
|
||||||
|
m_clipboard.reset();
|
||||||
|
SyncRootEntities();
|
||||||
|
m_currentScenePath = snapshot.scenePath;
|
||||||
|
m_currentSceneName = m_scene ? m_scene->GetName() : "Untitled Scene";
|
||||||
|
SetSceneDirty(snapshot.dirty);
|
||||||
|
|
||||||
|
OnSceneChanged.Invoke();
|
||||||
|
if (m_eventBus) {
|
||||||
|
m_eventBus->Publish(SceneChangedEvent{});
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
::XCEngine::Components::GameObject::ID SceneManager::DuplicateEntity(::XCEngine::Components::GameObject::ID id) {
|
::XCEngine::Components::GameObject::ID SceneManager::DuplicateEntity(::XCEngine::Components::GameObject::ID id) {
|
||||||
if (!m_scene) return 0;
|
if (!m_scene) return 0;
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#include <XCEngine/Components/GameObject.h>
|
#include <XCEngine/Components/GameObject.h>
|
||||||
#include <XCEngine/Scene/Scene.h>
|
#include <XCEngine/Scene/Scene.h>
|
||||||
|
|
||||||
|
#include "Core/SceneSnapshot.h"
|
||||||
#include "Core/ISceneManager.h"
|
#include "Core/ISceneManager.h"
|
||||||
|
|
||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
@@ -61,6 +62,8 @@ public:
|
|||||||
void CreateDemoScene() override;
|
void CreateDemoScene() override;
|
||||||
|
|
||||||
bool HasClipboardData() const { return m_clipboard.has_value(); }
|
bool HasClipboardData() const { return m_clipboard.has_value(); }
|
||||||
|
SceneSnapshot CaptureSceneSnapshot() const;
|
||||||
|
bool RestoreSceneSnapshot(const SceneSnapshot& snapshot);
|
||||||
|
|
||||||
::XCEngine::Core::Event<::XCEngine::Components::GameObject::ID> OnEntityCreated;
|
::XCEngine::Core::Event<::XCEngine::Components::GameObject::ID> OnEntityCreated;
|
||||||
::XCEngine::Core::Event<::XCEngine::Components::GameObject::ID> OnEntityDeleted;
|
::XCEngine::Core::Event<::XCEngine::Components::GameObject::ID> OnEntityDeleted;
|
||||||
|
|||||||
34
editor/src/Utils/UndoUtils.h
Normal file
34
editor/src/Utils/UndoUtils.h
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Core/IEditorContext.h"
|
||||||
|
#include "Core/IUndoManager.h"
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Editor {
|
||||||
|
namespace UndoUtils {
|
||||||
|
|
||||||
|
template<typename Func>
|
||||||
|
auto ExecuteSceneCommand(IEditorContext& context, const std::string& label, Func&& func) {
|
||||||
|
auto& undoManager = context.GetUndoManager();
|
||||||
|
if (undoManager.HasPendingInteractiveChange()) {
|
||||||
|
undoManager.FinalizeInteractiveChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto before = undoManager.CaptureCurrentState();
|
||||||
|
|
||||||
|
if constexpr (std::is_void_v<std::invoke_result_t<Func>>) {
|
||||||
|
std::forward<Func>(func)();
|
||||||
|
undoManager.PushCommand(label, std::move(before), undoManager.CaptureCurrentState());
|
||||||
|
} else {
|
||||||
|
auto result = std::forward<Func>(func)();
|
||||||
|
undoManager.PushCommand(label, std::move(before), undoManager.CaptureCurrentState());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace UndoUtils
|
||||||
|
} // namespace Editor
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -2,8 +2,10 @@
|
|||||||
#include "Core/IEditorContext.h"
|
#include "Core/IEditorContext.h"
|
||||||
#include "Core/ISceneManager.h"
|
#include "Core/ISceneManager.h"
|
||||||
#include "Core/ISelectionManager.h"
|
#include "Core/ISelectionManager.h"
|
||||||
|
#include "Core/IUndoManager.h"
|
||||||
#include "Core/EditorEvents.h"
|
#include "Core/EditorEvents.h"
|
||||||
#include "Core/EventBus.h"
|
#include "Core/EventBus.h"
|
||||||
|
#include "Utils/UndoUtils.h"
|
||||||
#include <XCEngine/Core/Math/Vector3.h>
|
#include <XCEngine/Core/Math/Vector3.h>
|
||||||
#include <XCEngine/Core/Math/Quaternion.h>
|
#include <XCEngine/Core/Math/Quaternion.h>
|
||||||
#include <XCEngine/Components/CameraComponent.h>
|
#include <XCEngine/Components/CameraComponent.h>
|
||||||
@@ -73,16 +75,19 @@ void HierarchyPanel::Render() {
|
|||||||
::XCEngine::Components::GameObject* sourceGameObject = *(::XCEngine::Components::GameObject**)payload->Data;
|
::XCEngine::Components::GameObject* sourceGameObject = *(::XCEngine::Components::GameObject**)payload->Data;
|
||||||
if (sourceGameObject && sourceGameObject->GetParent() != nullptr) {
|
if (sourceGameObject && sourceGameObject->GetParent() != nullptr) {
|
||||||
auto& sceneManager = m_context->GetSceneManager();
|
auto& sceneManager = m_context->GetSceneManager();
|
||||||
auto* srcTransform = sourceGameObject->GetTransform();
|
UndoUtils::ExecuteSceneCommand(*m_context, "Reparent Entity", [&]() {
|
||||||
Math::Vector3 worldPos = srcTransform->GetPosition();
|
auto* srcTransform = sourceGameObject->GetTransform();
|
||||||
Math::Quaternion worldRot = srcTransform->GetRotation();
|
Math::Vector3 worldPos = srcTransform->GetPosition();
|
||||||
Math::Vector3 worldScale = srcTransform->GetScale();
|
Math::Quaternion worldRot = srcTransform->GetRotation();
|
||||||
|
Math::Vector3 worldScale = srcTransform->GetScale();
|
||||||
|
|
||||||
sceneManager.MoveEntity(sourceGameObject->GetID(), 0);
|
sceneManager.MoveEntity(sourceGameObject->GetID(), 0);
|
||||||
|
|
||||||
srcTransform->SetPosition(worldPos);
|
srcTransform->SetPosition(worldPos);
|
||||||
srcTransform->SetRotation(worldRot);
|
srcTransform->SetRotation(worldRot);
|
||||||
srcTransform->SetScale(worldScale);
|
srcTransform->SetScale(worldScale);
|
||||||
|
sceneManager.MarkSceneDirty();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui::EndDragDropTarget();
|
ImGui::EndDragDropTarget();
|
||||||
@@ -133,7 +138,9 @@ void HierarchyPanel::RenderEntity(::XCEngine::Components::GameObject* gameObject
|
|||||||
ImGui::SetNextItemWidth(-1);
|
ImGui::SetNextItemWidth(-1);
|
||||||
if (ImGui::InputText("##Rename", m_renameBuffer, sizeof(m_renameBuffer), ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll)) {
|
if (ImGui::InputText("##Rename", m_renameBuffer, sizeof(m_renameBuffer), ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll)) {
|
||||||
if (strlen(m_renameBuffer) > 0) {
|
if (strlen(m_renameBuffer) > 0) {
|
||||||
m_context->GetSceneManager().RenameEntity(gameObject->GetID(), m_renameBuffer);
|
UndoUtils::ExecuteSceneCommand(*m_context, "Rename Entity", [&]() {
|
||||||
|
m_context->GetSceneManager().RenameEntity(gameObject->GetID(), m_renameBuffer);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
m_renaming = false;
|
m_renaming = false;
|
||||||
m_renamingEntity = nullptr;
|
m_renamingEntity = nullptr;
|
||||||
@@ -141,7 +148,9 @@ void HierarchyPanel::RenderEntity(::XCEngine::Components::GameObject* gameObject
|
|||||||
|
|
||||||
if (!ImGui::IsItemActive() && ImGui::IsMouseClicked(0)) {
|
if (!ImGui::IsItemActive() && ImGui::IsMouseClicked(0)) {
|
||||||
if (strlen(m_renameBuffer) > 0) {
|
if (strlen(m_renameBuffer) > 0) {
|
||||||
m_context->GetSceneManager().RenameEntity(gameObject->GetID(), m_renameBuffer);
|
UndoUtils::ExecuteSceneCommand(*m_context, "Rename Entity", [&]() {
|
||||||
|
m_context->GetSceneManager().RenameEntity(gameObject->GetID(), m_renameBuffer);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
m_renaming = false;
|
m_renaming = false;
|
||||||
m_renamingEntity = nullptr;
|
m_renamingEntity = nullptr;
|
||||||
@@ -195,15 +204,19 @@ void HierarchyPanel::RenderContextMenu(::XCEngine::Components::GameObject* gameO
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (gameObject != nullptr && ImGui::MenuItem("Create Child")) {
|
if (gameObject != nullptr && ImGui::MenuItem("Create Child")) {
|
||||||
auto* child = sceneManager.CreateEntity("GameObject", gameObject);
|
UndoUtils::ExecuteSceneCommand(*m_context, "Create Child", [&]() {
|
||||||
selectionManager.SetSelectedEntity(child->GetID());
|
auto* child = sceneManager.CreateEntity("GameObject", gameObject);
|
||||||
|
selectionManager.SetSelectedEntity(child->GetID());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|
||||||
if (gameObject != nullptr && gameObject->GetParent() != nullptr) {
|
if (gameObject != nullptr && gameObject->GetParent() != nullptr) {
|
||||||
if (ImGui::MenuItem("Detach from Parent")) {
|
if (ImGui::MenuItem("Detach from Parent")) {
|
||||||
sceneManager.MoveEntity(gameObject->GetID(), 0);
|
UndoUtils::ExecuteSceneCommand(*m_context, "Reparent Entity", [&]() {
|
||||||
|
sceneManager.MoveEntity(gameObject->GetID(), 0);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,7 +230,9 @@ void HierarchyPanel::RenderContextMenu(::XCEngine::Components::GameObject* gameO
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui::MenuItem("Delete", "Delete")) {
|
if (ImGui::MenuItem("Delete", "Delete")) {
|
||||||
sceneManager.DeleteEntity(gameObject->GetID());
|
UndoUtils::ExecuteSceneCommand(*m_context, "Delete Entity", [&]() {
|
||||||
|
sceneManager.DeleteEntity(gameObject->GetID());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
@@ -227,11 +242,22 @@ void HierarchyPanel::RenderContextMenu(::XCEngine::Components::GameObject* gameO
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui::MenuItem("Paste", "Ctrl+V", false, sceneManager.HasClipboardData())) {
|
if (ImGui::MenuItem("Paste", "Ctrl+V", false, sceneManager.HasClipboardData())) {
|
||||||
sceneManager.PasteEntity(gameObject->GetID());
|
UndoUtils::ExecuteSceneCommand(*m_context, "Paste Entity", [&]() {
|
||||||
|
const uint64_t newId = sceneManager.PasteEntity(gameObject->GetID());
|
||||||
|
if (newId != 0) {
|
||||||
|
selectionManager.SetSelectedEntity(newId);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui::MenuItem("Duplicate", "Ctrl+D")) {
|
if (ImGui::MenuItem("Duplicate", "Ctrl+D")) {
|
||||||
uint64_t newId = sceneManager.DuplicateEntity(gameObject->GetID());
|
uint64_t newId = 0;
|
||||||
|
UndoUtils::ExecuteSceneCommand(*m_context, "Duplicate Entity", [&]() {
|
||||||
|
newId = sceneManager.DuplicateEntity(gameObject->GetID());
|
||||||
|
if (newId != 0) {
|
||||||
|
selectionManager.SetSelectedEntity(newId);
|
||||||
|
}
|
||||||
|
});
|
||||||
if (newId != 0) {
|
if (newId != 0) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -242,39 +268,53 @@ void HierarchyPanel::RenderCreateMenu(::XCEngine::Components::GameObject* parent
|
|||||||
auto& selectionManager = m_context->GetSelectionManager();
|
auto& selectionManager = m_context->GetSelectionManager();
|
||||||
|
|
||||||
if (ImGui::MenuItem("Empty Object")) {
|
if (ImGui::MenuItem("Empty Object")) {
|
||||||
auto* newEntity = sceneManager.CreateEntity("GameObject", parent);
|
UndoUtils::ExecuteSceneCommand(*m_context, "Create Entity", [&]() {
|
||||||
selectionManager.SetSelectedEntity(newEntity->GetID());
|
auto* newEntity = sceneManager.CreateEntity("GameObject", parent);
|
||||||
|
selectionManager.SetSelectedEntity(newEntity->GetID());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|
||||||
if (ImGui::MenuItem("Camera")) {
|
if (ImGui::MenuItem("Camera")) {
|
||||||
auto* newEntity = sceneManager.CreateEntity("Camera", parent);
|
UndoUtils::ExecuteSceneCommand(*m_context, "Create Camera", [&]() {
|
||||||
newEntity->AddComponent<::XCEngine::Components::CameraComponent>();
|
auto* newEntity = sceneManager.CreateEntity("Camera", parent);
|
||||||
selectionManager.SetSelectedEntity(newEntity->GetID());
|
newEntity->AddComponent<::XCEngine::Components::CameraComponent>();
|
||||||
|
sceneManager.MarkSceneDirty();
|
||||||
|
selectionManager.SetSelectedEntity(newEntity->GetID());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui::MenuItem("Light")) {
|
if (ImGui::MenuItem("Light")) {
|
||||||
auto* newEntity = sceneManager.CreateEntity("Light", parent);
|
UndoUtils::ExecuteSceneCommand(*m_context, "Create Light", [&]() {
|
||||||
newEntity->AddComponent<::XCEngine::Components::LightComponent>();
|
auto* newEntity = sceneManager.CreateEntity("Light", parent);
|
||||||
selectionManager.SetSelectedEntity(newEntity->GetID());
|
newEntity->AddComponent<::XCEngine::Components::LightComponent>();
|
||||||
|
sceneManager.MarkSceneDirty();
|
||||||
|
selectionManager.SetSelectedEntity(newEntity->GetID());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|
||||||
if (ImGui::MenuItem("Cube")) {
|
if (ImGui::MenuItem("Cube")) {
|
||||||
auto* newEntity = sceneManager.CreateEntity("Cube", parent);
|
UndoUtils::ExecuteSceneCommand(*m_context, "Create Cube", [&]() {
|
||||||
selectionManager.SetSelectedEntity(newEntity->GetID());
|
auto* newEntity = sceneManager.CreateEntity("Cube", parent);
|
||||||
|
selectionManager.SetSelectedEntity(newEntity->GetID());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui::MenuItem("Sphere")) {
|
if (ImGui::MenuItem("Sphere")) {
|
||||||
auto* newEntity = sceneManager.CreateEntity("Sphere", parent);
|
UndoUtils::ExecuteSceneCommand(*m_context, "Create Sphere", [&]() {
|
||||||
selectionManager.SetSelectedEntity(newEntity->GetID());
|
auto* newEntity = sceneManager.CreateEntity("Sphere", parent);
|
||||||
|
selectionManager.SetSelectedEntity(newEntity->GetID());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui::MenuItem("Plane")) {
|
if (ImGui::MenuItem("Plane")) {
|
||||||
auto* newEntity = sceneManager.CreateEntity("Plane", parent);
|
UndoUtils::ExecuteSceneCommand(*m_context, "Create Plane", [&]() {
|
||||||
selectionManager.SetSelectedEntity(newEntity->GetID());
|
auto* newEntity = sceneManager.CreateEntity("Plane", parent);
|
||||||
|
selectionManager.SetSelectedEntity(newEntity->GetID());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,16 +342,19 @@ void HierarchyPanel::HandleDragDrop(::XCEngine::Components::GameObject* gameObje
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isValidMove) {
|
if (isValidMove) {
|
||||||
auto* srcTransform = sourceGameObject->GetTransform();
|
UndoUtils::ExecuteSceneCommand(*m_context, "Reparent Entity", [&]() {
|
||||||
Math::Vector3 worldPos = srcTransform->GetPosition();
|
auto* srcTransform = sourceGameObject->GetTransform();
|
||||||
Math::Quaternion worldRot = srcTransform->GetRotation();
|
Math::Vector3 worldPos = srcTransform->GetPosition();
|
||||||
Math::Vector3 worldScale = srcTransform->GetScale();
|
Math::Quaternion worldRot = srcTransform->GetRotation();
|
||||||
|
Math::Vector3 worldScale = srcTransform->GetScale();
|
||||||
|
|
||||||
sceneManager.MoveEntity(sourceGameObject->GetID(), gameObject->GetID());
|
sceneManager.MoveEntity(sourceGameObject->GetID(), gameObject->GetID());
|
||||||
|
|
||||||
srcTransform->SetPosition(worldPos);
|
srcTransform->SetPosition(worldPos);
|
||||||
srcTransform->SetRotation(worldRot);
|
srcTransform->SetRotation(worldRot);
|
||||||
srcTransform->SetScale(worldScale);
|
srcTransform->SetScale(worldScale);
|
||||||
|
sceneManager.MarkSceneDirty();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -328,7 +371,9 @@ void HierarchyPanel::HandleKeyboardShortcuts() {
|
|||||||
if (ImGui::IsWindowFocused()) {
|
if (ImGui::IsWindowFocused()) {
|
||||||
if (ImGui::IsKeyPressed(ImGuiKey_Delete)) {
|
if (ImGui::IsKeyPressed(ImGuiKey_Delete)) {
|
||||||
if (selectedGameObject != nullptr) {
|
if (selectedGameObject != nullptr) {
|
||||||
sceneManager.DeleteEntity(selectedGameObject->GetID());
|
UndoUtils::ExecuteSceneCommand(*m_context, "Delete Entity", [&]() {
|
||||||
|
sceneManager.DeleteEntity(selectedGameObject->GetID());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,13 +396,23 @@ void HierarchyPanel::HandleKeyboardShortcuts() {
|
|||||||
|
|
||||||
if (ImGui::IsKeyPressed(ImGuiKey_V)) {
|
if (ImGui::IsKeyPressed(ImGuiKey_V)) {
|
||||||
if (sceneManager.HasClipboardData()) {
|
if (sceneManager.HasClipboardData()) {
|
||||||
sceneManager.PasteEntity(selectedGameObject ? selectedGameObject->GetID() : 0);
|
UndoUtils::ExecuteSceneCommand(*m_context, "Paste Entity", [&]() {
|
||||||
|
const uint64_t newId = sceneManager.PasteEntity(selectedGameObject ? selectedGameObject->GetID() : 0);
|
||||||
|
if (newId != 0) {
|
||||||
|
selectionManager.SetSelectedEntity(newId);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui::IsKeyPressed(ImGuiKey_D)) {
|
if (ImGui::IsKeyPressed(ImGuiKey_D)) {
|
||||||
if (selectedGameObject != nullptr) {
|
if (selectedGameObject != nullptr) {
|
||||||
sceneManager.DuplicateEntity(selectedGameObject->GetID());
|
UndoUtils::ExecuteSceneCommand(*m_context, "Duplicate Entity", [&]() {
|
||||||
|
const uint64_t newId = sceneManager.DuplicateEntity(selectedGameObject->GetID());
|
||||||
|
if (newId != 0) {
|
||||||
|
selectionManager.SetSelectedEntity(newId);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,14 @@
|
|||||||
#include "Core/IEditorContext.h"
|
#include "Core/IEditorContext.h"
|
||||||
#include "Core/ISceneManager.h"
|
#include "Core/ISceneManager.h"
|
||||||
#include "Core/ISelectionManager.h"
|
#include "Core/ISelectionManager.h"
|
||||||
|
#include "Core/IUndoManager.h"
|
||||||
#include "Core/EventBus.h"
|
#include "Core/EventBus.h"
|
||||||
#include "Core/EditorEvents.h"
|
#include "Core/EditorEvents.h"
|
||||||
#include "ComponentEditors/CameraComponentEditor.h"
|
#include "ComponentEditors/CameraComponentEditor.h"
|
||||||
#include "ComponentEditors/IComponentEditor.h"
|
#include "ComponentEditors/IComponentEditor.h"
|
||||||
#include "ComponentEditors/LightComponentEditor.h"
|
#include "ComponentEditors/LightComponentEditor.h"
|
||||||
#include "ComponentEditors/TransformComponentEditor.h"
|
#include "ComponentEditors/TransformComponentEditor.h"
|
||||||
|
#include "Utils/UndoUtils.h"
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
@@ -25,6 +27,9 @@ InspectorPanel::~InspectorPanel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void InspectorPanel::OnSelectionChanged(const SelectionChangedEvent& event) {
|
void InspectorPanel::OnSelectionChanged(const SelectionChangedEvent& event) {
|
||||||
|
if (m_context && m_context->GetUndoManager().HasPendingInteractiveChange()) {
|
||||||
|
m_context->GetUndoManager().FinalizeInteractiveChange();
|
||||||
|
}
|
||||||
m_selectedEntityId = event.primarySelection;
|
m_selectedEntityId = event.primarySelection;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +95,9 @@ void InspectorPanel::RenderGameObject(::XCEngine::Components::GameObject* gameOb
|
|||||||
strcpy_s(nameBuffer, gameObject->GetName().c_str());
|
strcpy_s(nameBuffer, gameObject->GetName().c_str());
|
||||||
ImGui::InputText("##Name", nameBuffer, sizeof(nameBuffer));
|
ImGui::InputText("##Name", nameBuffer, sizeof(nameBuffer));
|
||||||
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
||||||
m_context->GetSceneManager().RenameEntity(gameObject->GetID(), nameBuffer);
|
UndoUtils::ExecuteSceneCommand(*m_context, "Rename Entity", [&]() {
|
||||||
|
m_context->GetSceneManager().RenameEntity(gameObject->GetID(), nameBuffer);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
@@ -104,6 +111,10 @@ void InspectorPanel::RenderGameObject(::XCEngine::Components::GameObject* gameOb
|
|||||||
for (auto* component : components) {
|
for (auto* component : components) {
|
||||||
RenderComponent(component, gameObject);
|
RenderComponent(component, gameObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_context->GetUndoManager().HasPendingInteractiveChange() && !ImGui::IsAnyItemActive()) {
|
||||||
|
m_context->GetUndoManager().FinalizeInteractiveChange();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InspectorPanel::RenderAddComponentPopup(::XCEngine::Components::GameObject* gameObject) {
|
void InspectorPanel::RenderAddComponentPopup(::XCEngine::Components::GameObject* gameObject) {
|
||||||
@@ -133,8 +144,14 @@ void InspectorPanel::RenderAddComponentPopup(::XCEngine::Components::GameObject*
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui::MenuItem(label.c_str(), nullptr, false, canAdd)) {
|
if (ImGui::MenuItem(label.c_str(), nullptr, false, canAdd)) {
|
||||||
if (editor->AddTo(gameObject)) {
|
bool added = false;
|
||||||
m_context->GetSceneManager().MarkSceneDirty();
|
UndoUtils::ExecuteSceneCommand(*m_context, std::string("Add ") + editor->GetDisplayName() + " Component", [&]() {
|
||||||
|
added = editor->AddTo(gameObject) != nullptr;
|
||||||
|
if (added) {
|
||||||
|
m_context->GetSceneManager().MarkSceneDirty();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (added) {
|
||||||
ImGui::CloseCurrentPopup();
|
ImGui::CloseCurrentPopup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -183,7 +200,7 @@ void InspectorPanel::RenderComponent(::XCEngine::Components::Component* componen
|
|||||||
|
|
||||||
if (open) {
|
if (open) {
|
||||||
if (editor) {
|
if (editor) {
|
||||||
if (editor->Render(component)) {
|
if (editor->Render(component, &m_context->GetUndoManager())) {
|
||||||
m_context->GetSceneManager().MarkSceneDirty();
|
m_context->GetSceneManager().MarkSceneDirty();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -201,8 +218,10 @@ void InspectorPanel::RemoveComponentByType(::XCEngine::Components::Component* co
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
gameObject->RemoveComponent(component);
|
UndoUtils::ExecuteSceneCommand(*m_context, std::string("Remove ") + component->GetName() + " Component", [&]() {
|
||||||
m_context->GetSceneManager().MarkSceneDirty();
|
gameObject->RemoveComponent(component);
|
||||||
|
m_context->GetSceneManager().MarkSceneDirty();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#include "MenuBar.h"
|
#include "MenuBar.h"
|
||||||
#include "Core/IEditorContext.h"
|
#include "Core/IEditorContext.h"
|
||||||
#include "Core/ISceneManager.h"
|
#include "Core/ISceneManager.h"
|
||||||
|
#include "Core/IUndoManager.h"
|
||||||
|
#include "Core/ISelectionManager.h"
|
||||||
#include "Utils/SceneEditorUtils.h"
|
#include "Utils/SceneEditorUtils.h"
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
@@ -29,6 +31,8 @@ void MenuBar::NewScene() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
m_context->GetSceneManager().NewScene();
|
m_context->GetSceneManager().NewScene();
|
||||||
|
m_context->GetSelectionManager().ClearSelection();
|
||||||
|
m_context->GetUndoManager().ClearHistory();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MenuBar::OpenScene() {
|
void MenuBar::OpenScene() {
|
||||||
@@ -40,7 +44,10 @@ void MenuBar::OpenScene() {
|
|||||||
m_context->GetProjectPath(),
|
m_context->GetProjectPath(),
|
||||||
m_context->GetSceneManager().GetCurrentScenePath());
|
m_context->GetSceneManager().GetCurrentScenePath());
|
||||||
if (!filePath.empty()) {
|
if (!filePath.empty()) {
|
||||||
m_context->GetSceneManager().LoadScene(filePath);
|
if (m_context->GetSceneManager().LoadScene(filePath)) {
|
||||||
|
m_context->GetSelectionManager().ClearSelection();
|
||||||
|
m_context->GetUndoManager().ClearHistory();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,7 +85,7 @@ void MenuBar::HandleShortcuts() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& sceneManager = m_context->GetSceneManager();
|
auto& undoManager = m_context->GetUndoManager();
|
||||||
if (ImGui::IsKeyPressed(ImGuiKey_N, false)) {
|
if (ImGui::IsKeyPressed(ImGuiKey_N, false)) {
|
||||||
NewScene();
|
NewScene();
|
||||||
}
|
}
|
||||||
@@ -92,6 +99,20 @@ void MenuBar::HandleShortcuts() {
|
|||||||
SaveScene();
|
SaveScene();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!io.WantTextInput) {
|
||||||
|
if (ImGui::IsKeyPressed(ImGuiKey_Z, false)) {
|
||||||
|
if (io.KeyShift) {
|
||||||
|
if (undoManager.CanRedo()) {
|
||||||
|
undoManager.Redo();
|
||||||
|
}
|
||||||
|
} else if (undoManager.CanUndo()) {
|
||||||
|
undoManager.Undo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ImGui::IsKeyPressed(ImGuiKey_Y, false) && undoManager.CanRedo()) {
|
||||||
|
undoManager.Redo();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MenuBar::ShowFileMenu() {
|
void MenuBar::ShowFileMenu() {
|
||||||
@@ -116,8 +137,15 @@ void MenuBar::ShowFileMenu() {
|
|||||||
|
|
||||||
void MenuBar::ShowEditMenu() {
|
void MenuBar::ShowEditMenu() {
|
||||||
if (ImGui::BeginMenu("Edit")) {
|
if (ImGui::BeginMenu("Edit")) {
|
||||||
if (ImGui::MenuItem("Undo", "Ctrl+Z")) {}
|
auto& undoManager = m_context->GetUndoManager();
|
||||||
if (ImGui::MenuItem("Redo", "Ctrl+Y")) {}
|
const std::string undoLabel = undoManager.CanUndo() ? "Undo " + undoManager.GetUndoLabel() : "Undo";
|
||||||
|
const std::string redoLabel = undoManager.CanRedo() ? "Redo " + undoManager.GetRedoLabel() : "Redo";
|
||||||
|
if (ImGui::MenuItem(undoLabel.c_str(), "Ctrl+Z", false, undoManager.CanUndo())) {
|
||||||
|
undoManager.Undo();
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem(redoLabel.c_str(), "Ctrl+Y", false, undoManager.CanRedo())) {
|
||||||
|
undoManager.Redo();
|
||||||
|
}
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
if (ImGui::MenuItem("Cut", "Ctrl+X")) {}
|
if (ImGui::MenuItem("Cut", "Ctrl+X")) {}
|
||||||
if (ImGui::MenuItem("Copy", "Ctrl+C")) {}
|
if (ImGui::MenuItem("Copy", "Ctrl+C")) {}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
#include "Core/IEditorContext.h"
|
#include "Core/IEditorContext.h"
|
||||||
#include "Core/IProjectManager.h"
|
#include "Core/IProjectManager.h"
|
||||||
#include "Core/ISceneManager.h"
|
#include "Core/ISceneManager.h"
|
||||||
|
#include "Core/IUndoManager.h"
|
||||||
|
#include "Core/ISelectionManager.h"
|
||||||
#include "Core/AssetItem.h"
|
#include "Core/AssetItem.h"
|
||||||
#include "Utils/SceneEditorUtils.h"
|
#include "Utils/SceneEditorUtils.h"
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
@@ -115,7 +117,10 @@ void ProjectPanel::Render() {
|
|||||||
if (item->isFolder) {
|
if (item->isFolder) {
|
||||||
manager.NavigateToFolder(item);
|
manager.NavigateToFolder(item);
|
||||||
} else if (SceneEditorUtils::ConfirmSceneSwitch(*m_context)) {
|
} else if (SceneEditorUtils::ConfirmSceneSwitch(*m_context)) {
|
||||||
m_context->GetSceneManager().LoadScene(item->fullPath);
|
if (m_context->GetSceneManager().LoadScene(item->fullPath)) {
|
||||||
|
m_context->GetSelectionManager().ClearSelection();
|
||||||
|
m_context->GetUndoManager().ClearHistory();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
@@ -287,7 +292,10 @@ void ProjectPanel::RenderAssetItem(const AssetItemPtr& item, int index) {
|
|||||||
manager.NavigateToFolder(item);
|
manager.NavigateToFolder(item);
|
||||||
} else if (item->type == "Scene") {
|
} else if (item->type == "Scene") {
|
||||||
if (SceneEditorUtils::ConfirmSceneSwitch(*m_context)) {
|
if (SceneEditorUtils::ConfirmSceneSwitch(*m_context)) {
|
||||||
m_context->GetSceneManager().LoadScene(item->fullPath);
|
if (m_context->GetSceneManager().LoadScene(item->fullPath)) {
|
||||||
|
m_context->GetSelectionManager().ClearSelection();
|
||||||
|
m_context->GetUndoManager().ClearHistory();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ public:
|
|||||||
|
|
||||||
void Save(const std::string& filePath);
|
void Save(const std::string& filePath);
|
||||||
void Load(const std::string& filePath);
|
void Load(const std::string& filePath);
|
||||||
|
std::string SerializeToString() const;
|
||||||
|
void DeserializeFromString(const std::string& data);
|
||||||
|
|
||||||
Core::Event<GameObject*>& OnGameObjectCreated() { return m_onGameObjectCreated; }
|
Core::Event<GameObject*>& OnGameObjectCreated() { return m_onGameObjectCreated; }
|
||||||
Core::Event<GameObject*>& OnGameObjectDestroyed() { return m_onGameObjectDestroyed; }
|
Core::Event<GameObject*>& OnGameObjectDestroyed() { return m_onGameObjectDestroyed; }
|
||||||
|
|||||||
@@ -264,22 +264,18 @@ void Scene::LateUpdate(float deltaTime) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Scene::Load(const std::string& filePath) {
|
void Scene::DeserializeFromString(const std::string& data) {
|
||||||
std::ifstream file(filePath);
|
|
||||||
if (!file.is_open()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_gameObjects.clear();
|
m_gameObjects.clear();
|
||||||
m_rootGameObjects.clear();
|
m_rootGameObjects.clear();
|
||||||
m_gameObjectIDs.clear();
|
m_gameObjectIDs.clear();
|
||||||
|
|
||||||
std::vector<PendingGameObjectData> pendingObjects;
|
std::vector<PendingGameObjectData> pendingObjects;
|
||||||
|
std::istringstream input(data);
|
||||||
std::string line;
|
std::string line;
|
||||||
PendingGameObjectData* currentObject = nullptr;
|
PendingGameObjectData* currentObject = nullptr;
|
||||||
GameObject::ID maxId = 0;
|
GameObject::ID maxId = 0;
|
||||||
|
|
||||||
while (std::getline(file, line)) {
|
while (std::getline(input, line)) {
|
||||||
if (line.empty() || line[0] == '#') continue;
|
if (line.empty() || line[0] == '#') continue;
|
||||||
|
|
||||||
if (line == "gameobject_begin") {
|
if (line == "gameobject_begin") {
|
||||||
@@ -389,20 +385,39 @@ void Scene::Load(const std::string& filePath) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string Scene::SerializeToString() const {
|
||||||
|
std::ostringstream output;
|
||||||
|
|
||||||
|
output << "# XCEngine Scene File\n";
|
||||||
|
output << "scene=" << EscapeString(m_name) << "\n";
|
||||||
|
output << "active=" << (m_active ? "1" : "0") << "\n\n";
|
||||||
|
|
||||||
|
for (auto* go : GetRootGameObjects()) {
|
||||||
|
SerializeGameObjectRecursive(output, go);
|
||||||
|
output << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return output.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Scene::Load(const std::string& filePath) {
|
||||||
|
std::ifstream file(filePath);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::stringstream buffer;
|
||||||
|
buffer << file.rdbuf();
|
||||||
|
DeserializeFromString(buffer.str());
|
||||||
|
}
|
||||||
|
|
||||||
void Scene::Save(const std::string& filePath) {
|
void Scene::Save(const std::string& filePath) {
|
||||||
std::ofstream file(filePath);
|
std::ofstream file(filePath);
|
||||||
if (!file.is_open()) {
|
if (!file.is_open()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
file << "# XCEngine Scene File\n";
|
file << SerializeToString();
|
||||||
file << "scene=" << EscapeString(m_name) << "\n";
|
|
||||||
file << "active=" << (m_active ? "1" : "0") << "\n\n";
|
|
||||||
|
|
||||||
for (auto* go : GetRootGameObjects()) {
|
|
||||||
SerializeGameObjectRecursive(file, go);
|
|
||||||
file << "\n";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Components
|
} // namespace Components
|
||||||
|
|||||||
@@ -330,6 +330,65 @@ TEST_F(SceneTest, Save_And_Load_PreservesHierarchyAndComponents) {
|
|||||||
std::filesystem::remove(scenePath);
|
std::filesystem::remove(scenePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(SceneTest, SerializeToString_And_DeserializeFromString_PreservesHierarchyAndComponents) {
|
||||||
|
testScene->SetName("Round Trip Scene");
|
||||||
|
testScene->SetActive(false);
|
||||||
|
|
||||||
|
GameObject* parent = testScene->CreateGameObject("Parent");
|
||||||
|
parent->GetTransform()->SetLocalPosition(Vector3(2.0f, 4.0f, 6.0f));
|
||||||
|
parent->GetTransform()->SetLocalScale(Vector3(1.5f, 1.5f, 1.5f));
|
||||||
|
|
||||||
|
auto* light = parent->AddComponent<LightComponent>();
|
||||||
|
light->SetLightType(LightType::Point);
|
||||||
|
light->SetIntensity(4.0f);
|
||||||
|
light->SetRange(20.0f);
|
||||||
|
light->SetCastsShadows(true);
|
||||||
|
|
||||||
|
GameObject* child = testScene->CreateGameObject("Child Camera", parent);
|
||||||
|
child->GetTransform()->SetLocalPosition(Vector3(1.0f, 0.0f, -3.0f));
|
||||||
|
|
||||||
|
auto* camera = child->AddComponent<CameraComponent>();
|
||||||
|
camera->SetProjectionType(CameraProjectionType::Perspective);
|
||||||
|
camera->SetFieldOfView(72.0f);
|
||||||
|
camera->SetNearClipPlane(0.2f);
|
||||||
|
camera->SetFarClipPlane(512.0f);
|
||||||
|
camera->SetPrimary(true);
|
||||||
|
|
||||||
|
const std::string serialized = testScene->SerializeToString();
|
||||||
|
|
||||||
|
Scene loadedScene;
|
||||||
|
loadedScene.DeserializeFromString(serialized);
|
||||||
|
|
||||||
|
EXPECT_EQ(loadedScene.GetName(), "Round Trip Scene");
|
||||||
|
EXPECT_FALSE(loadedScene.IsActive());
|
||||||
|
EXPECT_EQ(loadedScene.GetRootGameObjects().size(), 1u);
|
||||||
|
|
||||||
|
GameObject* loadedParent = loadedScene.Find("Parent");
|
||||||
|
GameObject* loadedChild = loadedScene.Find("Child Camera");
|
||||||
|
ASSERT_NE(loadedParent, nullptr);
|
||||||
|
ASSERT_NE(loadedChild, nullptr);
|
||||||
|
|
||||||
|
EXPECT_EQ(loadedChild->GetParent(), loadedParent);
|
||||||
|
EXPECT_EQ(loadedParent->GetTransform()->GetLocalPosition(), Vector3(2.0f, 4.0f, 6.0f));
|
||||||
|
EXPECT_EQ(loadedParent->GetTransform()->GetLocalScale(), Vector3(1.5f, 1.5f, 1.5f));
|
||||||
|
EXPECT_EQ(loadedChild->GetTransform()->GetPosition(), Vector3(3.5f, 4.0f, 1.5f));
|
||||||
|
|
||||||
|
auto* loadedLight = loadedParent->GetComponent<LightComponent>();
|
||||||
|
ASSERT_NE(loadedLight, nullptr);
|
||||||
|
EXPECT_EQ(loadedLight->GetLightType(), LightType::Point);
|
||||||
|
EXPECT_FLOAT_EQ(loadedLight->GetIntensity(), 4.0f);
|
||||||
|
EXPECT_FLOAT_EQ(loadedLight->GetRange(), 20.0f);
|
||||||
|
EXPECT_TRUE(loadedLight->GetCastsShadows());
|
||||||
|
|
||||||
|
auto* loadedCamera = loadedChild->GetComponent<CameraComponent>();
|
||||||
|
ASSERT_NE(loadedCamera, nullptr);
|
||||||
|
EXPECT_EQ(loadedCamera->GetProjectionType(), CameraProjectionType::Perspective);
|
||||||
|
EXPECT_FLOAT_EQ(loadedCamera->GetFieldOfView(), 72.0f);
|
||||||
|
EXPECT_FLOAT_EQ(loadedCamera->GetNearClipPlane(), 0.2f);
|
||||||
|
EXPECT_FLOAT_EQ(loadedCamera->GetFarClipPlane(), 512.0f);
|
||||||
|
EXPECT_TRUE(loadedCamera->IsPrimary());
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(SceneTest, Save_ContainsHierarchyAndComponentEntries) {
|
TEST_F(SceneTest, Save_ContainsHierarchyAndComponentEntries) {
|
||||||
GameObject* parent = testScene->CreateGameObject("Parent");
|
GameObject* parent = testScene->CreateGameObject("Parent");
|
||||||
GameObject* child = testScene->CreateGameObject("Child", parent);
|
GameObject* child = testScene->CreateGameObject("Child", parent);
|
||||||
|
|||||||
Reference in New Issue
Block a user