Fix editor scene persistence and XC scene workflow
This commit is contained in:
@@ -8,15 +8,69 @@
|
||||
#include <imgui_impl_win32.h>
|
||||
#include <imgui_impl_dx12.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <filesystem>
|
||||
#include <stdio.h>
|
||||
#include <windows.h>
|
||||
#include <string>
|
||||
|
||||
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
||||
|
||||
namespace {
|
||||
|
||||
std::string WideToUtf8(const std::wstring& value) {
|
||||
if (value.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
int len = WideCharToMultiByte(CP_UTF8, 0, value.c_str(), -1, nullptr, 0, nullptr, nullptr);
|
||||
if (len <= 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string result(len - 1, '\0');
|
||||
WideCharToMultiByte(CP_UTF8, 0, value.c_str(), -1, &result[0], len, nullptr, nullptr);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::wstring Utf8ToWide(const std::string& value) {
|
||||
if (value.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
int len = MultiByteToWideChar(CP_UTF8, 0, value.c_str(), -1, nullptr, 0);
|
||||
if (len <= 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::wstring result(len - 1, L'\0');
|
||||
MultiByteToWideChar(CP_UTF8, 0, value.c_str(), -1, &result[0], len);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string GetExecutableDirectoryUtf8() {
|
||||
wchar_t exePath[MAX_PATH];
|
||||
GetModuleFileNameW(nullptr, exePath, MAX_PATH);
|
||||
|
||||
std::wstring exeDirW(exePath);
|
||||
const size_t pos = exeDirW.find_last_of(L"\\/");
|
||||
if (pos != std::wstring::npos) {
|
||||
exeDirW = exeDirW.substr(0, pos);
|
||||
}
|
||||
|
||||
return WideToUtf8(exeDirW);
|
||||
}
|
||||
|
||||
std::string GetExecutableLogPath(const char* fileName) {
|
||||
return GetExecutableDirectoryUtf8() + "\\" + fileName;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
static LONG WINAPI GlobalExceptionFilter(EXCEPTION_POINTERS* exceptionPointers) {
|
||||
const char* logPath = "D:\\Xuanchi\\Main\\XCEngine\\editor\\bin\\Release\\crash.log";
|
||||
|
||||
FILE* f = fopen(logPath, "a");
|
||||
const std::string logPath = GetExecutableLogPath("crash.log");
|
||||
|
||||
FILE* f = nullptr;
|
||||
fopen_s(&f, logPath.c_str(), "a");
|
||||
if (f) {
|
||||
fprintf(f, "[CRASH] ExceptionCode=0x%08X, Address=0x%p\n",
|
||||
exceptionPointers->ExceptionRecord->ExceptionCode,
|
||||
@@ -45,20 +99,7 @@ bool Application::Initialize(HWND hwnd) {
|
||||
|
||||
// Redirect stderr to log file to capture ImGui errors
|
||||
{
|
||||
wchar_t exePath[MAX_PATH];
|
||||
GetModuleFileNameW(nullptr, exePath, MAX_PATH);
|
||||
std::wstring exeDirW(exePath);
|
||||
size_t pos = exeDirW.find_last_of(L"\\/");
|
||||
if (pos != std::wstring::npos) {
|
||||
exeDirW = exeDirW.substr(0, pos);
|
||||
}
|
||||
std::string exeDir;
|
||||
int len = WideCharToMultiByte(CP_UTF8, 0, exeDirW.c_str(), -1, nullptr, 0, nullptr, nullptr);
|
||||
if (len > 0) {
|
||||
exeDir.resize(len - 1);
|
||||
WideCharToMultiByte(CP_UTF8, 0, exeDirW.c_str(), -1, &exeDir[0], len, nullptr, nullptr);
|
||||
}
|
||||
std::string stderrPath = exeDir + "\\stderr.log";
|
||||
const std::string stderrPath = GetExecutableLogPath("stderr.log");
|
||||
freopen(stderrPath.c_str(), "w", stderr);
|
||||
|
||||
fprintf(stderr, "[TEST] stderr redirection test - this should appear in stderr.log\n");
|
||||
@@ -70,19 +111,7 @@ bool Application::Initialize(HWND hwnd) {
|
||||
Debug::Logger::Get().AddSink(std::make_unique<Debug::EditorConsoleSink>());
|
||||
|
||||
// Get exe directory for log file path
|
||||
wchar_t exePath[MAX_PATH];
|
||||
GetModuleFileNameW(nullptr, exePath, MAX_PATH);
|
||||
std::wstring exeDirW(exePath);
|
||||
size_t pos = exeDirW.find_last_of(L"\\/");
|
||||
if (pos != std::wstring::npos) {
|
||||
exeDirW = exeDirW.substr(0, pos);
|
||||
}
|
||||
std::string exeDir;
|
||||
int len = WideCharToMultiByte(CP_UTF8, 0, exeDirW.c_str(), -1, nullptr, 0, nullptr, nullptr);
|
||||
if (len > 0) {
|
||||
exeDir.resize(len - 1);
|
||||
WideCharToMultiByte(CP_UTF8, 0, exeDirW.c_str(), -1, &exeDir[0], len, nullptr, nullptr);
|
||||
}
|
||||
const std::string exeDir = GetExecutableDirectoryUtf8();
|
||||
std::string logPath = exeDir + "\\editor.log";
|
||||
Debug::Logger::Get().AddSink(std::make_unique<Debug::FileLogSink>(logPath.c_str()));
|
||||
Debug::Logger::Get().Info(Debug::LogCategory::General, "Editor Application starting...");
|
||||
@@ -154,6 +183,7 @@ void Application::Render() {
|
||||
ImGui::NewFrame();
|
||||
|
||||
m_layerStack.onImGuiRender();
|
||||
UpdateWindowTitle();
|
||||
|
||||
ImGui::Render();
|
||||
|
||||
@@ -198,6 +228,34 @@ void Application::Render() {
|
||||
}
|
||||
}
|
||||
|
||||
void Application::UpdateWindowTitle() {
|
||||
if (!m_hwnd || !m_editorContext) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& sceneManager = m_editorContext->GetSceneManager();
|
||||
std::string sceneName = sceneManager.HasActiveScene() ? sceneManager.GetCurrentSceneName() : "No Scene";
|
||||
if (sceneName.empty()) {
|
||||
sceneName = "Untitled Scene";
|
||||
}
|
||||
|
||||
if (sceneManager.IsSceneDirty()) {
|
||||
sceneName += " *";
|
||||
}
|
||||
if (sceneManager.GetCurrentScenePath().empty()) {
|
||||
sceneName += " (Unsaved)";
|
||||
} else {
|
||||
sceneName += " - ";
|
||||
sceneName += std::filesystem::path(sceneManager.GetCurrentScenePath()).filename().string();
|
||||
}
|
||||
|
||||
const std::wstring title = Utf8ToWide(sceneName + " - XCVolumeRenderer - Unity Style Editor");
|
||||
if (title != m_lastWindowTitle) {
|
||||
SetWindowTextW(m_hwnd, title.c_str());
|
||||
m_lastWindowTitle = title;
|
||||
}
|
||||
}
|
||||
|
||||
void Application::OnResize(int width, int height) {
|
||||
if (width <= 0 || height <= 0) return;
|
||||
|
||||
@@ -303,4 +361,4 @@ void Application::CleanupRenderTarget() {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <imgui.h>
|
||||
#include <d3d12.h>
|
||||
#include <dxgi1_6.h>
|
||||
@@ -23,6 +24,7 @@ public:
|
||||
void Shutdown();
|
||||
void Render();
|
||||
void OnResize(int width, int height);
|
||||
HWND GetWindowHandle() const { return m_hwnd; }
|
||||
|
||||
IEditorContext& GetEditorContext() const { return *m_editorContext; }
|
||||
|
||||
@@ -33,6 +35,7 @@ private:
|
||||
bool CreateDevice();
|
||||
bool CreateRenderTarget();
|
||||
void CleanupRenderTarget();
|
||||
void UpdateWindowTitle();
|
||||
|
||||
HWND m_hwnd = nullptr;
|
||||
int m_width = 1280;
|
||||
@@ -54,7 +57,8 @@ private:
|
||||
Core::LayerStack m_layerStack;
|
||||
EditorLayer* m_editorLayer = nullptr;
|
||||
std::shared_ptr<IEditorContext> m_editorContext;
|
||||
std::wstring m_lastWindowTitle;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
108
editor/src/ComponentEditors/CameraComponentEditor.h
Normal file
108
editor/src/ComponentEditors/CameraComponentEditor.h
Normal file
@@ -0,0 +1,108 @@
|
||||
#pragma once
|
||||
|
||||
#include "IComponentEditor.h"
|
||||
#include "UI/UI.h"
|
||||
|
||||
#include <XCEngine/Components/CameraComponent.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
|
||||
class CameraComponentEditor : public IComponentEditor {
|
||||
public:
|
||||
const char* GetDisplayName() const override {
|
||||
return "Camera";
|
||||
}
|
||||
|
||||
bool CanEdit(::XCEngine::Components::Component* component) const override {
|
||||
return dynamic_cast<::XCEngine::Components::CameraComponent*>(component) != nullptr;
|
||||
}
|
||||
|
||||
bool Render(::XCEngine::Components::Component* component) override {
|
||||
auto* camera = dynamic_cast<::XCEngine::Components::CameraComponent*>(component);
|
||||
if (!camera) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int projectionType = static_cast<int>(camera->GetProjectionType());
|
||||
const char* projectionLabels[] = { "Perspective", "Orthographic" };
|
||||
bool changed = false;
|
||||
if (ImGui::Combo("Projection", &projectionType, projectionLabels, 2)) {
|
||||
camera->SetProjectionType(static_cast<::XCEngine::Components::CameraProjectionType>(projectionType));
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (camera->GetProjectionType() == ::XCEngine::Components::CameraProjectionType::Perspective) {
|
||||
float fieldOfView = camera->GetFieldOfView();
|
||||
if (UI::DrawSliderFloat("Field Of View", fieldOfView, 1.0f, 179.0f, 100.0f, "%.1f")) {
|
||||
camera->SetFieldOfView(fieldOfView);
|
||||
changed = true;
|
||||
}
|
||||
} else {
|
||||
float orthographicSize = camera->GetOrthographicSize();
|
||||
if (UI::DrawFloat("Orthographic Size", orthographicSize, 100.0f, 0.1f, 0.001f)) {
|
||||
camera->SetOrthographicSize(orthographicSize);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
float nearClip = camera->GetNearClipPlane();
|
||||
if (UI::DrawFloat("Near Clip", nearClip, 100.0f, 0.01f, 0.001f)) {
|
||||
camera->SetNearClipPlane(nearClip);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
float farClip = camera->GetFarClipPlane();
|
||||
if (UI::DrawFloat("Far Clip", farClip, 100.0f, 0.1f, nearClip + 0.001f)) {
|
||||
camera->SetFarClipPlane(farClip);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
float depth = camera->GetDepth();
|
||||
if (UI::DrawFloat("Depth", depth, 100.0f, 0.1f)) {
|
||||
camera->SetDepth(depth);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
bool primary = camera->IsPrimary();
|
||||
if (UI::DrawBool("Primary", primary)) {
|
||||
camera->SetPrimary(primary);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
float clearColor[4] = {
|
||||
camera->GetClearColor().r,
|
||||
camera->GetClearColor().g,
|
||||
camera->GetClearColor().b,
|
||||
camera->GetClearColor().a
|
||||
};
|
||||
if (UI::DrawColor4("Clear Color", clearColor)) {
|
||||
camera->SetClearColor(::XCEngine::Math::Color(clearColor[0], clearColor[1], clearColor[2], clearColor[3]));
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool CanAddTo(::XCEngine::Components::GameObject* gameObject) const override {
|
||||
return gameObject && !gameObject->GetComponent<::XCEngine::Components::CameraComponent>();
|
||||
}
|
||||
|
||||
const char* GetAddDisabledReason(::XCEngine::Components::GameObject* gameObject) const override {
|
||||
if (!gameObject) {
|
||||
return "Invalid";
|
||||
}
|
||||
return gameObject->GetComponent<::XCEngine::Components::CameraComponent>() ? "Already Added" : nullptr;
|
||||
}
|
||||
|
||||
::XCEngine::Components::Component* AddTo(::XCEngine::Components::GameObject* gameObject) const override {
|
||||
return gameObject ? gameObject->AddComponent<::XCEngine::Components::CameraComponent>() : nullptr;
|
||||
}
|
||||
|
||||
bool CanRemove(::XCEngine::Components::Component* component) const override {
|
||||
return CanEdit(component);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
35
editor/src/ComponentEditors/IComponentEditor.h
Normal file
35
editor/src/ComponentEditors/IComponentEditor.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Components/Component.h>
|
||||
#include <XCEngine/Components/GameObject.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
|
||||
class IComponentEditor {
|
||||
public:
|
||||
virtual ~IComponentEditor() = default;
|
||||
|
||||
virtual const char* GetDisplayName() const = 0;
|
||||
virtual bool CanEdit(::XCEngine::Components::Component* component) const = 0;
|
||||
virtual bool Render(::XCEngine::Components::Component* component) = 0;
|
||||
|
||||
virtual bool ShowInAddComponentMenu() const { return true; }
|
||||
virtual bool CanAddTo(::XCEngine::Components::GameObject* gameObject) const { return false; }
|
||||
virtual const char* GetAddDisabledReason(::XCEngine::Components::GameObject* gameObject) const {
|
||||
(void)gameObject;
|
||||
return nullptr;
|
||||
}
|
||||
virtual ::XCEngine::Components::Component* AddTo(::XCEngine::Components::GameObject* gameObject) const {
|
||||
(void)gameObject;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
virtual bool CanRemove(::XCEngine::Components::Component* component) const {
|
||||
(void)component;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
98
editor/src/ComponentEditors/LightComponentEditor.h
Normal file
98
editor/src/ComponentEditors/LightComponentEditor.h
Normal file
@@ -0,0 +1,98 @@
|
||||
#pragma once
|
||||
|
||||
#include "IComponentEditor.h"
|
||||
#include "UI/UI.h"
|
||||
|
||||
#include <XCEngine/Components/LightComponent.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
|
||||
class LightComponentEditor : public IComponentEditor {
|
||||
public:
|
||||
const char* GetDisplayName() const override {
|
||||
return "Light";
|
||||
}
|
||||
|
||||
bool CanEdit(::XCEngine::Components::Component* component) const override {
|
||||
return dynamic_cast<::XCEngine::Components::LightComponent*>(component) != nullptr;
|
||||
}
|
||||
|
||||
bool Render(::XCEngine::Components::Component* component) override {
|
||||
auto* light = dynamic_cast<::XCEngine::Components::LightComponent*>(component);
|
||||
if (!light) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int lightType = static_cast<int>(light->GetLightType());
|
||||
const char* lightTypeLabels[] = { "Directional", "Point", "Spot" };
|
||||
bool changed = false;
|
||||
if (ImGui::Combo("Type", &lightType, lightTypeLabels, 3)) {
|
||||
light->SetLightType(static_cast<::XCEngine::Components::LightType>(lightType));
|
||||
changed = true;
|
||||
}
|
||||
|
||||
float color[4] = {
|
||||
light->GetColor().r,
|
||||
light->GetColor().g,
|
||||
light->GetColor().b,
|
||||
light->GetColor().a
|
||||
};
|
||||
if (UI::DrawColor4("Color", color)) {
|
||||
light->SetColor(::XCEngine::Math::Color(color[0], color[1], color[2], color[3]));
|
||||
changed = true;
|
||||
}
|
||||
|
||||
float intensity = light->GetIntensity();
|
||||
if (UI::DrawFloat("Intensity", intensity, 100.0f, 0.1f, 0.0f)) {
|
||||
light->SetIntensity(intensity);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (light->GetLightType() != ::XCEngine::Components::LightType::Directional) {
|
||||
float range = light->GetRange();
|
||||
if (UI::DrawFloat("Range", range, 100.0f, 0.1f, 0.001f)) {
|
||||
light->SetRange(range);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (light->GetLightType() == ::XCEngine::Components::LightType::Spot) {
|
||||
float spotAngle = light->GetSpotAngle();
|
||||
if (UI::DrawSliderFloat("Spot Angle", spotAngle, 1.0f, 179.0f, 100.0f, "%.1f")) {
|
||||
light->SetSpotAngle(spotAngle);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool castsShadows = light->GetCastsShadows();
|
||||
if (UI::DrawBool("Cast Shadows", castsShadows)) {
|
||||
light->SetCastsShadows(castsShadows);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool CanAddTo(::XCEngine::Components::GameObject* gameObject) const override {
|
||||
return gameObject && !gameObject->GetComponent<::XCEngine::Components::LightComponent>();
|
||||
}
|
||||
|
||||
const char* GetAddDisabledReason(::XCEngine::Components::GameObject* gameObject) const override {
|
||||
if (!gameObject) {
|
||||
return "Invalid";
|
||||
}
|
||||
return gameObject->GetComponent<::XCEngine::Components::LightComponent>() ? "Already Added" : nullptr;
|
||||
}
|
||||
|
||||
::XCEngine::Components::Component* AddTo(::XCEngine::Components::GameObject* gameObject) const override {
|
||||
return gameObject ? gameObject->AddComponent<::XCEngine::Components::LightComponent>() : nullptr;
|
||||
}
|
||||
|
||||
bool CanRemove(::XCEngine::Components::Component* component) const override {
|
||||
return CanEdit(component);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
67
editor/src/ComponentEditors/TransformComponentEditor.h
Normal file
67
editor/src/ComponentEditors/TransformComponentEditor.h
Normal file
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#include "IComponentEditor.h"
|
||||
#include "UI/UI.h"
|
||||
|
||||
#include <XCEngine/Components/TransformComponent.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
|
||||
class TransformComponentEditor : public IComponentEditor {
|
||||
public:
|
||||
const char* GetDisplayName() const override {
|
||||
return "Transform";
|
||||
}
|
||||
|
||||
bool CanEdit(::XCEngine::Components::Component* component) const override {
|
||||
return dynamic_cast<::XCEngine::Components::TransformComponent*>(component) != nullptr;
|
||||
}
|
||||
|
||||
bool Render(::XCEngine::Components::Component* component) override {
|
||||
auto* transform = dynamic_cast<::XCEngine::Components::TransformComponent*>(component);
|
||||
if (!transform) {
|
||||
return false;
|
||||
}
|
||||
|
||||
::XCEngine::Math::Vector3 position = transform->GetLocalPosition();
|
||||
::XCEngine::Math::Vector3 rotation = transform->GetLocalEulerAngles();
|
||||
::XCEngine::Math::Vector3 scale = transform->GetLocalScale();
|
||||
bool changed = false;
|
||||
|
||||
if (UI::DrawVec3("Position", position, 0.0f, 80.0f, 0.1f)) {
|
||||
transform->SetLocalPosition(position);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (UI::DrawVec3("Rotation", rotation, 0.0f, 80.0f, 1.0f)) {
|
||||
transform->SetLocalEulerAngles(rotation);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (UI::DrawVec3("Scale", scale, 1.0f, 80.0f, 0.1f)) {
|
||||
transform->SetLocalScale(scale);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool CanAddTo(::XCEngine::Components::GameObject* gameObject) const override {
|
||||
(void)gameObject;
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* GetAddDisabledReason(::XCEngine::Components::GameObject* gameObject) const override {
|
||||
(void)gameObject;
|
||||
return "Built-in";
|
||||
}
|
||||
|
||||
bool CanRemove(::XCEngine::Components::Component* component) const override {
|
||||
(void)component;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -3,14 +3,22 @@
|
||||
namespace XCEngine {
|
||||
namespace Debug {
|
||||
|
||||
EditorConsoleSink* EditorConsoleSink::s_instance = nullptr;
|
||||
|
||||
EditorConsoleSink* EditorConsoleSink::GetInstance() {
|
||||
static EditorConsoleSink instance;
|
||||
return &instance;
|
||||
static EditorConsoleSink fallbackInstance;
|
||||
return s_instance ? s_instance : &fallbackInstance;
|
||||
}
|
||||
|
||||
EditorConsoleSink::EditorConsoleSink() = default;
|
||||
EditorConsoleSink::EditorConsoleSink() {
|
||||
s_instance = this;
|
||||
}
|
||||
|
||||
EditorConsoleSink::~EditorConsoleSink() = default;
|
||||
EditorConsoleSink::~EditorConsoleSink() {
|
||||
if (s_instance == this) {
|
||||
s_instance = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorConsoleSink::Log(const LogEntry& entry) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
@@ -26,7 +34,8 @@ void EditorConsoleSink::Log(const LogEntry& entry) {
|
||||
void EditorConsoleSink::Flush() {
|
||||
}
|
||||
|
||||
const std::vector<LogEntry>& EditorConsoleSink::GetLogs() const {
|
||||
std::vector<LogEntry> EditorConsoleSink::GetLogs() const {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
return m_logs;
|
||||
}
|
||||
|
||||
@@ -40,4 +49,4 @@ void EditorConsoleSink::SetCallback(std::function<void()> callback) {
|
||||
}
|
||||
|
||||
} // namespace Debug
|
||||
} // namespace XCEngine
|
||||
} // namespace XCEngine
|
||||
|
||||
@@ -19,7 +19,7 @@ public:
|
||||
void Log(const LogEntry& entry) override;
|
||||
void Flush() override;
|
||||
|
||||
const std::vector<LogEntry>& GetLogs() const;
|
||||
std::vector<LogEntry> GetLogs() const;
|
||||
void Clear();
|
||||
void SetCallback(std::function<void()> callback);
|
||||
|
||||
@@ -27,8 +27,9 @@ private:
|
||||
mutable std::mutex m_mutex;
|
||||
std::vector<LogEntry> m_logs;
|
||||
std::function<void()> m_callback;
|
||||
static EditorConsoleSink* s_instance;
|
||||
static constexpr size_t MAX_LOGS = 1000;
|
||||
};
|
||||
|
||||
} // namespace Debug
|
||||
} // namespace XCEngine
|
||||
} // namespace XCEngine
|
||||
|
||||
@@ -19,7 +19,7 @@ public:
|
||||
EditorContext()
|
||||
: m_eventBus(std::make_unique<EventBus>())
|
||||
, m_selectionManager(std::make_unique<SelectionManager>(*m_eventBus))
|
||||
, m_sceneManager(std::make_unique<SceneManager>())
|
||||
, m_sceneManager(std::make_unique<SceneManager>(m_eventBus.get()))
|
||||
, m_projectManager(std::make_unique<ProjectManager>()) {
|
||||
|
||||
m_entityDeletedHandlerId = m_eventBus->Subscribe<EntityDeletedEvent>([this](const EntityDeletedEvent& event) {
|
||||
|
||||
@@ -6,24 +6,30 @@
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
|
||||
template<typename T>
|
||||
struct EventTypeId {
|
||||
static uint32_t Get() {
|
||||
static const uint32_t id = s_nextId++;
|
||||
return id;
|
||||
class EventTypeRegistry {
|
||||
public:
|
||||
static uint32_t NextId() {
|
||||
return s_nextId.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
private:
|
||||
static uint32_t s_nextId;
|
||||
inline static std::atomic<uint32_t> s_nextId{0};
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
uint32_t EventTypeId<T>::s_nextId = 0;
|
||||
struct EventTypeId {
|
||||
static uint32_t Get() {
|
||||
static const uint32_t id = EventTypeRegistry::NextId();
|
||||
return id;
|
||||
}
|
||||
};
|
||||
|
||||
class EventBus {
|
||||
public:
|
||||
|
||||
@@ -22,8 +22,18 @@ public:
|
||||
virtual ::XCEngine::Components::GameObject::ID DuplicateEntity(::XCEngine::Components::GameObject::ID id) = 0;
|
||||
virtual void MoveEntity(::XCEngine::Components::GameObject::ID id, ::XCEngine::Components::GameObject::ID newParent) = 0;
|
||||
virtual bool HasClipboardData() const = 0;
|
||||
virtual void NewScene(const std::string& name = "Untitled Scene") = 0;
|
||||
virtual bool LoadScene(const std::string& filePath) = 0;
|
||||
virtual bool SaveScene() = 0;
|
||||
virtual bool SaveSceneAs(const std::string& filePath) = 0;
|
||||
virtual bool LoadStartupScene(const std::string& projectPath) = 0;
|
||||
virtual bool HasActiveScene() const = 0;
|
||||
virtual bool IsSceneDirty() const = 0;
|
||||
virtual void MarkSceneDirty() = 0;
|
||||
virtual const std::string& GetCurrentScenePath() const = 0;
|
||||
virtual const std::string& GetCurrentSceneName() const = 0;
|
||||
virtual void CreateDemoScene() = 0;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "panels/ProjectPanel.h"
|
||||
#include "Core/IEditorContext.h"
|
||||
#include "Core/EditorContext.h"
|
||||
#include <filesystem>
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
|
||||
@@ -41,6 +42,10 @@ void EditorLayer::onAttach() {
|
||||
m_consolePanel->SetContext(m_context.get());
|
||||
m_projectPanel->SetContext(m_context.get());
|
||||
|
||||
m_projectPanel->Initialize(m_context->GetProjectPath());
|
||||
m_context->GetSceneManager().LoadStartupScene(m_context->GetProjectPath());
|
||||
m_context->GetProjectManager().RefreshCurrentFolder();
|
||||
|
||||
m_menuBar->OnAttach();
|
||||
m_hierarchyPanel->OnAttach();
|
||||
m_sceneViewPanel->OnAttach();
|
||||
@@ -48,11 +53,18 @@ void EditorLayer::onAttach() {
|
||||
m_inspectorPanel->OnAttach();
|
||||
m_consolePanel->OnAttach();
|
||||
m_projectPanel->OnAttach();
|
||||
|
||||
m_projectPanel->Initialize(m_context->GetProjectPath());
|
||||
}
|
||||
|
||||
void EditorLayer::onDetach() {
|
||||
auto& sceneManager = m_context->GetSceneManager();
|
||||
if (sceneManager.HasActiveScene() && sceneManager.IsSceneDirty()) {
|
||||
if (!sceneManager.SaveScene()) {
|
||||
const std::string fallbackPath =
|
||||
(std::filesystem::path(m_context->GetProjectPath()) / "Assets" / "Scenes" / "Main.xc").string();
|
||||
sceneManager.SaveSceneAs(fallbackPath);
|
||||
}
|
||||
}
|
||||
|
||||
m_menuBar->OnDetach();
|
||||
m_hierarchyPanel->OnDetach();
|
||||
m_sceneViewPanel->OnDetach();
|
||||
|
||||
@@ -89,7 +89,6 @@ void ProjectManager::Initialize(const std::string& projectPath) {
|
||||
std::ofstream((assetsPath / L"Textures" / L"Stone.png").wstring());
|
||||
std::ofstream((assetsPath / L"Models" / L"Character.fbx").wstring());
|
||||
std::ofstream((assetsPath / L"Scripts" / L"PlayerController.cs").wstring());
|
||||
std::ofstream((assetsPath / L"Scenes" / L"Main.unity").wstring());
|
||||
}
|
||||
|
||||
m_rootFolder = ScanDirectory(assetsPath.wstring());
|
||||
@@ -227,7 +226,7 @@ AssetItemPtr ProjectManager::CreateAssetItem(const std::wstring& path, const std
|
||||
item->type = "Script";
|
||||
} else if (ext == L".mat") {
|
||||
item->type = "Material";
|
||||
} else if (ext == L".unity" || ext == L".scene") {
|
||||
} else if (ext == L".xc" || ext == L".unity" || ext == L".scene") {
|
||||
item->type = "Scene";
|
||||
} else if (ext == L".prefab") {
|
||||
item->type = "Prefab";
|
||||
@@ -240,4 +239,4 @@ AssetItemPtr ProjectManager::CreateAssetItem(const std::wstring& path, const std
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,106 @@
|
||||
#include "SceneManager.h"
|
||||
#include "Core/EventBus.h"
|
||||
#include "Core/EditorEvents.h"
|
||||
#include <XCEngine/Components/CameraComponent.h>
|
||||
#include <XCEngine/Components/LightComponent.h>
|
||||
#include <XCEngine/Components/AudioSourceComponent.h>
|
||||
#include <XCEngine/Components/AudioListenerComponent.h>
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
|
||||
SceneManager::SceneManager() = default;
|
||||
namespace {
|
||||
|
||||
std::pair<std::string, std::string> SerializeComponent(const ::XCEngine::Components::Component* component) {
|
||||
std::ostringstream payload;
|
||||
component->Serialize(payload);
|
||||
return { component->GetName(), payload.str() };
|
||||
}
|
||||
|
||||
::XCEngine::Components::Component* CreateComponentByName(
|
||||
::XCEngine::Components::GameObject* gameObject,
|
||||
const std::string& componentName) {
|
||||
if (!gameObject) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (componentName == "Camera") {
|
||||
return gameObject->AddComponent<::XCEngine::Components::CameraComponent>();
|
||||
}
|
||||
if (componentName == "Light") {
|
||||
return gameObject->AddComponent<::XCEngine::Components::LightComponent>();
|
||||
}
|
||||
if (componentName == "AudioSource") {
|
||||
return gameObject->AddComponent<::XCEngine::Components::AudioSourceComponent>();
|
||||
}
|
||||
if (componentName == "AudioListener") {
|
||||
return gameObject->AddComponent<::XCEngine::Components::AudioListenerComponent>();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SceneManager::SceneManager(EventBus* eventBus)
|
||||
: m_eventBus(eventBus) {}
|
||||
|
||||
void SceneManager::SetSceneDirty(bool dirty) {
|
||||
m_isSceneDirty = dirty;
|
||||
}
|
||||
|
||||
void SceneManager::MarkSceneDirty() {
|
||||
SetSceneDirty(true);
|
||||
}
|
||||
|
||||
::XCEngine::Components::GameObject* SceneManager::CreateEntity(const std::string& name, ::XCEngine::Components::GameObject* parent) {
|
||||
if (!m_scene) {
|
||||
m_scene = new ::XCEngine::Components::Scene("EditorScene");
|
||||
m_scene = std::make_unique<::XCEngine::Components::Scene>("EditorScene");
|
||||
}
|
||||
|
||||
::XCEngine::Components::GameObject* entity = m_scene->CreateGameObject(name, parent);
|
||||
|
||||
if (parent == nullptr) {
|
||||
m_rootEntities.push_back(entity);
|
||||
const auto entityId = entity->GetID();
|
||||
SyncRootEntities();
|
||||
SetSceneDirty(true);
|
||||
|
||||
OnEntityCreated.Invoke(entityId);
|
||||
OnSceneChanged.Invoke();
|
||||
|
||||
if (m_eventBus) {
|
||||
m_eventBus->Publish(EntityCreatedEvent{ entityId });
|
||||
m_eventBus->Publish(SceneChangedEvent{});
|
||||
}
|
||||
|
||||
OnEntityCreated.Invoke(entity->GetID());
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
void SceneManager::DeleteEntity(::XCEngine::Components::GameObject::ID id) {
|
||||
if (!m_scene) return;
|
||||
|
||||
::XCEngine::Components::GameObject* entity = m_scene->Find(std::to_string(id));
|
||||
::XCEngine::Components::GameObject* entity = m_scene->FindByID(id);
|
||||
if (!entity) return;
|
||||
|
||||
std::vector<::XCEngine::Components::GameObject*> children = entity->GetChildren();
|
||||
for (auto* child : children) {
|
||||
DeleteEntity(child->GetID());
|
||||
}
|
||||
|
||||
if (entity->GetParent() == nullptr) {
|
||||
m_rootEntities.erase(std::remove(m_rootEntities.begin(), m_rootEntities.end(), entity), m_rootEntities.end());
|
||||
}
|
||||
|
||||
|
||||
const auto entityId = entity->GetID();
|
||||
m_scene->DestroyGameObject(entity);
|
||||
OnEntityDeleted.Invoke(entity->GetID());
|
||||
SyncRootEntities();
|
||||
SetSceneDirty(true);
|
||||
|
||||
OnEntityDeleted.Invoke(entityId);
|
||||
OnSceneChanged.Invoke();
|
||||
|
||||
if (m_eventBus) {
|
||||
m_eventBus->Publish(EntityDeletedEvent{ entityId });
|
||||
m_eventBus->Publish(SceneChangedEvent{});
|
||||
}
|
||||
}
|
||||
|
||||
SceneManager::ClipboardData SceneManager::CopyEntityRecursive(const ::XCEngine::Components::GameObject* entity) {
|
||||
@@ -50,6 +113,14 @@ SceneManager::ClipboardData SceneManager::CopyEntityRecursive(const ::XCEngine::
|
||||
data.localScale = transform->GetLocalScale();
|
||||
}
|
||||
|
||||
auto components = entity->GetComponents<::XCEngine::Components::Component>();
|
||||
for (auto* component : components) {
|
||||
if (!component || component == entity->GetTransform()) {
|
||||
continue;
|
||||
}
|
||||
data.components.push_back(SerializeComponent(component));
|
||||
}
|
||||
|
||||
for (auto* child : entity->GetChildren()) {
|
||||
data.children.push_back(CopyEntityRecursive(child));
|
||||
}
|
||||
@@ -60,7 +131,7 @@ SceneManager::ClipboardData SceneManager::CopyEntityRecursive(const ::XCEngine::
|
||||
void SceneManager::CopyEntity(::XCEngine::Components::GameObject::ID id) {
|
||||
if (!m_scene) return;
|
||||
|
||||
::XCEngine::Components::GameObject* entity = m_scene->Find(std::to_string(id));
|
||||
::XCEngine::Components::GameObject* entity = m_scene->FindByID(id);
|
||||
if (!entity) return;
|
||||
|
||||
m_clipboard = CopyEntityRecursive(entity);
|
||||
@@ -69,7 +140,7 @@ void SceneManager::CopyEntity(::XCEngine::Components::GameObject::ID id) {
|
||||
::XCEngine::Components::GameObject::ID SceneManager::PasteEntityRecursive(const ClipboardData& data, ::XCEngine::Components::GameObject::ID parent) {
|
||||
::XCEngine::Components::GameObject* parentObj = nullptr;
|
||||
if (parent != 0) {
|
||||
parentObj = m_scene->Find(std::to_string(parent));
|
||||
parentObj = m_scene->FindByID(parent);
|
||||
}
|
||||
|
||||
::XCEngine::Components::GameObject* newEntity = m_scene->CreateGameObject(data.name, parentObj);
|
||||
@@ -79,9 +150,14 @@ void SceneManager::CopyEntity(::XCEngine::Components::GameObject::ID id) {
|
||||
transform->SetLocalRotation(data.localRotation);
|
||||
transform->SetLocalScale(data.localScale);
|
||||
}
|
||||
|
||||
if (parentObj == nullptr) {
|
||||
m_rootEntities.push_back(newEntity);
|
||||
|
||||
for (const auto& componentData : data.components) {
|
||||
if (auto* component = CreateComponentByName(newEntity, componentData.first)) {
|
||||
if (!componentData.second.empty()) {
|
||||
std::istringstream payloadStream(componentData.second);
|
||||
component->Deserialize(payloadStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& childData : data.children) {
|
||||
@@ -93,13 +169,25 @@ void SceneManager::CopyEntity(::XCEngine::Components::GameObject::ID id) {
|
||||
|
||||
::XCEngine::Components::GameObject::ID SceneManager::PasteEntity(::XCEngine::Components::GameObject::ID parent) {
|
||||
if (!m_clipboard || !m_scene) return 0;
|
||||
return PasteEntityRecursive(*m_clipboard, parent);
|
||||
|
||||
const auto newEntityId = PasteEntityRecursive(*m_clipboard, parent);
|
||||
SyncRootEntities();
|
||||
|
||||
OnEntityCreated.Invoke(newEntityId);
|
||||
OnSceneChanged.Invoke();
|
||||
|
||||
if (m_eventBus) {
|
||||
m_eventBus->Publish(EntityCreatedEvent{ newEntityId });
|
||||
m_eventBus->Publish(SceneChangedEvent{});
|
||||
}
|
||||
|
||||
return newEntityId;
|
||||
}
|
||||
|
||||
::XCEngine::Components::GameObject::ID SceneManager::DuplicateEntity(::XCEngine::Components::GameObject::ID id) {
|
||||
if (!m_scene) return 0;
|
||||
|
||||
::XCEngine::Components::GameObject* entity = m_scene->Find(std::to_string(id));
|
||||
::XCEngine::Components::GameObject* entity = m_scene->FindByID(id);
|
||||
if (!entity) return 0;
|
||||
|
||||
CopyEntity(id);
|
||||
@@ -110,54 +198,210 @@ void SceneManager::CopyEntity(::XCEngine::Components::GameObject::ID id) {
|
||||
return PasteEntity(parentId);
|
||||
}
|
||||
|
||||
void SceneManager::NewScene(const std::string& name) {
|
||||
m_scene = std::make_unique<::XCEngine::Components::Scene>(name);
|
||||
m_rootEntities.clear();
|
||||
m_clipboard.reset();
|
||||
m_currentScenePath.clear();
|
||||
m_currentSceneName = name;
|
||||
SetSceneDirty(true);
|
||||
|
||||
OnSceneChanged.Invoke();
|
||||
if (m_eventBus) {
|
||||
m_eventBus->Publish(SceneChangedEvent{});
|
||||
}
|
||||
}
|
||||
|
||||
bool SceneManager::LoadScene(const std::string& filePath) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
if (!fs::exists(filePath) || !fs::is_regular_file(filePath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto scene = std::make_unique<::XCEngine::Components::Scene>();
|
||||
scene->Load(filePath);
|
||||
|
||||
m_scene = std::move(scene);
|
||||
m_clipboard.reset();
|
||||
SyncRootEntities();
|
||||
m_currentScenePath = filePath;
|
||||
m_currentSceneName = m_scene ? m_scene->GetName() : "Untitled Scene";
|
||||
SetSceneDirty(false);
|
||||
|
||||
OnSceneChanged.Invoke();
|
||||
if (m_eventBus) {
|
||||
m_eventBus->Publish(SceneChangedEvent{});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SceneManager::SaveScene() {
|
||||
if (!m_scene) {
|
||||
return false;
|
||||
}
|
||||
if (m_currentScenePath.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
fs::path savePath = fs::path(m_currentScenePath).replace_extension(".xc");
|
||||
fs::create_directories(savePath.parent_path());
|
||||
|
||||
m_scene->Save(savePath.string());
|
||||
m_currentScenePath = savePath.string();
|
||||
SetSceneDirty(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SceneManager::SaveSceneAs(const std::string& filePath) {
|
||||
if (!m_scene || filePath.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
fs::path savePath = fs::path(filePath).replace_extension(".xc");
|
||||
fs::create_directories(savePath.parent_path());
|
||||
|
||||
m_scene->Save(savePath.string());
|
||||
m_currentScenePath = savePath.string();
|
||||
m_currentSceneName = m_scene->GetName();
|
||||
SetSceneDirty(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SceneManager::LoadStartupScene(const std::string& projectPath) {
|
||||
const std::string defaultScenePath = ResolveDefaultScenePath(projectPath);
|
||||
if (IsSceneFileUsable(defaultScenePath) && LoadScene(defaultScenePath)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
CreateDemoScene();
|
||||
return SaveSceneAs(defaultScenePath);
|
||||
}
|
||||
|
||||
void SceneManager::RenameEntity(::XCEngine::Components::GameObject::ID id, const std::string& newName) {
|
||||
if (!m_scene) return;
|
||||
|
||||
::XCEngine::Components::GameObject* obj = m_scene->Find(std::to_string(id));
|
||||
::XCEngine::Components::GameObject* obj = m_scene->FindByID(id);
|
||||
if (!obj) return;
|
||||
|
||||
obj->SetName(newName);
|
||||
SetSceneDirty(true);
|
||||
OnEntityChanged.Invoke(id);
|
||||
|
||||
if (m_eventBus) {
|
||||
m_eventBus->Publish(EntityChangedEvent{ id });
|
||||
}
|
||||
}
|
||||
|
||||
void SceneManager::MoveEntity(::XCEngine::Components::GameObject::ID id, ::XCEngine::Components::GameObject::ID newParentId) {
|
||||
if (!m_scene) return;
|
||||
|
||||
::XCEngine::Components::GameObject* obj = m_scene->Find(std::to_string(id));
|
||||
::XCEngine::Components::GameObject* obj = m_scene->FindByID(id);
|
||||
if (!obj) return;
|
||||
const auto oldParentId = obj->GetParent() ? obj->GetParent()->GetID() : 0;
|
||||
|
||||
::XCEngine::Components::GameObject* newParent = nullptr;
|
||||
if (newParentId != 0) {
|
||||
newParent = m_scene->Find(std::to_string(newParentId));
|
||||
newParent = m_scene->FindByID(newParentId);
|
||||
}
|
||||
|
||||
obj->SetParent(newParent);
|
||||
SyncRootEntities();
|
||||
SetSceneDirty(true);
|
||||
|
||||
OnEntityChanged.Invoke(id);
|
||||
OnSceneChanged.Invoke();
|
||||
|
||||
if (m_eventBus) {
|
||||
m_eventBus->Publish(EntityParentChangedEvent{ id, oldParentId, newParentId });
|
||||
m_eventBus->Publish(EntityChangedEvent{ id });
|
||||
m_eventBus->Publish(SceneChangedEvent{});
|
||||
}
|
||||
}
|
||||
|
||||
void SceneManager::CreateDemoScene() {
|
||||
if (m_scene) {
|
||||
delete m_scene;
|
||||
}
|
||||
m_scene = new ::XCEngine::Components::Scene("DemoScene");
|
||||
m_scene = std::make_unique<::XCEngine::Components::Scene>("Main Scene");
|
||||
m_rootEntities.clear();
|
||||
m_clipboard.reset();
|
||||
m_currentScenePath.clear();
|
||||
m_currentSceneName = m_scene->GetName();
|
||||
SetSceneDirty(true);
|
||||
|
||||
::XCEngine::Components::GameObject* camera = CreateEntity("Main Camera", nullptr);
|
||||
camera->AddComponent<::XCEngine::Components::TransformComponent>();
|
||||
camera->AddComponent<::XCEngine::Components::CameraComponent>();
|
||||
|
||||
::XCEngine::Components::GameObject* light = CreateEntity("Directional Light", nullptr);
|
||||
light->AddComponent<::XCEngine::Components::LightComponent>();
|
||||
|
||||
::XCEngine::Components::GameObject* cube = CreateEntity("Cube", nullptr);
|
||||
cube->AddComponent<::XCEngine::Components::TransformComponent>();
|
||||
|
||||
::XCEngine::Components::GameObject* sphere = CreateEntity("Sphere", nullptr);
|
||||
sphere->AddComponent<::XCEngine::Components::TransformComponent>();
|
||||
|
||||
::XCEngine::Components::GameObject* player = CreateEntity("Player", nullptr);
|
||||
::XCEngine::Components::GameObject* weapon = CreateEntity("Weapon", player);
|
||||
|
||||
SyncRootEntities();
|
||||
OnSceneChanged.Invoke();
|
||||
|
||||
if (m_eventBus) {
|
||||
m_eventBus->Publish(SceneChangedEvent{});
|
||||
}
|
||||
}
|
||||
|
||||
void SceneManager::SyncRootEntities() {
|
||||
if (!m_scene) {
|
||||
m_rootEntities.clear();
|
||||
m_currentSceneName = "Untitled Scene";
|
||||
SetSceneDirty(false);
|
||||
return;
|
||||
}
|
||||
|
||||
m_rootEntities = m_scene->GetRootGameObjects();
|
||||
m_currentSceneName = m_scene->GetName();
|
||||
}
|
||||
|
||||
std::string SceneManager::GetScenesDirectory(const std::string& projectPath) {
|
||||
return (std::filesystem::path(projectPath) / "Assets" / "Scenes").string();
|
||||
}
|
||||
|
||||
std::string SceneManager::ResolveDefaultScenePath(const std::string& projectPath) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
const fs::path scenesDir = GetScenesDirectory(projectPath);
|
||||
const fs::path mainXC = scenesDir / "Main.xc";
|
||||
if (fs::exists(mainXC)) {
|
||||
return mainXC.string();
|
||||
}
|
||||
|
||||
const fs::path mainScene = scenesDir / "Main.scene";
|
||||
if (fs::exists(mainScene)) {
|
||||
return mainScene.string();
|
||||
}
|
||||
|
||||
const fs::path mainUnity = scenesDir / "Main.unity";
|
||||
if (fs::exists(mainUnity)) {
|
||||
return mainUnity.string();
|
||||
}
|
||||
|
||||
if (fs::exists(scenesDir)) {
|
||||
for (const auto& entry : fs::directory_iterator(scenesDir)) {
|
||||
if (!entry.is_regular_file()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const fs::path ext = entry.path().extension();
|
||||
if (ext == ".xc" || ext == ".scene" || ext == ".unity") {
|
||||
return entry.path().string();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mainXC.string();
|
||||
}
|
||||
|
||||
bool SceneManager::IsSceneFileUsable(const std::string& filePath) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
return !filePath.empty() &&
|
||||
fs::exists(filePath) &&
|
||||
fs::is_regular_file(filePath) &&
|
||||
fs::file_size(filePath) > 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include <XCEngine/Core/Event.h>
|
||||
#include <XCEngine/Core/Math/Vector3.h>
|
||||
@@ -18,9 +19,11 @@
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
|
||||
class EventBus;
|
||||
|
||||
class SceneManager : public ISceneManager {
|
||||
public:
|
||||
SceneManager();
|
||||
explicit SceneManager(EventBus* eventBus = nullptr);
|
||||
|
||||
::XCEngine::Components::GameObject* CreateEntity(const std::string& name, ::XCEngine::Components::GameObject* parent = nullptr);
|
||||
|
||||
@@ -45,7 +48,17 @@ public:
|
||||
::XCEngine::Components::GameObject::ID DuplicateEntity(::XCEngine::Components::GameObject::ID id);
|
||||
void MoveEntity(::XCEngine::Components::GameObject::ID id, ::XCEngine::Components::GameObject::ID newParent);
|
||||
|
||||
void CreateDemoScene();
|
||||
void NewScene(const std::string& name = "Untitled Scene") override;
|
||||
bool LoadScene(const std::string& filePath) override;
|
||||
bool SaveScene() override;
|
||||
bool SaveSceneAs(const std::string& filePath) override;
|
||||
bool LoadStartupScene(const std::string& projectPath) override;
|
||||
bool HasActiveScene() const override { return m_scene != nullptr; }
|
||||
bool IsSceneDirty() const override { return m_isSceneDirty; }
|
||||
void MarkSceneDirty() override;
|
||||
const std::string& GetCurrentScenePath() const override { return m_currentScenePath; }
|
||||
const std::string& GetCurrentSceneName() const override { return m_currentSceneName; }
|
||||
void CreateDemoScene() override;
|
||||
|
||||
bool HasClipboardData() const { return m_clipboard.has_value(); }
|
||||
|
||||
@@ -60,15 +73,25 @@ private:
|
||||
Math::Vector3 localPosition = Math::Vector3::Zero();
|
||||
Math::Quaternion localRotation = Math::Quaternion::Identity();
|
||||
Math::Vector3 localScale = Math::Vector3::One();
|
||||
std::vector<std::pair<std::string, std::string>> components;
|
||||
std::vector<ClipboardData> children;
|
||||
};
|
||||
|
||||
ClipboardData CopyEntityRecursive(const ::XCEngine::Components::GameObject* entity);
|
||||
::XCEngine::Components::GameObject::ID PasteEntityRecursive(const ClipboardData& data, ::XCEngine::Components::GameObject::ID parent);
|
||||
void SetSceneDirty(bool dirty);
|
||||
void SyncRootEntities();
|
||||
static std::string GetScenesDirectory(const std::string& projectPath);
|
||||
static std::string ResolveDefaultScenePath(const std::string& projectPath);
|
||||
static bool IsSceneFileUsable(const std::string& filePath);
|
||||
|
||||
::XCEngine::Components::Scene* m_scene = nullptr;
|
||||
std::unique_ptr<::XCEngine::Components::Scene> m_scene;
|
||||
std::vector<::XCEngine::Components::GameObject*> m_rootEntities;
|
||||
std::optional<ClipboardData> m_clipboard;
|
||||
EventBus* m_eventBus = nullptr;
|
||||
std::string m_currentScenePath;
|
||||
std::string m_currentSceneName = "Untitled Scene";
|
||||
bool m_isSceneDirty = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
196
editor/src/Utils/SceneEditorUtils.h
Normal file
196
editor/src/Utils/SceneEditorUtils.h
Normal file
@@ -0,0 +1,196 @@
|
||||
#pragma once
|
||||
|
||||
#include "Core/IEditorContext.h"
|
||||
#include "Core/IProjectManager.h"
|
||||
#include "Core/ISceneManager.h"
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <windows.h>
|
||||
#include <commdlg.h>
|
||||
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace SceneEditorUtils {
|
||||
|
||||
inline std::wstring Utf8ToWide(const std::string& value) {
|
||||
if (value.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const int length = MultiByteToWideChar(CP_UTF8, 0, value.c_str(), -1, nullptr, 0);
|
||||
if (length <= 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::wstring result(length - 1, L'\0');
|
||||
MultiByteToWideChar(CP_UTF8, 0, value.c_str(), -1, &result[0], length);
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::string WideToUtf8(const std::wstring& value) {
|
||||
if (value.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const int length = WideCharToMultiByte(CP_UTF8, 0, value.c_str(), -1, nullptr, 0, nullptr, nullptr);
|
||||
if (length <= 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string result(length - 1, '\0');
|
||||
WideCharToMultiByte(CP_UTF8, 0, value.c_str(), -1, &result[0], length, nullptr, nullptr);
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::string SanitizeSceneFileName(const std::string& value) {
|
||||
std::string result = value.empty() ? "Untitled Scene" : value;
|
||||
for (char& ch : result) {
|
||||
switch (ch) {
|
||||
case '\\':
|
||||
case '/':
|
||||
case ':':
|
||||
case '*':
|
||||
case '?':
|
||||
case '"':
|
||||
case '<':
|
||||
case '>':
|
||||
case '|':
|
||||
ch = '_';
|
||||
break;
|
||||
default:
|
||||
if (static_cast<unsigned char>(ch) < 32u) {
|
||||
ch = '_';
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
inline HWND GetDialogOwnerWindow() {
|
||||
HWND owner = GetActiveWindow();
|
||||
if (!owner) {
|
||||
owner = GetForegroundWindow();
|
||||
}
|
||||
return owner;
|
||||
}
|
||||
|
||||
inline const wchar_t* GetSceneFilter() {
|
||||
static const wchar_t filter[] =
|
||||
L"XCEngine Scene (*.xc)\0*.xc\0Legacy Scene (*.scene;*.unity)\0*.scene;*.unity\0All Files (*.*)\0*.*\0\0";
|
||||
return filter;
|
||||
}
|
||||
|
||||
inline std::string OpenSceneFileDialog(const std::string& projectPath, const std::string& initialPath = {}) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
const fs::path scenesDir = fs::path(projectPath) / "Assets" / "Scenes";
|
||||
const std::wstring initialDir = Utf8ToWide(scenesDir.string());
|
||||
|
||||
std::array<wchar_t, 1024> fileBuffer{};
|
||||
if (!initialPath.empty()) {
|
||||
const std::wstring initialFile = Utf8ToWide(initialPath);
|
||||
wcsncpy_s(fileBuffer.data(), fileBuffer.size(), initialFile.c_str(), _TRUNCATE);
|
||||
}
|
||||
|
||||
OPENFILENAMEW dialog{};
|
||||
dialog.lStructSize = sizeof(dialog);
|
||||
dialog.hwndOwner = GetDialogOwnerWindow();
|
||||
dialog.lpstrFilter = GetSceneFilter();
|
||||
dialog.lpstrFile = fileBuffer.data();
|
||||
dialog.nMaxFile = static_cast<DWORD>(fileBuffer.size());
|
||||
dialog.lpstrInitialDir = initialDir.empty() ? nullptr : initialDir.c_str();
|
||||
dialog.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR;
|
||||
|
||||
if (!GetOpenFileNameW(&dialog)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return WideToUtf8(fileBuffer.data());
|
||||
}
|
||||
|
||||
inline std::string SaveSceneFileDialog(
|
||||
const std::string& projectPath,
|
||||
const std::string& currentScenePath,
|
||||
const std::string& currentSceneName) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
const fs::path scenesDir = fs::path(projectPath) / "Assets" / "Scenes";
|
||||
const fs::path suggestedPath = currentScenePath.empty()
|
||||
? scenesDir / (SanitizeSceneFileName(currentSceneName) + ".xc")
|
||||
: fs::path(currentScenePath).replace_extension(".xc");
|
||||
|
||||
std::array<wchar_t, 1024> fileBuffer{};
|
||||
const std::wstring suggestedWide = Utf8ToWide(suggestedPath.string());
|
||||
wcsncpy_s(fileBuffer.data(), fileBuffer.size(), suggestedWide.c_str(), _TRUNCATE);
|
||||
|
||||
const std::wstring initialDir = Utf8ToWide(suggestedPath.parent_path().string());
|
||||
OPENFILENAMEW dialog{};
|
||||
dialog.lStructSize = sizeof(dialog);
|
||||
dialog.hwndOwner = GetDialogOwnerWindow();
|
||||
dialog.lpstrFilter = GetSceneFilter();
|
||||
dialog.lpstrFile = fileBuffer.data();
|
||||
dialog.nMaxFile = static_cast<DWORD>(fileBuffer.size());
|
||||
dialog.lpstrInitialDir = initialDir.empty() ? nullptr : initialDir.c_str();
|
||||
dialog.lpstrDefExt = L"xc";
|
||||
dialog.Flags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR;
|
||||
|
||||
if (!GetSaveFileNameW(&dialog)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return WideToUtf8(fileBuffer.data());
|
||||
}
|
||||
|
||||
inline bool SaveCurrentScene(IEditorContext& context) {
|
||||
auto& sceneManager = context.GetSceneManager();
|
||||
if (sceneManager.SaveScene()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::string filePath = SaveSceneFileDialog(
|
||||
context.GetProjectPath(),
|
||||
sceneManager.GetCurrentScenePath(),
|
||||
sceneManager.GetCurrentSceneName());
|
||||
if (filePath.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool saved = sceneManager.SaveSceneAs(filePath);
|
||||
if (saved) {
|
||||
context.GetProjectManager().RefreshCurrentFolder();
|
||||
}
|
||||
return saved;
|
||||
}
|
||||
|
||||
inline bool ConfirmSceneSwitch(IEditorContext& context) {
|
||||
auto& sceneManager = context.GetSceneManager();
|
||||
if (!sceneManager.HasActiveScene() || !sceneManager.IsSceneDirty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const int result = MessageBoxW(
|
||||
GetDialogOwnerWindow(),
|
||||
L"Save changes to the current scene before continuing?",
|
||||
L"Unsaved Scene Changes",
|
||||
MB_YESNOCANCEL | MB_ICONWARNING);
|
||||
|
||||
if (result == IDCANCEL) {
|
||||
return false;
|
||||
}
|
||||
if (result == IDYES) {
|
||||
return SaveCurrentScene(context);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace SceneEditorUtils
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -11,9 +11,11 @@ ConsolePanel::ConsolePanel() : Panel("Console") {
|
||||
|
||||
void ConsolePanel::Render() {
|
||||
ImGui::Begin(m_name.c_str(), nullptr, ImGuiWindowFlags_None);
|
||||
|
||||
auto* sink = Debug::EditorConsoleSink::GetInstance();
|
||||
|
||||
if (ImGui::Button("Clear")) {
|
||||
Debug::EditorConsoleSink::GetInstance()->Clear();
|
||||
sink->Clear();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
|
||||
@@ -52,7 +54,7 @@ void ConsolePanel::Render() {
|
||||
|
||||
ImGui::BeginChild("LogScroll", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar);
|
||||
|
||||
const auto& logs = Debug::EditorConsoleSink::GetInstance()->GetLogs();
|
||||
const auto logs = sink->GetLogs();
|
||||
size_t logIndex = 0;
|
||||
for (const auto& log : logs) {
|
||||
bool shouldShow = false;
|
||||
@@ -117,4 +119,4 @@ void ConsolePanel::Render() {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include "Core/EventBus.h"
|
||||
#include <XCEngine/Core/Math/Vector3.h>
|
||||
#include <XCEngine/Core/Math/Quaternion.h>
|
||||
#include <XCEngine/Components/CameraComponent.h>
|
||||
#include <XCEngine/Components/LightComponent.h>
|
||||
#include <imgui.h>
|
||||
#include <cstring>
|
||||
|
||||
@@ -22,9 +24,6 @@ HierarchyPanel::~HierarchyPanel() {
|
||||
}
|
||||
|
||||
void HierarchyPanel::OnAttach() {
|
||||
auto& sceneManager = m_context->GetSceneManager();
|
||||
sceneManager.CreateDemoScene();
|
||||
|
||||
m_selectionHandlerId = m_context->GetEventBus().Subscribe<SelectionChangedEvent>(
|
||||
[this](const SelectionChangedEvent& event) {
|
||||
OnSelectionChanged(event);
|
||||
@@ -74,7 +73,16 @@ void HierarchyPanel::Render() {
|
||||
::XCEngine::Components::GameObject* sourceGameObject = *(::XCEngine::Components::GameObject**)payload->Data;
|
||||
if (sourceGameObject && sourceGameObject->GetParent() != nullptr) {
|
||||
auto& sceneManager = m_context->GetSceneManager();
|
||||
auto* srcTransform = sourceGameObject->GetTransform();
|
||||
Math::Vector3 worldPos = srcTransform->GetPosition();
|
||||
Math::Quaternion worldRot = srcTransform->GetRotation();
|
||||
Math::Vector3 worldScale = srcTransform->GetScale();
|
||||
|
||||
sceneManager.MoveEntity(sourceGameObject->GetID(), 0);
|
||||
|
||||
srcTransform->SetPosition(worldPos);
|
||||
srcTransform->SetRotation(worldRot);
|
||||
srcTransform->SetScale(worldScale);
|
||||
}
|
||||
}
|
||||
ImGui::EndDragDropTarget();
|
||||
@@ -195,7 +203,7 @@ void HierarchyPanel::RenderContextMenu(::XCEngine::Components::GameObject* gameO
|
||||
|
||||
if (gameObject != nullptr && gameObject->GetParent() != nullptr) {
|
||||
if (ImGui::MenuItem("Detach from Parent")) {
|
||||
gameObject->DetachFromParent();
|
||||
sceneManager.MoveEntity(gameObject->GetID(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,12 +250,13 @@ void HierarchyPanel::RenderCreateMenu(::XCEngine::Components::GameObject* parent
|
||||
|
||||
if (ImGui::MenuItem("Camera")) {
|
||||
auto* newEntity = sceneManager.CreateEntity("Camera", parent);
|
||||
newEntity->AddComponent<::XCEngine::Components::TransformComponent>();
|
||||
newEntity->AddComponent<::XCEngine::Components::CameraComponent>();
|
||||
selectionManager.SetSelectedEntity(newEntity->GetID());
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("Light")) {
|
||||
auto* newEntity = sceneManager.CreateEntity("Light", parent);
|
||||
newEntity->AddComponent<::XCEngine::Components::LightComponent>();
|
||||
selectionManager.SetSelectedEntity(newEntity->GetID());
|
||||
}
|
||||
|
||||
@@ -255,19 +264,16 @@ void HierarchyPanel::RenderCreateMenu(::XCEngine::Components::GameObject* parent
|
||||
|
||||
if (ImGui::MenuItem("Cube")) {
|
||||
auto* newEntity = sceneManager.CreateEntity("Cube", parent);
|
||||
newEntity->AddComponent<::XCEngine::Components::TransformComponent>();
|
||||
selectionManager.SetSelectedEntity(newEntity->GetID());
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("Sphere")) {
|
||||
auto* newEntity = sceneManager.CreateEntity("Sphere", parent);
|
||||
newEntity->AddComponent<::XCEngine::Components::TransformComponent>();
|
||||
selectionManager.SetSelectedEntity(newEntity->GetID());
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("Plane")) {
|
||||
auto* newEntity = sceneManager.CreateEntity("Plane", parent);
|
||||
newEntity->AddComponent<::XCEngine::Components::TransformComponent>();
|
||||
selectionManager.SetSelectedEntity(newEntity->GetID());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
#include "InspectorPanel.h"
|
||||
#include "Core/EditorContext.h"
|
||||
#include "Core/IEditorContext.h"
|
||||
#include "Core/ISceneManager.h"
|
||||
#include "UI/UI.h"
|
||||
#include <XCEngine/Debug/Logger.h>
|
||||
#include "Core/ISelectionManager.h"
|
||||
#include "Core/EventBus.h"
|
||||
#include "Core/EditorEvents.h"
|
||||
#include "ComponentEditors/CameraComponentEditor.h"
|
||||
#include "ComponentEditors/IComponentEditor.h"
|
||||
#include "ComponentEditors/LightComponentEditor.h"
|
||||
#include "ComponentEditors/TransformComponentEditor.h"
|
||||
#include <imgui.h>
|
||||
#include <string>
|
||||
|
||||
@@ -10,7 +15,7 @@ namespace XCEngine {
|
||||
namespace Editor {
|
||||
|
||||
InspectorPanel::InspectorPanel() : Panel("Inspector") {
|
||||
Debug::Logger::Get().Debug(Debug::LogCategory::General, "InspectorPanel constructed");
|
||||
RegisterDefaultComponentEditors();
|
||||
}
|
||||
|
||||
InspectorPanel::~InspectorPanel() {
|
||||
@@ -23,8 +28,35 @@ void InspectorPanel::OnSelectionChanged(const SelectionChangedEvent& event) {
|
||||
m_selectedEntityId = event.primarySelection;
|
||||
}
|
||||
|
||||
void InspectorPanel::RegisterDefaultComponentEditors() {
|
||||
RegisterComponentEditor(std::make_unique<TransformComponentEditor>());
|
||||
RegisterComponentEditor(std::make_unique<CameraComponentEditor>());
|
||||
RegisterComponentEditor(std::make_unique<LightComponentEditor>());
|
||||
}
|
||||
|
||||
void InspectorPanel::RegisterComponentEditor(std::unique_ptr<IComponentEditor> editor) {
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_componentEditors.push_back(std::move(editor));
|
||||
}
|
||||
|
||||
IComponentEditor* InspectorPanel::GetEditorFor(::XCEngine::Components::Component* component) const {
|
||||
if (!component) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (const auto& editor : m_componentEditors) {
|
||||
if (editor && editor->CanEdit(component)) {
|
||||
return editor.get();
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void InspectorPanel::Render() {
|
||||
Debug::Logger::Get().Debug(Debug::LogCategory::General, "InspectorPanel::Render START");
|
||||
ImGui::Begin(m_name.c_str(), nullptr, ImGuiWindowFlags_None);
|
||||
|
||||
if (!m_selectionHandlerId && m_context) {
|
||||
@@ -51,21 +83,18 @@ void InspectorPanel::Render() {
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
Debug::Logger::Get().Debug(Debug::LogCategory::General, "InspectorPanel::Render END");
|
||||
}
|
||||
|
||||
void InspectorPanel::RenderGameObject(::XCEngine::Components::GameObject* gameObject) {
|
||||
Debug::Logger::Get().Debug(Debug::LogCategory::General, "RenderGameObject START");
|
||||
char nameBuffer[256];
|
||||
strcpy_s(nameBuffer, gameObject->GetName().c_str());
|
||||
ImGui::InputText("##Name", nameBuffer, sizeof(nameBuffer));
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
||||
gameObject->SetName(nameBuffer);
|
||||
m_context->GetSceneManager().RenameEntity(gameObject->GetID(), nameBuffer);
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Add Component")) {
|
||||
Debug::Logger::Get().Debug(Debug::LogCategory::General, "Add Component BUTTON CLICKED");
|
||||
ImGui::OpenPopup("AddComponent");
|
||||
}
|
||||
|
||||
@@ -75,53 +104,54 @@ void InspectorPanel::RenderGameObject(::XCEngine::Components::GameObject* gameOb
|
||||
for (auto* component : components) {
|
||||
RenderComponent(component, gameObject);
|
||||
}
|
||||
Debug::Logger::Get().Debug(Debug::LogCategory::General, "RenderGameObject END");
|
||||
}
|
||||
|
||||
void InspectorPanel::RenderAddComponentPopup(::XCEngine::Components::GameObject* gameObject) {
|
||||
Debug::Logger::Get().Debug(Debug::LogCategory::General, "RenderAddComponentPopup called");
|
||||
|
||||
if (!gameObject) {
|
||||
Debug::Logger::Get().Error(Debug::LogCategory::General, "ERROR: gameObject is nullptr!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ImGui::BeginPopup("AddComponent")) {
|
||||
Debug::Logger::Get().Debug(Debug::LogCategory::General, "BeginPopup returned false");
|
||||
return;
|
||||
}
|
||||
|
||||
Debug::Logger::Get().Debug(Debug::LogCategory::General, "BeginPopup succeeded");
|
||||
|
||||
|
||||
ImGui::Text("Components");
|
||||
ImGui::Separator();
|
||||
|
||||
bool hasTransform = gameObject->GetComponent<::XCEngine::Components::TransformComponent>() != nullptr;
|
||||
Debug::Logger::Get().Debug(Debug::LogCategory::General, hasTransform ? "Has Transform: yes" : "Has Transform: no");
|
||||
|
||||
Debug::Logger::Get().Debug(Debug::LogCategory::General, "About to check MenuItem condition");
|
||||
if (ImGui::MenuItem("Transform", nullptr, false, !hasTransform)) {
|
||||
Debug::Logger::Get().Debug(Debug::LogCategory::General, "MenuItem CLICKED! Before AddComponent");
|
||||
|
||||
auto* newComp = gameObject->AddComponent<::XCEngine::Components::TransformComponent>();
|
||||
Debug::Logger::Get().Debug(Debug::LogCategory::General, newComp ? "AddComponent SUCCEEDED" : "AddComponent FAILED");
|
||||
|
||||
ImGui::CloseCurrentPopup();
|
||||
} else {
|
||||
Debug::Logger::Get().Debug(Debug::LogCategory::General, "MenuItem not clicked (disabled or condition false)");
|
||||
|
||||
bool drewAnyEntry = false;
|
||||
for (const auto& editor : m_componentEditors) {
|
||||
if (!editor || !editor->ShowInAddComponentMenu()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
drewAnyEntry = true;
|
||||
const bool canAdd = editor->CanAddTo(gameObject);
|
||||
std::string label = editor->GetDisplayName();
|
||||
if (!canAdd) {
|
||||
const char* reason = editor->GetAddDisabledReason(gameObject);
|
||||
if (reason && reason[0] != '\0') {
|
||||
label += " (";
|
||||
label += reason;
|
||||
label += ")";
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem(label.c_str(), nullptr, false, canAdd)) {
|
||||
if (editor->AddTo(gameObject)) {
|
||||
m_context->GetSceneManager().MarkSceneDirty();
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::TextDisabled("No more components available");
|
||||
|
||||
Debug::Logger::Get().Debug(Debug::LogCategory::General, "About to EndPopup - calling EndPopup");
|
||||
|
||||
if (!drewAnyEntry) {
|
||||
ImGui::TextDisabled("No registered component editors");
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
Debug::Logger::Get().Debug(Debug::LogCategory::General, "Popup closed");
|
||||
}
|
||||
|
||||
void InspectorPanel::RenderComponent(::XCEngine::Components::Component* component, ::XCEngine::Components::GameObject* gameObject) {
|
||||
if (!component) return;
|
||||
|
||||
IComponentEditor* editor = GetEditorFor(component);
|
||||
|
||||
const char* name = component->GetName().c_str();
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2{4, 2});
|
||||
@@ -138,8 +168,9 @@ void InspectorPanel::RenderComponent(::XCEngine::Components::Component* componen
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
bool removeComponent = false;
|
||||
const bool canRemoveComponent = editor ? editor->CanRemove(component) : false;
|
||||
if (ImGui::BeginPopupContextItem("ComponentSettings")) {
|
||||
if (ImGui::MenuItem("Remove Component")) {
|
||||
if (ImGui::MenuItem("Remove Component", nullptr, false, canRemoveComponent)) {
|
||||
removeComponent = true;
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
@@ -151,22 +182,12 @@ void InspectorPanel::RenderComponent(::XCEngine::Components::Component* componen
|
||||
}
|
||||
|
||||
if (open) {
|
||||
if (auto* transform = dynamic_cast<::XCEngine::Components::TransformComponent*>(component)) {
|
||||
::XCEngine::Math::Vector3 position = transform->GetLocalPosition();
|
||||
::XCEngine::Math::Vector3 rotation = transform->GetLocalEulerAngles();
|
||||
::XCEngine::Math::Vector3 scale = transform->GetLocalScale();
|
||||
|
||||
if (UI::DrawVec3("Position", position, 0.0f, 80.0f, 0.1f)) {
|
||||
transform->SetLocalPosition(position);
|
||||
}
|
||||
|
||||
if (UI::DrawVec3("Rotation", rotation, 0.0f, 80.0f, 1.0f)) {
|
||||
transform->SetLocalEulerAngles(rotation);
|
||||
}
|
||||
|
||||
if (UI::DrawVec3("Scale", scale, 1.0f, 80.0f, 0.1f)) {
|
||||
transform->SetLocalScale(scale);
|
||||
if (editor) {
|
||||
if (editor->Render(component)) {
|
||||
m_context->GetSceneManager().MarkSceneDirty();
|
||||
}
|
||||
} else {
|
||||
ImGui::TextDisabled("No registered editor for this component");
|
||||
}
|
||||
|
||||
ImGui::TreePop();
|
||||
@@ -179,14 +200,9 @@ void InspectorPanel::RemoveComponentByType(::XCEngine::Components::Component* co
|
||||
if (dynamic_cast<::XCEngine::Components::TransformComponent*>(component)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto components = gameObject->GetComponents<::XCEngine::Components::Component>();
|
||||
for (auto* comp : components) {
|
||||
if (comp == component) {
|
||||
gameObject->RemoveComponent<::XCEngine::Components::Component>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
gameObject->RemoveComponent(component);
|
||||
m_context->GetSceneManager().MarkSceneDirty();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "Panel.h"
|
||||
#include <XCEngine/Components/TransformComponent.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Components {
|
||||
class Component;
|
||||
class GameObject;
|
||||
}
|
||||
|
||||
namespace Editor {
|
||||
|
||||
class IComponentEditor;
|
||||
|
||||
class InspectorPanel : public Panel {
|
||||
public:
|
||||
InspectorPanel();
|
||||
@@ -14,6 +24,9 @@ public:
|
||||
void Render() override;
|
||||
|
||||
private:
|
||||
void RegisterDefaultComponentEditors();
|
||||
void RegisterComponentEditor(std::unique_ptr<IComponentEditor> editor);
|
||||
IComponentEditor* GetEditorFor(::XCEngine::Components::Component* component) const;
|
||||
void RenderGameObject(::XCEngine::Components::GameObject* gameObject);
|
||||
void RenderAddComponentPopup(::XCEngine::Components::GameObject* gameObject);
|
||||
void RenderComponent(::XCEngine::Components::Component* component, ::XCEngine::Components::GameObject* gameObject);
|
||||
@@ -22,6 +35,7 @@ private:
|
||||
|
||||
uint64_t m_selectionHandlerId = 0;
|
||||
uint64_t m_selectedEntityId = 0;
|
||||
std::vector<std::unique_ptr<IComponentEditor>> m_componentEditors;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
#include "MenuBar.h"
|
||||
#include "Core/IEditorContext.h"
|
||||
#include "Core/ISceneManager.h"
|
||||
#include "Utils/SceneEditorUtils.h"
|
||||
#include <filesystem>
|
||||
#include <imgui.h>
|
||||
|
||||
namespace XCEngine {
|
||||
@@ -7,20 +11,103 @@ namespace Editor {
|
||||
MenuBar::MenuBar() : Panel("MenuBar") {}
|
||||
|
||||
void MenuBar::Render() {
|
||||
HandleShortcuts();
|
||||
|
||||
if (ImGui::BeginMainMenuBar()) {
|
||||
ShowFileMenu();
|
||||
ShowEditMenu();
|
||||
ShowViewMenu();
|
||||
ShowHelpMenu();
|
||||
RenderSceneStatus();
|
||||
ImGui::EndMainMenuBar();
|
||||
}
|
||||
}
|
||||
|
||||
void MenuBar::NewScene() {
|
||||
if (!m_context || !SceneEditorUtils::ConfirmSceneSwitch(*m_context)) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_context->GetSceneManager().NewScene();
|
||||
}
|
||||
|
||||
void MenuBar::OpenScene() {
|
||||
if (!m_context || !SceneEditorUtils::ConfirmSceneSwitch(*m_context)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string filePath = SceneEditorUtils::OpenSceneFileDialog(
|
||||
m_context->GetProjectPath(),
|
||||
m_context->GetSceneManager().GetCurrentScenePath());
|
||||
if (!filePath.empty()) {
|
||||
m_context->GetSceneManager().LoadScene(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
void MenuBar::SaveScene() {
|
||||
if (!m_context) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SceneEditorUtils::SaveCurrentScene(*m_context)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void MenuBar::SaveSceneAs() {
|
||||
if (!m_context) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string filePath = SceneEditorUtils::SaveSceneFileDialog(
|
||||
m_context->GetProjectPath(),
|
||||
m_context->GetSceneManager().GetCurrentScenePath(),
|
||||
m_context->GetSceneManager().GetCurrentSceneName());
|
||||
if (!filePath.empty() && m_context->GetSceneManager().SaveSceneAs(filePath)) {
|
||||
m_context->GetProjectManager().RefreshCurrentFolder();
|
||||
}
|
||||
}
|
||||
|
||||
void MenuBar::HandleShortcuts() {
|
||||
if (!m_context) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
if (!io.KeyCtrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& sceneManager = m_context->GetSceneManager();
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_N, false)) {
|
||||
NewScene();
|
||||
}
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_O, false)) {
|
||||
OpenScene();
|
||||
}
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_S, false)) {
|
||||
if (io.KeyShift) {
|
||||
SaveSceneAs();
|
||||
} else {
|
||||
SaveScene();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MenuBar::ShowFileMenu() {
|
||||
if (ImGui::BeginMenu("File")) {
|
||||
if (ImGui::MenuItem("New Scene", "Ctrl+N")) {}
|
||||
if (ImGui::MenuItem("Open Scene", "Ctrl+O")) {}
|
||||
if (ImGui::MenuItem("Save Scene", "Ctrl+S")) {}
|
||||
if (ImGui::MenuItem("New Scene", "Ctrl+N")) {
|
||||
NewScene();
|
||||
}
|
||||
if (ImGui::MenuItem("Open Scene", "Ctrl+O")) {
|
||||
OpenScene();
|
||||
}
|
||||
if (ImGui::MenuItem("Save Scene", "Ctrl+S")) {
|
||||
SaveScene();
|
||||
}
|
||||
if (ImGui::MenuItem("Save Scene As...", "Ctrl+Shift+S")) {
|
||||
SaveSceneAs();
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("Exit", "Alt+F4")) {}
|
||||
ImGui::EndMenu();
|
||||
@@ -53,5 +140,45 @@ void MenuBar::ShowHelpMenu() {
|
||||
}
|
||||
}
|
||||
|
||||
void MenuBar::RenderSceneStatus() {
|
||||
if (!m_context) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& sceneManager = m_context->GetSceneManager();
|
||||
std::string sceneLabel = sceneManager.HasActiveScene() ? sceneManager.GetCurrentSceneName() : "No Scene";
|
||||
if (sceneLabel.empty()) {
|
||||
sceneLabel = "Untitled Scene";
|
||||
}
|
||||
|
||||
std::string fileLabel = sceneManager.GetCurrentScenePath().empty()
|
||||
? "Unsaved.xc"
|
||||
: std::filesystem::path(sceneManager.GetCurrentScenePath()).filename().string();
|
||||
|
||||
const bool dirty = sceneManager.IsSceneDirty();
|
||||
const std::string statusText = std::string("Scene: ") + fileLabel + (dirty ? " Modified" : " Saved");
|
||||
const ImVec2 textSize = ImGui::CalcTextSize(statusText.c_str());
|
||||
const float targetX = ImGui::GetWindowWidth() - textSize.x - 20.0f;
|
||||
if (targetX > ImGui::GetCursorPosX()) {
|
||||
ImGui::SetCursorPosX(targetX);
|
||||
}
|
||||
|
||||
const ImVec4 accentColor = dirty
|
||||
? ImVec4(0.94f, 0.68f, 0.20f, 1.0f)
|
||||
: ImVec4(0.48f, 0.78f, 0.49f, 1.0f);
|
||||
ImGui::TextColored(accentColor, "%s", statusText.c_str());
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::Text("Scene");
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Name: %s", sceneLabel.c_str());
|
||||
ImGui::Text("File: %s", fileLabel.c_str());
|
||||
ImGui::Text("State: %s", dirty ? "Modified" : "Saved");
|
||||
ImGui::Text("Path: %s", sceneManager.GetCurrentScenePath().empty() ? "(not saved yet)" : sceneManager.GetCurrentScenePath().c_str());
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,12 @@ public:
|
||||
void Render() override;
|
||||
|
||||
private:
|
||||
void HandleShortcuts();
|
||||
void NewScene();
|
||||
void OpenScene();
|
||||
void SaveScene();
|
||||
void SaveSceneAs();
|
||||
void RenderSceneStatus();
|
||||
void ShowFileMenu();
|
||||
void ShowEditMenu();
|
||||
void ShowViewMenu();
|
||||
@@ -18,4 +24,4 @@ private:
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#include "ProjectPanel.h"
|
||||
#include "Core/IEditorContext.h"
|
||||
#include "Core/IProjectManager.h"
|
||||
#include "Core/ISceneManager.h"
|
||||
#include "Core/AssetItem.h"
|
||||
#include "Utils/SceneEditorUtils.h"
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
|
||||
@@ -83,7 +85,7 @@ void ProjectPanel::Render() {
|
||||
|
||||
auto& items = manager.GetCurrentItems();
|
||||
std::string searchStr = m_searchBuffer;
|
||||
int itemIndex = 0;
|
||||
int displayedCount = 0;
|
||||
|
||||
for (int i = 0; i < (int)items.size(); i++) {
|
||||
if (!searchStr.empty()) {
|
||||
@@ -92,11 +94,11 @@ void ProjectPanel::Render() {
|
||||
}
|
||||
}
|
||||
|
||||
if (itemIndex > 0 && itemIndex % columns != 0) {
|
||||
if (displayedCount > 0 && displayedCount % columns != 0) {
|
||||
ImGui::SameLine();
|
||||
}
|
||||
RenderAssetItem(items[i], itemIndex);
|
||||
itemIndex++;
|
||||
RenderAssetItem(items[i], i);
|
||||
displayedCount++;
|
||||
}
|
||||
|
||||
ImGui::PopStyleVar();
|
||||
@@ -108,9 +110,13 @@ void ProjectPanel::Render() {
|
||||
if (ImGui::BeginPopup("ItemContextMenu")) {
|
||||
if (m_contextMenuIndex >= 0 && m_contextMenuIndex < (int)items.size()) {
|
||||
auto& item = items[m_contextMenuIndex];
|
||||
if (item->isFolder) {
|
||||
if (item->isFolder || item->type == "Scene") {
|
||||
if (ImGui::MenuItem("Open")) {
|
||||
manager.NavigateToFolder(item);
|
||||
if (item->isFolder) {
|
||||
manager.NavigateToFolder(item);
|
||||
} else if (SceneEditorUtils::ConfirmSceneSwitch(*m_context)) {
|
||||
m_context->GetSceneManager().LoadScene(item->fullPath);
|
||||
}
|
||||
}
|
||||
ImGui::Separator();
|
||||
}
|
||||
@@ -276,8 +282,14 @@ void ProjectPanel::RenderAssetItem(const AssetItemPtr& item, int index) {
|
||||
}
|
||||
}
|
||||
|
||||
if (doubleClicked && item->isFolder) {
|
||||
manager.NavigateToFolder(item);
|
||||
if (doubleClicked) {
|
||||
if (item->isFolder) {
|
||||
manager.NavigateToFolder(item);
|
||||
} else if (item->type == "Scene") {
|
||||
if (SceneEditorUtils::ConfirmSceneSwitch(*m_context)) {
|
||||
m_context->GetSceneManager().LoadScene(item->fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
@@ -297,4 +309,4 @@ bool ProjectPanel::HandleDrop(const AssetItemPtr& targetFolder) {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user