refactor: rename ui_editor to editor for consistency
This commit is contained in:
303
editor/src/Application.cpp
Normal file
303
editor/src/Application.cpp
Normal file
@@ -0,0 +1,303 @@
|
||||
#include "Application.h"
|
||||
#include <imgui_impl_win32.h>
|
||||
#include <imgui_impl_dx12.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <stdio.h>
|
||||
|
||||
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
||||
|
||||
namespace UI {
|
||||
|
||||
Application& Application::Get() {
|
||||
static Application instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool Application::Initialize(HWND hwnd) {
|
||||
m_hwnd = hwnd;
|
||||
|
||||
if (!CreateDevice()) {
|
||||
MessageBoxW(hwnd, L"Failed to create D3D12 device", L"Error", MB_OK | MB_ICONERROR);
|
||||
return false;
|
||||
}
|
||||
if (!CreateRenderTarget()) {
|
||||
MessageBoxW(hwnd, L"Failed to create render target", L"Error", MB_OK | MB_ICONERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
||||
|
||||
io.Fonts->AddFontFromFileTTF("C:/Windows/Fonts/msyh.ttc", 16.0f);
|
||||
io.Fonts->AddFontDefault();
|
||||
|
||||
unsigned char* pixels;
|
||||
int width, height;
|
||||
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
|
||||
|
||||
ApplyUnityDarkTheme();
|
||||
|
||||
ImGui_ImplWin32_Init(hwnd);
|
||||
ImGui_ImplDX12_Init(m_device, 3, DXGI_FORMAT_R8G8B8A8_UNORM, m_srvHeap,
|
||||
m_srvHeap->GetCPUDescriptorHandleForHeapStart(),
|
||||
m_srvHeap->GetGPUDescriptorHandleForHeapStart());
|
||||
|
||||
m_menuBar = std::make_unique<MenuBar>();
|
||||
m_hierarchyPanel = std::make_unique<HierarchyPanel>();
|
||||
m_sceneViewPanel = std::make_unique<SceneViewPanel>();
|
||||
m_gameViewPanel = std::make_unique<GameViewPanel>();
|
||||
m_inspectorPanel = std::make_unique<InspectorPanel>();
|
||||
m_consolePanel = std::make_unique<ConsolePanel>();
|
||||
m_projectPanel = std::make_unique<ProjectPanel>();
|
||||
|
||||
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);
|
||||
}
|
||||
m_projectPanel->Initialize(exeDir);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Application::Shutdown() {
|
||||
ImGui_ImplDX12_Shutdown();
|
||||
ImGui_ImplWin32_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
|
||||
CleanupRenderTarget();
|
||||
|
||||
if (m_fence) m_fence->Release();
|
||||
if (m_commandList) m_commandList->Release();
|
||||
if (m_commandAllocator) m_commandAllocator->Release();
|
||||
if (m_commandQueue) m_commandQueue->Release();
|
||||
if (m_rtvHeap) m_rtvHeap->Release();
|
||||
if (m_srvHeap) m_srvHeap->Release();
|
||||
if (m_swapChain) m_swapChain->Release();
|
||||
if (m_device) m_device->Release();
|
||||
}
|
||||
|
||||
void Application::Render() {
|
||||
ImGui_ImplDX12_NewFrame();
|
||||
ImGui_ImplWin32_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
|
||||
SetupDockspace();
|
||||
RenderUI();
|
||||
|
||||
ImGui::Render();
|
||||
|
||||
m_frameIndex = m_swapChain->GetCurrentBackBufferIndex();
|
||||
m_commandAllocator->Reset();
|
||||
m_commandList->Reset(m_commandAllocator, nullptr);
|
||||
|
||||
D3D12_RESOURCE_BARRIER barrier = {};
|
||||
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
|
||||
barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
|
||||
barrier.Transition.pResource = m_renderTargets[m_frameIndex];
|
||||
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT;
|
||||
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
|
||||
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
|
||||
m_commandList->ResourceBarrier(1, &barrier);
|
||||
|
||||
float clearColor[4] = { 0.12f, 0.12f, 0.12f, 1.0f };
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = m_rtvHeap->GetCPUDescriptorHandleForHeapStart();
|
||||
rtvHandle.ptr += m_frameIndex * m_rtvDescriptorSize;
|
||||
m_commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);
|
||||
m_commandList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr);
|
||||
|
||||
ID3D12DescriptorHeap* heaps[] = { m_srvHeap };
|
||||
m_commandList->SetDescriptorHeaps(1, heaps);
|
||||
|
||||
ImGui_ImplDX12_RenderDrawData(ImGui::GetDrawData(), m_commandList);
|
||||
|
||||
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
|
||||
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;
|
||||
m_commandList->ResourceBarrier(1, &barrier);
|
||||
|
||||
m_commandList->Close();
|
||||
ID3D12CommandList* cmdLists[] = { m_commandList };
|
||||
m_commandQueue->ExecuteCommandLists(1, cmdLists);
|
||||
|
||||
m_swapChain->Present(1, 0);
|
||||
|
||||
m_fenceValue++;
|
||||
m_commandQueue->Signal(m_fence, m_fenceValue);
|
||||
if (m_fence->GetCompletedValue() < m_fenceValue) {
|
||||
m_fence->SetEventOnCompletion(m_fenceValue, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::OnResize(int width, int height) {
|
||||
if (width <= 0 || height <= 0) return;
|
||||
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
|
||||
CleanupRenderTarget();
|
||||
|
||||
if (m_swapChain) {
|
||||
DXGI_SWAP_CHAIN_DESC desc;
|
||||
m_swapChain->GetDesc(&desc);
|
||||
m_swapChain->ResizeBuffers(3, width, height, desc.BufferDesc.Format, desc.Flags);
|
||||
}
|
||||
|
||||
CreateRenderTarget();
|
||||
}
|
||||
|
||||
bool Application::CreateDevice() {
|
||||
HRESULT hr = D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&m_device));
|
||||
if (FAILED(hr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
|
||||
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
|
||||
queueDesc.Priority = 0;
|
||||
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
|
||||
queueDesc.NodeMask = 0;
|
||||
hr = m_device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&m_commandQueue));
|
||||
if (FAILED(hr)) return false;
|
||||
|
||||
hr = m_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&m_commandAllocator));
|
||||
if (FAILED(hr)) return false;
|
||||
|
||||
hr = m_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, m_commandAllocator, nullptr, IID_PPV_ARGS(&m_commandList));
|
||||
if (FAILED(hr)) return false;
|
||||
m_commandList->Close();
|
||||
|
||||
hr = m_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_fence));
|
||||
if (FAILED(hr)) return false;
|
||||
|
||||
IDXGIFactory4* factory = nullptr;
|
||||
hr = CreateDXGIFactory1(IID_PPV_ARGS(&factory));
|
||||
if (FAILED(hr)) return false;
|
||||
|
||||
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
|
||||
swapChainDesc.BufferCount = 3;
|
||||
swapChainDesc.Width = m_width;
|
||||
swapChainDesc.Height = m_height;
|
||||
swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
||||
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
|
||||
swapChainDesc.SampleDesc.Count = 1;
|
||||
|
||||
IDXGISwapChain1* swapChain1 = nullptr;
|
||||
hr = factory->CreateSwapChainForHwnd(m_commandQueue, m_hwnd, &swapChainDesc, nullptr, nullptr, &swapChain1);
|
||||
factory->Release();
|
||||
if (FAILED(hr)) return false;
|
||||
|
||||
hr = swapChain1->QueryInterface(IID_PPV_ARGS(&m_swapChain));
|
||||
swapChain1->Release();
|
||||
if (FAILED(hr)) return false;
|
||||
|
||||
D3D12_DESCRIPTOR_HEAP_DESC rtvDesc = {};
|
||||
rtvDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
|
||||
rtvDesc.NumDescriptors = 3;
|
||||
rtvDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
|
||||
hr = m_device->CreateDescriptorHeap(&rtvDesc, IID_PPV_ARGS(&m_rtvHeap));
|
||||
if (FAILED(hr)) return false;
|
||||
m_rtvDescriptorSize = m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
|
||||
|
||||
D3D12_DESCRIPTOR_HEAP_DESC srvDesc = {};
|
||||
srvDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
|
||||
srvDesc.NumDescriptors = 1;
|
||||
srvDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
|
||||
hr = m_device->CreateDescriptorHeap(&srvDesc, IID_PPV_ARGS(&m_srvHeap));
|
||||
if (FAILED(hr)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Application::CreateRenderTarget() {
|
||||
if (!m_swapChain || !m_device || !m_rtvHeap) return false;
|
||||
|
||||
for (UINT i = 0; i < 3; i++) {
|
||||
HRESULT hr = m_swapChain->GetBuffer(i, IID_PPV_ARGS(&m_renderTargets[i]));
|
||||
if (FAILED(hr)) return false;
|
||||
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = m_rtvHeap->GetCPUDescriptorHandleForHeapStart();
|
||||
rtvHandle.ptr += i * m_rtvDescriptorSize;
|
||||
m_device->CreateRenderTargetView(m_renderTargets[i], nullptr, rtvHandle);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Application::CleanupRenderTarget() {
|
||||
for (UINT i = 0; i < 3; i++) {
|
||||
if (m_renderTargets[i]) {
|
||||
m_renderTargets[i]->Release();
|
||||
m_renderTargets[i] = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Application::SetupDockspace() {
|
||||
static ImGuiDockNodeFlags dockspaceFlags = ImGuiDockNodeFlags_NoWindowMenuButton;
|
||||
|
||||
ImGuiWindowFlags windowFlags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking;
|
||||
|
||||
ImGuiViewport* viewport = ImGui::GetMainViewport();
|
||||
ImGui::SetNextWindowPos(viewport->Pos);
|
||||
ImGui::SetNextWindowSize(viewport->Size);
|
||||
ImGui::SetNextWindowViewport(viewport->ID);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
|
||||
windowFlags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove;
|
||||
windowFlags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus;
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
|
||||
ImGui::Begin("MainDockspace", nullptr, windowFlags);
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopStyleVar(2);
|
||||
|
||||
ImGuiID dockspaceId = ImGui::GetID("MyDockspace");
|
||||
ImGui::DockSpace(dockspaceId, ImVec2(0.0f, 0.0f), dockspaceFlags);
|
||||
|
||||
static bool firstTime = true;
|
||||
if (firstTime) {
|
||||
firstTime = false;
|
||||
ImGui::DockBuilderRemoveNode(dockspaceId);
|
||||
ImGui::DockBuilderAddNode(dockspaceId, dockspaceFlags | ImGuiDockNodeFlags_DockSpace);
|
||||
ImGui::DockBuilderSetNodeSize(dockspaceId, viewport->Size);
|
||||
|
||||
ImGuiID dockMain = dockspaceId;
|
||||
ImGuiID dockBottom = ImGui::DockBuilderSplitNode(dockMain, ImGuiDir_Down, 0.25f, nullptr, &dockMain);
|
||||
ImGuiID dockLeft = ImGui::DockBuilderSplitNode(dockMain, ImGuiDir_Left, 0.15f, nullptr, &dockMain);
|
||||
ImGuiID dockRight = ImGui::DockBuilderSplitNode(dockMain, ImGuiDir_Right, 0.25f, nullptr, &dockMain);
|
||||
|
||||
ImGui::DockBuilderDockWindow("Hierarchy", dockLeft);
|
||||
ImGui::DockBuilderDockWindow("Scene", dockMain);
|
||||
ImGui::DockBuilderDockWindow("Game", dockMain);
|
||||
ImGui::DockBuilderDockWindow("Inspector", dockRight);
|
||||
ImGui::DockBuilderDockWindow("Console", dockBottom);
|
||||
ImGui::DockBuilderDockWindow("Project", dockBottom);
|
||||
|
||||
ImGui::DockBuilderFinish(dockspaceId);
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void Application::RenderUI() {
|
||||
m_menuBar->Render();
|
||||
|
||||
m_hierarchyPanel->Render();
|
||||
m_sceneViewPanel->Render();
|
||||
m_gameViewPanel->Render();
|
||||
m_inspectorPanel->Render();
|
||||
m_consolePanel->Render();
|
||||
m_projectPanel->Render();
|
||||
}
|
||||
|
||||
}
|
||||
65
editor/src/Application.h
Normal file
65
editor/src/Application.h
Normal file
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <imgui.h>
|
||||
#include <d3d12.h>
|
||||
#include <dxgi1_6.h>
|
||||
|
||||
#include "Theme.h"
|
||||
#include "panels/Panel.h"
|
||||
#include "panels/MenuBar.h"
|
||||
#include "panels/HierarchyPanel.h"
|
||||
#include "panels/SceneViewPanel.h"
|
||||
#include "panels/GameViewPanel.h"
|
||||
#include "panels/InspectorPanel.h"
|
||||
#include "panels/ConsolePanel.h"
|
||||
#include "panels/ProjectPanel.h"
|
||||
|
||||
namespace UI {
|
||||
|
||||
class Application {
|
||||
public:
|
||||
static Application& Get();
|
||||
|
||||
bool Initialize(HWND hwnd);
|
||||
void Shutdown();
|
||||
void Render();
|
||||
void OnResize(int width, int height);
|
||||
|
||||
private:
|
||||
Application() = default;
|
||||
~Application() = default;
|
||||
|
||||
bool CreateDevice();
|
||||
bool CreateRenderTarget();
|
||||
void CleanupRenderTarget();
|
||||
void SetupDockspace();
|
||||
void RenderUI();
|
||||
|
||||
HWND m_hwnd = nullptr;
|
||||
int m_width = 1280;
|
||||
int m_height = 720;
|
||||
|
||||
ID3D12Device* m_device = nullptr;
|
||||
ID3D12CommandQueue* m_commandQueue = nullptr;
|
||||
ID3D12CommandAllocator* m_commandAllocator = nullptr;
|
||||
ID3D12GraphicsCommandList* m_commandList = nullptr;
|
||||
IDXGISwapChain3* m_swapChain = nullptr;
|
||||
ID3D12DescriptorHeap* m_rtvHeap = nullptr;
|
||||
ID3D12DescriptorHeap* m_srvHeap = nullptr;
|
||||
ID3D12Resource* m_renderTargets[3] = {};
|
||||
ID3D12Fence* m_fence = nullptr;
|
||||
UINT64 m_fenceValue = 0;
|
||||
UINT m_rtvDescriptorSize = 0;
|
||||
UINT m_frameIndex = 0;
|
||||
|
||||
std::unique_ptr<MenuBar> m_menuBar;
|
||||
std::unique_ptr<HierarchyPanel> m_hierarchyPanel;
|
||||
std::unique_ptr<SceneViewPanel> m_sceneViewPanel;
|
||||
std::unique_ptr<GameViewPanel> m_gameViewPanel;
|
||||
std::unique_ptr<InspectorPanel> m_inspectorPanel;
|
||||
std::unique_ptr<ConsolePanel> m_consolePanel;
|
||||
std::unique_ptr<ProjectPanel> m_projectPanel;
|
||||
};
|
||||
|
||||
}
|
||||
19
editor/src/Core/AssetItem.h
Normal file
19
editor/src/Core/AssetItem.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace UI {
|
||||
|
||||
struct AssetItem {
|
||||
std::string name;
|
||||
std::string type;
|
||||
bool isFolder;
|
||||
std::string fullPath;
|
||||
std::vector<std::shared_ptr<AssetItem>> children;
|
||||
};
|
||||
|
||||
using AssetItemPtr = std::shared_ptr<AssetItem>;
|
||||
|
||||
}
|
||||
138
editor/src/Core/GameObject.h
Normal file
138
editor/src/Core/GameObject.h
Normal file
@@ -0,0 +1,138 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <functional>
|
||||
#include <cstdint>
|
||||
|
||||
#include <XCEngine/Core/Event.h>
|
||||
|
||||
namespace UI {
|
||||
|
||||
using EntityID = uint64_t;
|
||||
constexpr EntityID INVALID_ENTITY_ID = 0;
|
||||
|
||||
class Component;
|
||||
class TransformComponent;
|
||||
|
||||
class GameObject {
|
||||
public:
|
||||
EntityID id = INVALID_ENTITY_ID;
|
||||
std::string name;
|
||||
EntityID parent = INVALID_ENTITY_ID;
|
||||
std::vector<EntityID> children;
|
||||
std::vector<std::unique_ptr<Component>> components;
|
||||
bool selected = false;
|
||||
|
||||
template<typename T, typename... Args>
|
||||
T* AddComponent(Args&&... args) {
|
||||
auto comp = std::make_unique<T>(std::forward<Args>(args)...);
|
||||
comp->m_gameObject = this;
|
||||
T* ptr = comp.get();
|
||||
components.push_back(std::move(comp));
|
||||
return ptr;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T* GetComponent() {
|
||||
for (auto& comp : components) {
|
||||
if (auto casted = dynamic_cast<T*>(comp.get())) {
|
||||
return casted;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::vector<T*> GetComponents() {
|
||||
std::vector<T*> result;
|
||||
for (auto& comp : components) {
|
||||
if (auto casted = dynamic_cast<T*>(comp.get())) {
|
||||
result.push_back(casted);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
TransformComponent* GetTransform() {
|
||||
return GetComponent<TransformComponent>();
|
||||
}
|
||||
};
|
||||
|
||||
class Component {
|
||||
public:
|
||||
virtual ~Component() = default;
|
||||
virtual std::string GetName() const = 0;
|
||||
|
||||
virtual void Awake() {}
|
||||
virtual void Start() {}
|
||||
virtual void Update(float deltaTime) {}
|
||||
virtual void OnDestroy() {}
|
||||
|
||||
GameObject* GetGameObject() const { return m_gameObject; }
|
||||
bool IsEnabled() const { return m_enabled; }
|
||||
void SetEnabled(bool enabled) { m_enabled = enabled; }
|
||||
|
||||
protected:
|
||||
GameObject* m_gameObject = nullptr;
|
||||
bool m_enabled = true;
|
||||
|
||||
friend class GameObject;
|
||||
};
|
||||
|
||||
class TransformComponent : public Component {
|
||||
public:
|
||||
float position[3] = {0.0f, 0.0f, 0.0f};
|
||||
float rotation[3] = {0.0f, 0.0f, 0.0f};
|
||||
float scale[3] = {1.0f, 1.0f, 1.0f};
|
||||
|
||||
std::string GetName() const override { return "Transform"; }
|
||||
};
|
||||
|
||||
class MeshRendererComponent : public Component {
|
||||
public:
|
||||
std::string materialName = "Default-Material";
|
||||
std::string meshName = "";
|
||||
|
||||
std::string GetName() const override { return "Mesh Renderer"; }
|
||||
};
|
||||
|
||||
using ComponentInspectorFn = std::function<void(Component*)>;
|
||||
|
||||
struct ComponentInspectorInfo {
|
||||
std::string name;
|
||||
ComponentInspectorFn renderFn;
|
||||
};
|
||||
|
||||
class ComponentRegistry {
|
||||
public:
|
||||
static ComponentRegistry& Get() {
|
||||
static ComponentRegistry instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void RegisterComponent(const std::string& name, ComponentInspectorFn inspectorFn) {
|
||||
m_inspectors[name] = {name, inspectorFn};
|
||||
m_factories[name] = []() -> std::unique_ptr<Component> {
|
||||
return std::make_unique<T>();
|
||||
};
|
||||
}
|
||||
|
||||
ComponentInspectorInfo* GetInspector(const std::string& name) {
|
||||
auto it = m_inspectors.find(name);
|
||||
if (it != m_inspectors.end()) {
|
||||
return &it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
ComponentRegistry() = default;
|
||||
std::unordered_map<std::string, ComponentInspectorInfo> m_inspectors;
|
||||
std::unordered_map<std::string, std::function<std::unique_ptr<Component>()>> m_factories;
|
||||
};
|
||||
|
||||
}
|
||||
14
editor/src/Core/LogEntry.h
Normal file
14
editor/src/Core/LogEntry.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <XCEngine/Debug/LogLevel.h>
|
||||
|
||||
namespace UI {
|
||||
|
||||
struct LogEntry {
|
||||
XCEngine::Debug::LogLevel level;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
}
|
||||
19
editor/src/Managers/LogSystem.cpp
Normal file
19
editor/src/Managers/LogSystem.cpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#include "LogSystem.h"
|
||||
|
||||
namespace UI {
|
||||
|
||||
LogSystem& LogSystem::Get() {
|
||||
static LogSystem instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void LogSystem::AddLog(XCEngine::Debug::LogLevel level, const std::string& message) {
|
||||
m_logs.push_back({level, message});
|
||||
if (m_callback) m_callback();
|
||||
}
|
||||
|
||||
void LogSystem::Clear() {
|
||||
m_logs.clear();
|
||||
}
|
||||
|
||||
}
|
||||
26
editor/src/Managers/LogSystem.h
Normal file
26
editor/src/Managers/LogSystem.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "Core/LogEntry.h"
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
namespace UI {
|
||||
|
||||
class LogSystem {
|
||||
public:
|
||||
static LogSystem& Get();
|
||||
|
||||
void AddLog(XCEngine::Debug::LogLevel level, const std::string& message);
|
||||
void Clear();
|
||||
const std::vector<LogEntry>& GetLogs() const { return m_logs; }
|
||||
|
||||
void SetCallback(std::function<void()> callback) { m_callback = callback; }
|
||||
|
||||
private:
|
||||
LogSystem() = default;
|
||||
|
||||
std::vector<LogEntry> m_logs;
|
||||
std::function<void()> m_callback;
|
||||
};
|
||||
|
||||
}
|
||||
246
editor/src/Managers/ProjectManager.cpp
Normal file
246
editor/src/Managers/ProjectManager.cpp
Normal file
@@ -0,0 +1,246 @@
|
||||
#include "ProjectManager.h"
|
||||
#include <filesystem>
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <windows.h>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace UI {
|
||||
|
||||
ProjectManager& ProjectManager::Get() {
|
||||
static ProjectManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
std::vector<AssetItemPtr>& ProjectManager::GetCurrentItems() {
|
||||
if (m_path.empty()) {
|
||||
static std::vector<AssetItemPtr> empty;
|
||||
return empty;
|
||||
}
|
||||
return m_path.back()->children;
|
||||
}
|
||||
|
||||
void ProjectManager::NavigateToFolder(const AssetItemPtr& folder) {
|
||||
m_path.push_back(folder);
|
||||
m_selectedIndex = -1;
|
||||
}
|
||||
|
||||
void ProjectManager::NavigateBack() {
|
||||
if (m_path.size() > 1) {
|
||||
m_path.pop_back();
|
||||
m_selectedIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectManager::NavigateToIndex(size_t index) {
|
||||
if (index >= m_path.size()) return;
|
||||
while (m_path.size() > index + 1) {
|
||||
m_path.pop_back();
|
||||
}
|
||||
m_selectedIndex = -1;
|
||||
}
|
||||
|
||||
std::string ProjectManager::GetCurrentPath() const {
|
||||
if (m_path.empty()) return "Assets";
|
||||
std::string result = "Assets";
|
||||
for (size_t i = 1; i < m_path.size(); i++) {
|
||||
result += "/";
|
||||
result += m_path[i]->name;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string ProjectManager::GetPathName(size_t index) const {
|
||||
if (index >= m_path.size()) return "";
|
||||
return m_path[index]->name;
|
||||
}
|
||||
|
||||
static std::wstring Utf8ToWstring(const std::string& str) {
|
||||
if (str.empty()) return L"";
|
||||
int len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, nullptr, 0);
|
||||
if (len <= 0) return L"";
|
||||
std::wstring result(len - 1, 0);
|
||||
MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, &result[0], len);
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::string WstringToUtf8(const std::wstring& wstr) {
|
||||
if (wstr.empty()) return "";
|
||||
int len = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, nullptr, 0, nullptr, nullptr);
|
||||
if (len <= 0) return "";
|
||||
std::string result(len - 1, 0);
|
||||
WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, &result[0], len, nullptr, nullptr);
|
||||
return result;
|
||||
}
|
||||
|
||||
void ProjectManager::Initialize(const std::string& projectPath) {
|
||||
m_projectPath = projectPath;
|
||||
|
||||
std::wstring projectPathW = Utf8ToWstring(projectPath);
|
||||
fs::path assetsPath = fs::path(projectPathW) / L"Assets";
|
||||
|
||||
try {
|
||||
if (!fs::exists(assetsPath)) {
|
||||
fs::create_directories(assetsPath);
|
||||
fs::create_directories(assetsPath / L"Textures");
|
||||
fs::create_directories(assetsPath / L"Models");
|
||||
fs::create_directories(assetsPath / L"Scripts");
|
||||
fs::create_directories(assetsPath / L"Materials");
|
||||
fs::create_directories(assetsPath / L"Scenes");
|
||||
|
||||
std::ofstream((assetsPath / L"Textures" / L"Grass.png").wstring());
|
||||
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());
|
||||
m_rootFolder->name = "Assets";
|
||||
m_rootFolder->fullPath = WstringToUtf8(assetsPath.wstring());
|
||||
|
||||
m_path.clear();
|
||||
m_path.push_back(m_rootFolder);
|
||||
m_selectedIndex = -1;
|
||||
} catch (const std::exception& e) {
|
||||
m_rootFolder = std::make_shared<AssetItem>();
|
||||
m_rootFolder->name = "Assets";
|
||||
m_rootFolder->isFolder = true;
|
||||
m_rootFolder->type = "Folder";
|
||||
m_path.push_back(m_rootFolder);
|
||||
}
|
||||
}
|
||||
|
||||
std::wstring ProjectManager::GetCurrentFullPathW() const {
|
||||
if (m_path.empty()) return Utf8ToWstring(m_projectPath);
|
||||
|
||||
std::wstring fullPath = Utf8ToWstring(m_projectPath);
|
||||
for (size_t i = 0; i < m_path.size(); i++) {
|
||||
fullPath += L"/" + Utf8ToWstring(m_path[i]->name);
|
||||
}
|
||||
return fullPath;
|
||||
}
|
||||
|
||||
void ProjectManager::RefreshCurrentFolder() {
|
||||
if (m_path.empty()) return;
|
||||
|
||||
try {
|
||||
auto newFolder = ScanDirectory(GetCurrentFullPathW());
|
||||
m_path.back()->children = newFolder->children;
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectManager::CreateFolder(const std::string& name) {
|
||||
try {
|
||||
std::wstring fullPath = GetCurrentFullPathW();
|
||||
fs::path newFolderPath = fs::path(fullPath) / Utf8ToWstring(name);
|
||||
fs::create_directory(newFolderPath);
|
||||
RefreshCurrentFolder();
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectManager::DeleteItem(int index) {
|
||||
if (m_path.empty()) return;
|
||||
auto& items = m_path.back()->children;
|
||||
if (index < 0 || index >= (int)items.size()) return;
|
||||
|
||||
try {
|
||||
std::wstring fullPath = GetCurrentFullPathW();
|
||||
fs::path itemPath = fs::path(fullPath) / Utf8ToWstring(items[index]->name);
|
||||
fs::remove_all(itemPath);
|
||||
m_selectedIndex = -1;
|
||||
RefreshCurrentFolder();
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
bool ProjectManager::MoveItem(const std::string& sourceFullPath, const std::string& destFolderFullPath) {
|
||||
try {
|
||||
fs::path sourcePath = Utf8ToWstring(sourceFullPath);
|
||||
fs::path destPath = fs::path(Utf8ToWstring(destFolderFullPath)) / sourcePath.filename();
|
||||
|
||||
if (!fs::exists(sourcePath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fs::exists(destPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fs::rename(sourcePath, destPath);
|
||||
RefreshCurrentFolder();
|
||||
return true;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
AssetItemPtr ProjectManager::ScanDirectory(const std::wstring& path) {
|
||||
auto folder = std::make_shared<AssetItem>();
|
||||
folder->name = WstringToUtf8(fs::path(path).filename().wstring());
|
||||
folder->isFolder = true;
|
||||
folder->type = "Folder";
|
||||
|
||||
if (!fs::exists(path)) return folder;
|
||||
|
||||
std::vector<AssetItemPtr> items;
|
||||
|
||||
try {
|
||||
for (const auto& entry : fs::directory_iterator(path)) {
|
||||
std::wstring nameW = entry.path().filename().wstring();
|
||||
bool isFolder = entry.is_directory();
|
||||
items.push_back(CreateAssetItem(entry.path().wstring(), nameW, isFolder));
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
|
||||
std::sort(items.begin(), items.end(), [](const AssetItemPtr& a, const AssetItemPtr& b) {
|
||||
if (a->isFolder != b->isFolder) return a->isFolder;
|
||||
return a->name < b->name;
|
||||
});
|
||||
|
||||
folder->children = items;
|
||||
return folder;
|
||||
}
|
||||
|
||||
AssetItemPtr ProjectManager::CreateAssetItem(const std::wstring& path, const std::wstring& nameW, bool isFolder) {
|
||||
auto item = std::make_shared<AssetItem>();
|
||||
item->name = WstringToUtf8(nameW);
|
||||
item->isFolder = isFolder;
|
||||
item->fullPath = WstringToUtf8(path);
|
||||
|
||||
if (isFolder) {
|
||||
item->type = "Folder";
|
||||
try {
|
||||
auto subFolder = ScanDirectory(path);
|
||||
item->children = subFolder->children;
|
||||
} catch (...) {
|
||||
}
|
||||
} else {
|
||||
std::wstring ext = fs::path(path).extension().wstring();
|
||||
std::transform(ext.begin(), ext.end(), ext.begin(), ::towlower);
|
||||
|
||||
if (ext == L".png" || ext == L".jpg" || ext == L".tga" || ext == L".bmp") {
|
||||
item->type = "Texture";
|
||||
} else if (ext == L".fbx" || ext == L".obj" || ext == L".gltf" || ext == L".glb") {
|
||||
item->type = "Model";
|
||||
} else if (ext == L".cs" || ext == L".cpp" || ext == L".h") {
|
||||
item->type = "Script";
|
||||
} else if (ext == L".mat") {
|
||||
item->type = "Material";
|
||||
} else if (ext == L".unity" || ext == L".scene") {
|
||||
item->type = "Scene";
|
||||
} else if (ext == L".prefab") {
|
||||
item->type = "Prefab";
|
||||
} else {
|
||||
item->type = "File";
|
||||
}
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
}
|
||||
49
editor/src/Managers/ProjectManager.h
Normal file
49
editor/src/Managers/ProjectManager.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include "Core/AssetItem.h"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
namespace UI {
|
||||
|
||||
class ProjectManager {
|
||||
public:
|
||||
static ProjectManager& Get();
|
||||
|
||||
std::vector<AssetItemPtr>& GetCurrentItems();
|
||||
int GetSelectedIndex() const { return m_selectedIndex; }
|
||||
void SetSelectedIndex(int index) { m_selectedIndex = index; }
|
||||
|
||||
void NavigateToFolder(const AssetItemPtr& folder);
|
||||
void NavigateBack();
|
||||
void NavigateToIndex(size_t index);
|
||||
bool CanNavigateBack() const { return m_path.size() > 1; }
|
||||
|
||||
std::string GetCurrentPath() const;
|
||||
size_t GetPathDepth() const { return m_path.size(); }
|
||||
std::string GetPathName(size_t index) const;
|
||||
|
||||
void Initialize(const std::string& projectPath);
|
||||
void RefreshCurrentFolder();
|
||||
|
||||
void CreateFolder(const std::string& name);
|
||||
void DeleteItem(int index);
|
||||
bool MoveItem(const std::string& sourceFullPath, const std::string& destFolderFullPath);
|
||||
|
||||
const std::string& GetProjectPath() const { return m_projectPath; }
|
||||
|
||||
private:
|
||||
ProjectManager() = default;
|
||||
|
||||
AssetItemPtr ScanDirectory(const std::wstring& path);
|
||||
AssetItemPtr CreateAssetItem(const std::wstring& path, const std::wstring& nameW, bool isFolder);
|
||||
std::wstring GetCurrentFullPathW() const;
|
||||
|
||||
AssetItemPtr m_rootFolder;
|
||||
std::vector<AssetItemPtr> m_path;
|
||||
int m_selectedIndex = -1;
|
||||
std::string m_projectPath;
|
||||
};
|
||||
|
||||
}
|
||||
186
editor/src/Managers/SceneManager.cpp
Normal file
186
editor/src/Managers/SceneManager.cpp
Normal file
@@ -0,0 +1,186 @@
|
||||
#include "SceneManager.h"
|
||||
#include "SelectionManager.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace UI {
|
||||
|
||||
EntityID SceneManager::CreateEntity(const std::string& name, EntityID parent) {
|
||||
EntityID id = m_nextEntityId++;
|
||||
GameObject entity;
|
||||
entity.id = id;
|
||||
entity.name = name;
|
||||
entity.parent = parent;
|
||||
m_entities[id] = std::move(entity);
|
||||
|
||||
if (parent != INVALID_ENTITY_ID) {
|
||||
m_entities[parent].children.push_back(id);
|
||||
} else {
|
||||
m_rootEntities.push_back(id);
|
||||
}
|
||||
|
||||
OnEntityCreated.Invoke(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
void SceneManager::DeleteEntity(EntityID id) {
|
||||
auto it = m_entities.find(id);
|
||||
if (it == m_entities.end()) return;
|
||||
|
||||
GameObject& entity = it->second;
|
||||
|
||||
std::vector<EntityID> childrenToDelete = entity.children;
|
||||
for (EntityID childId : childrenToDelete) {
|
||||
DeleteEntity(childId);
|
||||
}
|
||||
|
||||
if (entity.parent != INVALID_ENTITY_ID) {
|
||||
auto* parent = GetEntity(entity.parent);
|
||||
if (parent) {
|
||||
auto& siblings = parent->children;
|
||||
siblings.erase(std::remove(siblings.begin(), siblings.end(), id), siblings.end());
|
||||
}
|
||||
} else {
|
||||
m_rootEntities.erase(std::remove(m_rootEntities.begin(), m_rootEntities.end(), id), m_rootEntities.end());
|
||||
}
|
||||
|
||||
if (SelectionManager::Get().GetSelectedEntity() == id) {
|
||||
SelectionManager::Get().ClearSelection();
|
||||
}
|
||||
|
||||
m_entities.erase(it);
|
||||
OnEntityDeleted.Invoke(id);
|
||||
}
|
||||
|
||||
ClipboardData SceneManager::CopyEntityRecursive(const GameObject* entity) {
|
||||
ClipboardData data;
|
||||
data.name = entity->name;
|
||||
|
||||
for (const auto& comp : entity->components) {
|
||||
if (auto* transform = dynamic_cast<const TransformComponent*>(comp.get())) {
|
||||
auto newComp = std::make_unique<TransformComponent>();
|
||||
memcpy(newComp->position, transform->position, sizeof(transform->position));
|
||||
memcpy(newComp->rotation, transform->rotation, sizeof(transform->rotation));
|
||||
memcpy(newComp->scale, transform->scale, sizeof(transform->scale));
|
||||
data.components.push_back(std::move(newComp));
|
||||
}
|
||||
else if (auto* meshRenderer = dynamic_cast<const MeshRendererComponent*>(comp.get())) {
|
||||
auto newComp = std::make_unique<MeshRendererComponent>();
|
||||
newComp->materialName = meshRenderer->materialName;
|
||||
newComp->meshName = meshRenderer->meshName;
|
||||
data.components.push_back(std::move(newComp));
|
||||
}
|
||||
}
|
||||
|
||||
for (EntityID childId : entity->children) {
|
||||
const GameObject* child = GetEntity(childId);
|
||||
if (child) {
|
||||
data.children.push_back(CopyEntityRecursive(child));
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
void SceneManager::CopyEntity(EntityID id) {
|
||||
const GameObject* entity = GetEntity(id);
|
||||
if (!entity) return;
|
||||
|
||||
m_clipboard = CopyEntityRecursive(entity);
|
||||
}
|
||||
|
||||
EntityID SceneManager::PasteEntityRecursive(const ClipboardData& data, EntityID parent) {
|
||||
EntityID newId = CreateEntity(data.name, parent);
|
||||
GameObject* newEntity = GetEntity(newId);
|
||||
|
||||
if (newEntity) {
|
||||
newEntity->components.clear();
|
||||
for (const auto& comp : data.components) {
|
||||
if (auto* transform = dynamic_cast<const TransformComponent*>(comp.get())) {
|
||||
auto newComp = std::make_unique<TransformComponent>();
|
||||
memcpy(newComp->position, transform->position, sizeof(transform->position));
|
||||
memcpy(newComp->rotation, transform->rotation, sizeof(transform->rotation));
|
||||
memcpy(newComp->scale, transform->scale, sizeof(transform->scale));
|
||||
newEntity->components.push_back(std::move(newComp));
|
||||
}
|
||||
else if (auto* meshRenderer = dynamic_cast<const MeshRendererComponent*>(comp.get())) {
|
||||
auto newComp = std::make_unique<MeshRendererComponent>();
|
||||
newComp->materialName = meshRenderer->materialName;
|
||||
newComp->meshName = meshRenderer->meshName;
|
||||
newEntity->components.push_back(std::move(newComp));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& childData : data.children) {
|
||||
PasteEntityRecursive(childData, newId);
|
||||
}
|
||||
|
||||
return newId;
|
||||
}
|
||||
|
||||
EntityID SceneManager::PasteEntity(EntityID parent) {
|
||||
if (!m_clipboard) return INVALID_ENTITY_ID;
|
||||
return PasteEntityRecursive(*m_clipboard, parent);
|
||||
}
|
||||
|
||||
EntityID SceneManager::DuplicateEntity(EntityID id) {
|
||||
CopyEntity(id);
|
||||
const GameObject* entity = GetEntity(id);
|
||||
if (!entity) return INVALID_ENTITY_ID;
|
||||
return PasteEntity(entity->parent);
|
||||
}
|
||||
|
||||
void SceneManager::MoveEntity(EntityID id, EntityID newParent) {
|
||||
GameObject* entity = GetEntity(id);
|
||||
if (!entity || id == newParent) return;
|
||||
|
||||
if (entity->parent != INVALID_ENTITY_ID) {
|
||||
GameObject* oldParent = GetEntity(entity->parent);
|
||||
if (oldParent) {
|
||||
auto& siblings = oldParent->children;
|
||||
siblings.erase(std::remove(siblings.begin(), siblings.end(), id), siblings.end());
|
||||
}
|
||||
} else {
|
||||
m_rootEntities.erase(std::remove(m_rootEntities.begin(), m_rootEntities.end(), id), m_rootEntities.end());
|
||||
}
|
||||
|
||||
entity->parent = newParent;
|
||||
|
||||
if (newParent != INVALID_ENTITY_ID) {
|
||||
GameObject* newParentEntity = GetEntity(newParent);
|
||||
if (newParentEntity) {
|
||||
newParentEntity->children.push_back(id);
|
||||
}
|
||||
} else {
|
||||
m_rootEntities.push_back(id);
|
||||
}
|
||||
|
||||
OnEntityChanged.Invoke(id);
|
||||
}
|
||||
|
||||
void SceneManager::CreateDemoScene() {
|
||||
m_entities.clear();
|
||||
m_rootEntities.clear();
|
||||
m_nextEntityId = 1;
|
||||
m_clipboard.reset();
|
||||
|
||||
EntityID camera = CreateEntity("Main Camera");
|
||||
GetEntity(camera)->AddComponent<TransformComponent>();
|
||||
|
||||
EntityID light = CreateEntity("Directional Light");
|
||||
|
||||
EntityID cube = CreateEntity("Cube");
|
||||
GetEntity(cube)->AddComponent<TransformComponent>();
|
||||
GetEntity(cube)->AddComponent<MeshRendererComponent>()->meshName = "Cube Mesh";
|
||||
|
||||
EntityID sphere = CreateEntity("Sphere");
|
||||
GetEntity(sphere)->AddComponent<TransformComponent>();
|
||||
GetEntity(sphere)->AddComponent<MeshRendererComponent>()->meshName = "Sphere Mesh";
|
||||
|
||||
EntityID player = CreateEntity("Player");
|
||||
EntityID weapon = CreateEntity("Weapon", player);
|
||||
|
||||
OnSceneChanged.Invoke();
|
||||
}
|
||||
|
||||
}
|
||||
87
editor/src/Managers/SceneManager.h
Normal file
87
editor/src/Managers/SceneManager.h
Normal file
@@ -0,0 +1,87 @@
|
||||
#pragma once
|
||||
|
||||
#include "Core/GameObject.h"
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
#include <XCEngine/Core/Event.h>
|
||||
|
||||
namespace UI {
|
||||
|
||||
struct ClipboardData {
|
||||
std::string name;
|
||||
std::vector<std::unique_ptr<Component>> components;
|
||||
std::vector<ClipboardData> children;
|
||||
};
|
||||
|
||||
class SceneManager {
|
||||
public:
|
||||
static SceneManager& Get() {
|
||||
static SceneManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
EntityID CreateEntity(const std::string& name, EntityID parent = INVALID_ENTITY_ID);
|
||||
|
||||
GameObject* GetEntity(EntityID id) {
|
||||
auto it = m_entities.find(id);
|
||||
if (it != m_entities.end()) {
|
||||
return &it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const GameObject* GetEntity(EntityID id) const {
|
||||
auto it = m_entities.find(id);
|
||||
if (it != m_entities.end()) {
|
||||
return &it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const std::vector<EntityID>& GetRootEntities() const {
|
||||
return m_rootEntities;
|
||||
}
|
||||
|
||||
void DeleteEntity(EntityID id);
|
||||
|
||||
void RenameEntity(EntityID id, const std::string& newName) {
|
||||
auto* entity = GetEntity(id);
|
||||
if (entity) {
|
||||
entity->name = newName;
|
||||
OnEntityChanged.Invoke(id);
|
||||
}
|
||||
}
|
||||
|
||||
void CopyEntity(EntityID id);
|
||||
|
||||
EntityID PasteEntity(EntityID parent = INVALID_ENTITY_ID);
|
||||
|
||||
EntityID DuplicateEntity(EntityID id);
|
||||
|
||||
void MoveEntity(EntityID id, EntityID newParent);
|
||||
|
||||
void CreateDemoScene();
|
||||
|
||||
bool HasClipboardData() const { return m_clipboard.has_value(); }
|
||||
|
||||
XCEngine::Core::Event<EntityID> OnEntityCreated;
|
||||
XCEngine::Core::Event<EntityID> OnEntityDeleted;
|
||||
XCEngine::Core::Event<EntityID> OnEntityChanged;
|
||||
XCEngine::Core::Event<> OnSceneChanged;
|
||||
|
||||
private:
|
||||
SceneManager() = default;
|
||||
|
||||
ClipboardData CopyEntityRecursive(const GameObject* entity);
|
||||
EntityID PasteEntityRecursive(const ClipboardData& data, EntityID parent);
|
||||
|
||||
EntityID m_nextEntityId = 1;
|
||||
std::unordered_map<EntityID, GameObject> m_entities;
|
||||
std::vector<EntityID> m_rootEntities;
|
||||
std::optional<ClipboardData> m_clipboard;
|
||||
};
|
||||
|
||||
}
|
||||
39
editor/src/Managers/SelectionManager.h
Normal file
39
editor/src/Managers/SelectionManager.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include "Core/GameObject.h"
|
||||
#include <unordered_set>
|
||||
|
||||
#include <XCEngine/Core/Event.h>
|
||||
|
||||
namespace UI {
|
||||
|
||||
class SelectionManager {
|
||||
public:
|
||||
static SelectionManager& Get() {
|
||||
static SelectionManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
EntityID GetSelectedEntity() const { return m_selectedEntity; }
|
||||
|
||||
void SetSelectedEntity(EntityID id) {
|
||||
m_selectedEntity = id;
|
||||
OnSelectionChanged.Invoke(id);
|
||||
}
|
||||
|
||||
void ClearSelection() {
|
||||
SetSelectedEntity(INVALID_ENTITY_ID);
|
||||
}
|
||||
|
||||
bool IsSelected(EntityID id) const {
|
||||
return m_selectedEntity == id;
|
||||
}
|
||||
|
||||
XCEngine::Core::Event<EntityID> OnSelectionChanged;
|
||||
|
||||
private:
|
||||
SelectionManager() = default;
|
||||
EntityID m_selectedEntity = INVALID_ENTITY_ID;
|
||||
};
|
||||
|
||||
}
|
||||
82
editor/src/Theme.cpp
Normal file
82
editor/src/Theme.cpp
Normal file
@@ -0,0 +1,82 @@
|
||||
#include "Theme.h"
|
||||
#include <imgui.h>
|
||||
|
||||
namespace UI {
|
||||
|
||||
void ApplyUnityDarkTheme() {
|
||||
ImGuiStyle& style = ImGui::GetStyle();
|
||||
ImVec4* colors = style.Colors;
|
||||
|
||||
colors[ImGuiCol_Text] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f);
|
||||
colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f);
|
||||
colors[ImGuiCol_WindowBg] = ImVec4(0.12f, 0.12f, 0.12f, 1.00f);
|
||||
colors[ImGuiCol_ChildBg] = ImVec4(0.15f, 0.15f, 0.15f, 1.00f);
|
||||
colors[ImGuiCol_PopupBg] = ImVec4(0.18f, 0.18f, 0.18f, 0.94f);
|
||||
colors[ImGuiCol_Border] = ImVec4(0.08f, 0.08f, 0.08f, 1.00f);
|
||||
colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
|
||||
colors[ImGuiCol_FrameBg] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f);
|
||||
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.28f, 0.28f, 0.28f, 1.00f);
|
||||
colors[ImGuiCol_FrameBgActive] = ImVec4(0.35f, 0.35f, 0.35f, 1.00f);
|
||||
colors[ImGuiCol_TitleBg] = ImVec4(0.15f, 0.15f, 0.15f, 1.00f);
|
||||
colors[ImGuiCol_TitleBgActive] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f);
|
||||
colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.12f, 0.12f, 0.12f, 0.75f);
|
||||
colors[ImGuiCol_MenuBarBg] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.10f, 0.10f, 0.10f, 0.53f);
|
||||
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.30f, 0.30f, 0.30f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.40f, 0.40f, 0.40f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f);
|
||||
colors[ImGuiCol_CheckMark] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f);
|
||||
colors[ImGuiCol_SliderGrab] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f);
|
||||
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.80f, 0.80f, 0.80f, 1.00f);
|
||||
colors[ImGuiCol_Button] = ImVec4(0.25f, 0.25f, 0.25f, 1.00f);
|
||||
colors[ImGuiCol_ButtonHovered] = ImVec4(0.35f, 0.35f, 0.35f, 1.00f);
|
||||
colors[ImGuiCol_ButtonActive] = ImVec4(0.45f, 0.45f, 0.45f, 1.00f);
|
||||
colors[ImGuiCol_Header] = ImVec4(0.25f, 0.25f, 0.25f, 1.00f);
|
||||
colors[ImGuiCol_HeaderHovered] = ImVec4(0.35f, 0.35f, 0.35f, 1.00f);
|
||||
colors[ImGuiCol_HeaderActive] = ImVec4(0.45f, 0.45f, 0.45f, 1.00f);
|
||||
colors[ImGuiCol_Separator] = ImVec4(0.08f, 0.08f, 0.08f, 1.00f);
|
||||
colors[ImGuiCol_SeparatorHovered] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f);
|
||||
colors[ImGuiCol_SeparatorActive] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f);
|
||||
colors[ImGuiCol_ResizeGrip] = ImVec4(0.30f, 0.30f, 0.30f, 1.00f);
|
||||
colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f);
|
||||
colors[ImGuiCol_ResizeGripActive] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f);
|
||||
colors[ImGuiCol_Tab] = ImVec4(0.18f, 0.18f, 0.18f, 0.86f);
|
||||
colors[ImGuiCol_TabHovered] = ImVec4(0.35f, 0.35f, 0.35f, 1.00f);
|
||||
colors[ImGuiCol_TabActive] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f);
|
||||
colors[ImGuiCol_TabUnfocused] = ImVec4(0.15f, 0.15f, 0.15f, 0.97f);
|
||||
colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.22f, 0.22f, 0.22f, 1.00f);
|
||||
colors[ImGuiCol_DockingPreview] = ImVec4(0.40f, 0.40f, 0.40f, 0.70f);
|
||||
colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.12f, 0.12f, 0.12f, 1.00f);
|
||||
colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f);
|
||||
colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f);
|
||||
colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f);
|
||||
colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f);
|
||||
colors[ImGuiCol_TableHeaderBg] = ImVec4(0.19f, 0.19f, 0.20f, 1.00f);
|
||||
colors[ImGuiCol_TableBorderStrong] = ImVec4(0.31f, 0.31f, 0.35f, 1.00f);
|
||||
colors[ImGuiCol_TableBorderLight] = ImVec4(0.23f, 0.23f, 0.25f, 1.00f);
|
||||
colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
|
||||
colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.06f);
|
||||
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.40f, 0.40f, 0.40f, 0.50f);
|
||||
colors[ImGuiCol_DragDropTarget] = ImVec4(0.60f, 0.60f, 0.60f, 0.90f);
|
||||
colors[ImGuiCol_NavHighlight] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f);
|
||||
colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);
|
||||
colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f);
|
||||
colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f);
|
||||
|
||||
style.WindowRounding = 4.0f;
|
||||
style.ChildRounding = 4.0f;
|
||||
style.FrameRounding = 4.0f;
|
||||
style.GrabRounding = 4.0f;
|
||||
style.PopupRounding = 4.0f;
|
||||
style.ScrollbarRounding = 4.0f;
|
||||
style.TabRounding = 4.0f;
|
||||
style.WindowBorderSize = 1.0f;
|
||||
style.ChildBorderSize = 1.0f;
|
||||
style.FrameBorderSize = 0.0f;
|
||||
style.WindowPadding = ImVec2(8.0f, 8.0f);
|
||||
style.FramePadding = ImVec2(6.0f, 4.0f);
|
||||
style.ItemSpacing = ImVec2(8.0f, 4.0f);
|
||||
style.ItemInnerSpacing = ImVec2(6.0f, 4.0f);
|
||||
}
|
||||
|
||||
}
|
||||
7
editor/src/Theme.h
Normal file
7
editor/src/Theme.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
namespace UI {
|
||||
|
||||
void ApplyUnityDarkTheme();
|
||||
|
||||
}
|
||||
96
editor/src/main.cpp
Normal file
96
editor/src/main.cpp
Normal file
@@ -0,0 +1,96 @@
|
||||
#include "Application.h"
|
||||
#include <imgui.h>
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
|
||||
LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
||||
|
||||
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int nCmdShow) {
|
||||
AllocConsole();
|
||||
freopen("CONOUT$", "w", stdout);
|
||||
printf("Starting UI application...\n");
|
||||
|
||||
WNDCLASSEXW wc = {};
|
||||
wc.cbSize = sizeof(wc);
|
||||
wc.style = CS_CLASSDC;
|
||||
wc.lpfnWndProc = WndProc;
|
||||
wc.hInstance = GetModuleHandle(nullptr);
|
||||
wc.lpszClassName = L"XCVolumeRendererUI2";
|
||||
|
||||
if (!RegisterClassExW(&wc)) {
|
||||
printf("Failed to register window class, error: %lu\n", GetLastError());
|
||||
return 1;
|
||||
}
|
||||
printf("Window class registered.\n");
|
||||
|
||||
HWND hwnd = CreateWindowExW(
|
||||
0, wc.lpszClassName, L"XCVolumeRenderer - Unity Style Editor",
|
||||
WS_OVERLAPPEDWINDOW, 100, 100, 1280, 720,
|
||||
nullptr, nullptr, wc.hInstance, nullptr
|
||||
);
|
||||
|
||||
if (!hwnd) {
|
||||
printf("Failed to create window, error: %lu\n", GetLastError());
|
||||
return 1;
|
||||
}
|
||||
printf("Window created.\n");
|
||||
|
||||
ShowWindow(hwnd, nCmdShow);
|
||||
UpdateWindow(hwnd);
|
||||
printf("Window shown.\n");
|
||||
|
||||
printf("Initializing application...\n");
|
||||
if (!UI::Application::Get().Initialize(hwnd)) {
|
||||
printf("Failed to initialize application!\n");
|
||||
UnregisterClassW(wc.lpszClassName, wc.hInstance);
|
||||
system("pause");
|
||||
return 1;
|
||||
}
|
||||
printf("Application initialized successfully.\n");
|
||||
|
||||
MSG msg = {};
|
||||
int frameCount = 0;
|
||||
while (msg.message != WM_QUIT) {
|
||||
if (PeekMessageW(&msg, nullptr, 0U, 0U, PM_REMOVE)) {
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessageW(&msg);
|
||||
} else {
|
||||
UI::Application::Get().Render();
|
||||
frameCount++;
|
||||
if (frameCount % 100 == 0) {
|
||||
printf("Frame %d\n", frameCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
printf("Shutting down...\n");
|
||||
UI::Application::Get().Shutdown();
|
||||
UnregisterClassW(wc.lpszClassName, wc.hInstance);
|
||||
|
||||
printf("Press any key to exit...\n");
|
||||
system("pause");
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
||||
|
||||
LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
|
||||
if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam))
|
||||
return true;
|
||||
|
||||
switch (msg) {
|
||||
case WM_SIZE:
|
||||
if (wParam != SIZE_MINIMIZED) {
|
||||
UI::Application::Get().OnResize((int)LOWORD(lParam), (int)HIWORD(lParam));
|
||||
}
|
||||
return 0;
|
||||
case WM_SYSCOMMAND:
|
||||
if ((wParam & 0xfff0) == SC_KEYMENU)
|
||||
return 0;
|
||||
break;
|
||||
case WM_DESTROY:
|
||||
PostQuitMessage(0);
|
||||
return 0;
|
||||
}
|
||||
return DefWindowProcW(hWnd, msg, wParam, lParam);
|
||||
}
|
||||
70
editor/src/panels/ConsolePanel.cpp
Normal file
70
editor/src/panels/ConsolePanel.cpp
Normal file
@@ -0,0 +1,70 @@
|
||||
#include "ConsolePanel.h"
|
||||
#include "Managers/LogSystem.h"
|
||||
#include "Core/LogEntry.h"
|
||||
#include <imgui.h>
|
||||
|
||||
namespace UI {
|
||||
|
||||
ConsolePanel::ConsolePanel() : Panel("Console") {
|
||||
LogSystem::Get().AddLog(XCEngine::Debug::LogLevel::Info, "Engine initialized successfully");
|
||||
LogSystem::Get().AddLog(XCEngine::Debug::LogLevel::Info, "Loading default scene...");
|
||||
LogSystem::Get().AddLog(XCEngine::Debug::LogLevel::Warning, "Missing material on object 'Cube'");
|
||||
LogSystem::Get().AddLog(XCEngine::Debug::LogLevel::Error, "Failed to load texture: 'Assets/Textures/missing.png'");
|
||||
LogSystem::Get().AddLog(XCEngine::Debug::LogLevel::Info, "Scene loaded successfully");
|
||||
}
|
||||
|
||||
void ConsolePanel::Render() {
|
||||
ImGui::Begin(m_name.c_str(), nullptr, ImGuiWindowFlags_None);
|
||||
|
||||
if (ImGui::Button("Clear")) {
|
||||
LogSystem::Get().Clear();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Info")) {
|
||||
LogSystem::Get().AddLog(XCEngine::Debug::LogLevel::Info, "Test info message");
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Warn")) {
|
||||
LogSystem::Get().AddLog(XCEngine::Debug::LogLevel::Warning, "Test warning message");
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Error")) {
|
||||
LogSystem::Get().AddLog(XCEngine::Debug::LogLevel::Error, "Test error message");
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::BeginChild("LogScroll", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar);
|
||||
|
||||
for (const auto& log : LogSystem::Get().GetLogs()) {
|
||||
ImVec4 color;
|
||||
const char* prefix;
|
||||
|
||||
switch (log.level) {
|
||||
case XCEngine::Debug::LogLevel::Info:
|
||||
color = ImVec4(0.7f, 0.7f, 0.7f, 1.0f);
|
||||
prefix = "[Info] ";
|
||||
break;
|
||||
case XCEngine::Debug::LogLevel::Warning:
|
||||
color = ImVec4(1.0f, 0.8f, 0.0f, 1.0f);
|
||||
prefix = "[Warn] ";
|
||||
break;
|
||||
case XCEngine::Debug::LogLevel::Error:
|
||||
color = ImVec4(1.0f, 0.3f, 0.3f, 1.0f);
|
||||
prefix = "[Error]";
|
||||
break;
|
||||
}
|
||||
|
||||
ImGui::TextColored(color, "%s%s", prefix, log.message.c_str());
|
||||
}
|
||||
|
||||
if (m_scrollToBottom) {
|
||||
ImGui::SetScrollHereY(1.0f);
|
||||
m_scrollToBottom = false;
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
}
|
||||
16
editor/src/panels/ConsolePanel.h
Normal file
16
editor/src/panels/ConsolePanel.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include "Panel.h"
|
||||
|
||||
namespace UI {
|
||||
|
||||
class ConsolePanel : public Panel {
|
||||
public:
|
||||
ConsolePanel();
|
||||
void Render() override;
|
||||
|
||||
private:
|
||||
bool m_scrollToBottom = false;
|
||||
};
|
||||
|
||||
}
|
||||
31
editor/src/panels/GameViewPanel.cpp
Normal file
31
editor/src/panels/GameViewPanel.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
#include "GameViewPanel.h"
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
|
||||
namespace UI {
|
||||
|
||||
GameViewPanel::GameViewPanel() : Panel("Game") {}
|
||||
|
||||
void GameViewPanel::Render() {
|
||||
ImGui::Begin(m_name.c_str(), nullptr, ImGuiWindowFlags_None);
|
||||
|
||||
RenderGameView();
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void GameViewPanel::RenderGameView() {
|
||||
ImVec2 canvasSize = ImGui::GetContentRegionAvail();
|
||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||
ImVec2 canvasPos = ImGui::GetCursorScreenPos();
|
||||
|
||||
ImU32 bgColor = IM_COL32(20, 20, 25, 255);
|
||||
drawList->AddRectFilled(canvasPos, ImVec2(canvasPos.x + canvasSize.x, canvasPos.y + canvasSize.y), bgColor);
|
||||
|
||||
const char* text = "Game View (Press Play)";
|
||||
ImVec2 textSize = ImGui::CalcTextSize(text);
|
||||
ImVec2 textPos(canvasPos.x + (canvasSize.x - textSize.x) * 0.5f, canvasPos.y + (canvasSize.y - textSize.y) * 0.5f);
|
||||
drawList->AddText(textPos, IM_COL32(128, 128, 128, 255), text);
|
||||
}
|
||||
|
||||
}
|
||||
16
editor/src/panels/GameViewPanel.h
Normal file
16
editor/src/panels/GameViewPanel.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include "Panel.h"
|
||||
|
||||
namespace UI {
|
||||
|
||||
class GameViewPanel : public Panel {
|
||||
public:
|
||||
GameViewPanel();
|
||||
void Render() override;
|
||||
|
||||
private:
|
||||
void RenderGameView();
|
||||
};
|
||||
|
||||
}
|
||||
345
editor/src/panels/HierarchyPanel.cpp
Normal file
345
editor/src/panels/HierarchyPanel.cpp
Normal file
@@ -0,0 +1,345 @@
|
||||
#include "HierarchyPanel.h"
|
||||
#include "Managers/SceneManager.h"
|
||||
#include "Managers/SelectionManager.h"
|
||||
#include <imgui.h>
|
||||
#include <cstring>
|
||||
|
||||
namespace UI {
|
||||
|
||||
HierarchyPanel::HierarchyPanel() : Panel("Hierarchy") {
|
||||
SceneManager::Get().CreateDemoScene();
|
||||
|
||||
m_selectionHandlerId = SelectionManager::Get().OnSelectionChanged.Subscribe([this](EntityID) {
|
||||
});
|
||||
}
|
||||
|
||||
HierarchyPanel::~HierarchyPanel() {
|
||||
SelectionManager::Get().OnSelectionChanged.Unsubscribe(m_selectionHandlerId);
|
||||
}
|
||||
|
||||
void HierarchyPanel::Render() {
|
||||
ImGui::Begin(m_name.c_str(), nullptr, ImGuiWindowFlags_None);
|
||||
|
||||
RenderSearchBar();
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
HandleKeyboardShortcuts();
|
||||
|
||||
std::string filter = m_searchBuffer;
|
||||
|
||||
ImGui::BeginChild("EntityList");
|
||||
|
||||
for (EntityID id : SceneManager::Get().GetRootEntities()) {
|
||||
RenderEntity(id, filter);
|
||||
}
|
||||
|
||||
if (ImGui::IsWindowHovered() && ImGui::IsMouseDown(0) && !ImGui::IsAnyItemHovered()) {
|
||||
if (!m_renaming) {
|
||||
SelectionManager::Get().ClearSelection();
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::BeginPopupContextWindow("HierarchyContextMenu", ImGuiPopupFlags_MouseButtonRight)) {
|
||||
RenderCreateMenu(INVALID_ENTITY_ID);
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
ImGui::InvisibleButton("##DragTarget", ImVec2(-1, -1));
|
||||
if (ImGui::BeginDragDropTarget()) {
|
||||
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ENTITY_ID")) {
|
||||
EntityID sourceId = *(const EntityID*)payload->Data;
|
||||
if (sourceId != INVALID_ENTITY_ID) {
|
||||
const GameObject* sourceEntity = SceneManager::Get().GetEntity(sourceId);
|
||||
if (sourceEntity && sourceEntity->parent != INVALID_ENTITY_ID) {
|
||||
SceneManager::Get().MoveEntity(sourceId, INVALID_ENTITY_ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::EndDragDropTarget();
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void HierarchyPanel::RenderSearchBar() {
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
ImGui::InputTextWithHint("##Search", "Search...", m_searchBuffer, sizeof(m_searchBuffer));
|
||||
}
|
||||
|
||||
void HierarchyPanel::RenderEntity(EntityID id, const std::string& filter) {
|
||||
auto& sceneManager = SceneManager::Get();
|
||||
GameObject* entity = sceneManager.GetEntity(id);
|
||||
if (!entity) return;
|
||||
|
||||
if (!filter.empty() && !PassesFilter(id, filter)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui::PushID(static_cast<int>(id));
|
||||
|
||||
ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_SpanAvailWidth;
|
||||
|
||||
if (entity->children.empty()) {
|
||||
flags |= ImGuiTreeNodeFlags_Leaf;
|
||||
}
|
||||
|
||||
if (SelectionManager::Get().IsSelected(id)) {
|
||||
flags |= ImGuiTreeNodeFlags_Selected;
|
||||
}
|
||||
|
||||
if (m_renaming && m_renamingEntity == id) {
|
||||
if (m_renameJustStarted) {
|
||||
ImGui::SetKeyboardFocusHere();
|
||||
m_renameJustStarted = false;
|
||||
}
|
||||
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
if (ImGui::InputText("##Rename", m_renameBuffer, sizeof(m_renameBuffer), ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll)) {
|
||||
if (strlen(m_renameBuffer) > 0) {
|
||||
sceneManager.RenameEntity(id, m_renameBuffer);
|
||||
}
|
||||
m_renaming = false;
|
||||
m_renamingEntity = INVALID_ENTITY_ID;
|
||||
}
|
||||
|
||||
if (!ImGui::IsItemActive() && ImGui::IsMouseClicked(0)) {
|
||||
if (strlen(m_renameBuffer) > 0) {
|
||||
sceneManager.RenameEntity(id, m_renameBuffer);
|
||||
}
|
||||
m_renaming = false;
|
||||
m_renamingEntity = INVALID_ENTITY_ID;
|
||||
}
|
||||
} else {
|
||||
bool isOpen = ImGui::TreeNodeEx(entity->name.c_str(), flags);
|
||||
|
||||
if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen()) {
|
||||
SelectionManager::Get().SetSelectedEntity(id);
|
||||
}
|
||||
|
||||
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) {
|
||||
m_renaming = true;
|
||||
m_renamingEntity = id;
|
||||
strcpy_s(m_renameBuffer, entity->name.c_str());
|
||||
m_renameJustStarted = true;
|
||||
}
|
||||
|
||||
HandleDragDrop(id);
|
||||
|
||||
if (ImGui::BeginPopupContextItem("EntityContextMenu")) {
|
||||
RenderContextMenu(id);
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
if (isOpen) {
|
||||
for (EntityID childId : entity->children) {
|
||||
RenderEntity(childId, filter);
|
||||
}
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
void HierarchyPanel::RenderContextMenu(EntityID id) {
|
||||
auto& sceneManager = SceneManager::Get();
|
||||
auto& selectionManager = SelectionManager::Get();
|
||||
|
||||
if (ImGui::BeginMenu("Create")) {
|
||||
RenderCreateMenu(id);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::MenuItem("Rename", "F2")) {
|
||||
const GameObject* entity = sceneManager.GetEntity(id);
|
||||
if (entity) {
|
||||
m_renaming = true;
|
||||
m_renamingEntity = id;
|
||||
strcpy_s(m_renameBuffer, entity->name.c_str());
|
||||
m_renameJustStarted = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("Delete", "Delete")) {
|
||||
sceneManager.DeleteEntity(id);
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::MenuItem("Copy", "Ctrl+C")) {
|
||||
sceneManager.CopyEntity(id);
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("Paste", "Ctrl+V", false, sceneManager.HasClipboardData())) {
|
||||
sceneManager.PasteEntity(id);
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("Duplicate", "Ctrl+D")) {
|
||||
EntityID newId = sceneManager.DuplicateEntity(id);
|
||||
if (newId != INVALID_ENTITY_ID) {
|
||||
selectionManager.SetSelectedEntity(newId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HierarchyPanel::RenderCreateMenu(EntityID parent) {
|
||||
auto& sceneManager = SceneManager::Get();
|
||||
auto& selectionManager = SelectionManager::Get();
|
||||
|
||||
if (ImGui::MenuItem("Empty Object")) {
|
||||
EntityID newId = sceneManager.CreateEntity("GameObject", parent);
|
||||
selectionManager.SetSelectedEntity(newId);
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::MenuItem("Camera")) {
|
||||
EntityID newId = sceneManager.CreateEntity("Camera", parent);
|
||||
sceneManager.GetEntity(newId)->AddComponent<TransformComponent>();
|
||||
selectionManager.SetSelectedEntity(newId);
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("Light")) {
|
||||
EntityID newId = sceneManager.CreateEntity("Light", parent);
|
||||
selectionManager.SetSelectedEntity(newId);
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::MenuItem("Cube")) {
|
||||
EntityID newId = sceneManager.CreateEntity("Cube", parent);
|
||||
sceneManager.GetEntity(newId)->AddComponent<TransformComponent>();
|
||||
sceneManager.GetEntity(newId)->AddComponent<MeshRendererComponent>()->meshName = "Cube";
|
||||
selectionManager.SetSelectedEntity(newId);
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("Sphere")) {
|
||||
EntityID newId = sceneManager.CreateEntity("Sphere", parent);
|
||||
sceneManager.GetEntity(newId)->AddComponent<TransformComponent>();
|
||||
sceneManager.GetEntity(newId)->AddComponent<MeshRendererComponent>()->meshName = "Sphere";
|
||||
selectionManager.SetSelectedEntity(newId);
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("Plane")) {
|
||||
EntityID newId = sceneManager.CreateEntity("Plane", parent);
|
||||
sceneManager.GetEntity(newId)->AddComponent<TransformComponent>();
|
||||
sceneManager.GetEntity(newId)->AddComponent<MeshRendererComponent>()->meshName = "Plane";
|
||||
selectionManager.SetSelectedEntity(newId);
|
||||
}
|
||||
}
|
||||
|
||||
void HierarchyPanel::HandleDragDrop(EntityID id) {
|
||||
auto& sceneManager = SceneManager::Get();
|
||||
|
||||
if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) {
|
||||
m_dragSource = id;
|
||||
ImGui::SetDragDropPayload("ENTITY_ID", &id, sizeof(EntityID));
|
||||
const GameObject* entity = sceneManager.GetEntity(id);
|
||||
if (entity) {
|
||||
ImGui::Text("%s", entity->name.c_str());
|
||||
}
|
||||
ImGui::EndDragDropSource();
|
||||
}
|
||||
|
||||
if (ImGui::BeginDragDropTarget()) {
|
||||
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ENTITY_ID")) {
|
||||
EntityID sourceId = *(const EntityID*)payload->Data;
|
||||
if (sourceId != id && sourceId != INVALID_ENTITY_ID) {
|
||||
const GameObject* targetEntity = sceneManager.GetEntity(id);
|
||||
const GameObject* sourceEntity = sceneManager.GetEntity(sourceId);
|
||||
|
||||
bool isValidMove = true;
|
||||
EntityID checkParent = targetEntity ? targetEntity->parent : INVALID_ENTITY_ID;
|
||||
while (checkParent != INVALID_ENTITY_ID) {
|
||||
if (checkParent == sourceId) {
|
||||
isValidMove = false;
|
||||
break;
|
||||
}
|
||||
const GameObject* parentEntity = sceneManager.GetEntity(checkParent);
|
||||
checkParent = parentEntity ? parentEntity->parent : INVALID_ENTITY_ID;
|
||||
}
|
||||
|
||||
if (isValidMove && sourceEntity && sourceEntity->parent != id) {
|
||||
sceneManager.MoveEntity(sourceId, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::EndDragDropTarget();
|
||||
}
|
||||
}
|
||||
|
||||
void HierarchyPanel::HandleKeyboardShortcuts() {
|
||||
auto& sceneManager = SceneManager::Get();
|
||||
auto& selectionManager = SelectionManager::Get();
|
||||
|
||||
EntityID selectedId = selectionManager.GetSelectedEntity();
|
||||
|
||||
if (ImGui::IsWindowFocused()) {
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_Delete)) {
|
||||
if (selectedId != INVALID_ENTITY_ID) {
|
||||
sceneManager.DeleteEntity(selectedId);
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_F2)) {
|
||||
if (selectedId != INVALID_ENTITY_ID) {
|
||||
const GameObject* entity = sceneManager.GetEntity(selectedId);
|
||||
if (entity) {
|
||||
m_renaming = true;
|
||||
m_renamingEntity = selectedId;
|
||||
strcpy_s(m_renameBuffer, entity->name.c_str());
|
||||
m_renameJustStarted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
if (io.KeyCtrl) {
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_C)) {
|
||||
if (selectedId != INVALID_ENTITY_ID) {
|
||||
sceneManager.CopyEntity(selectedId);
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_V)) {
|
||||
if (sceneManager.HasClipboardData()) {
|
||||
sceneManager.PasteEntity(selectedId);
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_D)) {
|
||||
if (selectedId != INVALID_ENTITY_ID) {
|
||||
EntityID newId = sceneManager.DuplicateEntity(selectedId);
|
||||
if (newId != INVALID_ENTITY_ID) {
|
||||
selectionManager.SetSelectedEntity(newId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool HierarchyPanel::PassesFilter(EntityID id, const std::string& filter) {
|
||||
auto& sceneManager = SceneManager::Get();
|
||||
const GameObject* entity = sceneManager.GetEntity(id);
|
||||
if (!entity) return false;
|
||||
|
||||
if (entity->name.find(filter) != std::string::npos) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (EntityID childId : entity->children) {
|
||||
if (PassesFilter(childId, filter)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
34
editor/src/panels/HierarchyPanel.h
Normal file
34
editor/src/panels/HierarchyPanel.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "Panel.h"
|
||||
#include "Core/GameObject.h"
|
||||
|
||||
namespace UI {
|
||||
|
||||
class HierarchyPanel : public Panel {
|
||||
public:
|
||||
HierarchyPanel();
|
||||
~HierarchyPanel();
|
||||
|
||||
void Render() override;
|
||||
|
||||
private:
|
||||
void RenderSearchBar();
|
||||
void RenderEntity(EntityID id, const std::string& filter);
|
||||
void RenderContextMenu(EntityID id);
|
||||
void RenderCreateMenu(EntityID parent);
|
||||
void HandleDragDrop(EntityID id);
|
||||
void HandleKeyboardShortcuts();
|
||||
bool PassesFilter(EntityID id, const std::string& filter);
|
||||
|
||||
uint64_t m_selectionHandlerId = 0;
|
||||
|
||||
char m_searchBuffer[256] = "";
|
||||
bool m_renaming = false;
|
||||
EntityID m_renamingEntity = INVALID_ENTITY_ID;
|
||||
char m_renameBuffer[256] = "";
|
||||
bool m_renameJustStarted = false;
|
||||
EntityID m_dragSource = INVALID_ENTITY_ID;
|
||||
};
|
||||
|
||||
}
|
||||
94
editor/src/panels/InspectorPanel.cpp
Normal file
94
editor/src/panels/InspectorPanel.cpp
Normal file
@@ -0,0 +1,94 @@
|
||||
#include "InspectorPanel.h"
|
||||
#include "Managers/SceneManager.h"
|
||||
#include "Managers/SelectionManager.h"
|
||||
#include <imgui.h>
|
||||
#include <string>
|
||||
|
||||
namespace UI {
|
||||
|
||||
InspectorPanel::InspectorPanel() : Panel("Inspector") {
|
||||
m_selectionHandlerId = SelectionManager::Get().OnSelectionChanged.Subscribe([this](EntityID) {
|
||||
});
|
||||
}
|
||||
|
||||
InspectorPanel::~InspectorPanel() {
|
||||
SelectionManager::Get().OnSelectionChanged.Unsubscribe(m_selectionHandlerId);
|
||||
}
|
||||
|
||||
void InspectorPanel::Render() {
|
||||
ImGui::Begin(m_name.c_str(), nullptr, ImGuiWindowFlags_None);
|
||||
|
||||
EntityID selectedId = SelectionManager::Get().GetSelectedEntity();
|
||||
GameObject* entity = SceneManager::Get().GetEntity(selectedId);
|
||||
|
||||
if (entity) {
|
||||
RenderEntity(entity);
|
||||
} else {
|
||||
ImGui::Text("No object selected");
|
||||
ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "Select an object in Hierarchy");
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void InspectorPanel::RenderEntity(GameObject* entity) {
|
||||
ImGui::Text("%s", entity->name.c_str());
|
||||
ImGui::Separator();
|
||||
|
||||
for (auto& component : entity->components) {
|
||||
RenderComponent(component.get());
|
||||
ImGui::Separator();
|
||||
}
|
||||
}
|
||||
|
||||
void InspectorPanel::RenderComponent(Component* component) {
|
||||
if (!component) return;
|
||||
|
||||
const char* name = component->GetName().c_str();
|
||||
|
||||
std::string headerId = name + std::string("##") + std::to_string(reinterpret_cast<uintptr_t>(component));
|
||||
|
||||
if (ImGui::CollapsingHeader(headerId.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
ImGui::Indent(10.0f);
|
||||
|
||||
if (auto* transform = dynamic_cast<TransformComponent*>(component)) {
|
||||
ImGui::Text("Position");
|
||||
ImGui::SameLine(80);
|
||||
ImGui::SetNextItemWidth(180);
|
||||
ImGui::DragFloat3("##Position", transform->position, 0.1f);
|
||||
|
||||
ImGui::Text("Rotation");
|
||||
ImGui::SameLine(80);
|
||||
ImGui::SetNextItemWidth(180);
|
||||
ImGui::DragFloat3("##Rotation", transform->rotation, 1.0f);
|
||||
|
||||
ImGui::Text("Scale");
|
||||
ImGui::SameLine(80);
|
||||
ImGui::SetNextItemWidth(180);
|
||||
ImGui::DragFloat3("##Scale", transform->scale, 0.1f);
|
||||
}
|
||||
else if (auto* meshRenderer = dynamic_cast<MeshRendererComponent*>(component)) {
|
||||
char materialBuffer[256] = {};
|
||||
strncpy_s(materialBuffer, meshRenderer->materialName.c_str(), sizeof(materialBuffer) - 1);
|
||||
ImGui::Text("Material");
|
||||
ImGui::SameLine(80);
|
||||
ImGui::SetNextItemWidth(180);
|
||||
if (ImGui::InputText("##Material", materialBuffer, sizeof(materialBuffer))) {
|
||||
meshRenderer->materialName = materialBuffer;
|
||||
}
|
||||
|
||||
char meshBuffer[256] = {};
|
||||
strncpy_s(meshBuffer, meshRenderer->meshName.c_str(), sizeof(meshBuffer) - 1);
|
||||
ImGui::Text("Mesh");
|
||||
ImGui::SameLine(80);
|
||||
ImGui::SetNextItemWidth(180);
|
||||
if (ImGui::InputText("##Mesh", meshBuffer, sizeof(meshBuffer))) {
|
||||
meshRenderer->meshName = meshBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Unindent(10.0f);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
22
editor/src/panels/InspectorPanel.h
Normal file
22
editor/src/panels/InspectorPanel.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include "Panel.h"
|
||||
#include "Core/GameObject.h"
|
||||
|
||||
namespace UI {
|
||||
|
||||
class InspectorPanel : public Panel {
|
||||
public:
|
||||
InspectorPanel();
|
||||
~InspectorPanel();
|
||||
|
||||
void Render() override;
|
||||
|
||||
private:
|
||||
void RenderEntity(GameObject* entity);
|
||||
void RenderComponent(Component* component);
|
||||
|
||||
uint64_t m_selectionHandlerId = 0;
|
||||
};
|
||||
|
||||
}
|
||||
55
editor/src/panels/MenuBar.cpp
Normal file
55
editor/src/panels/MenuBar.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
#include "MenuBar.h"
|
||||
#include <imgui.h>
|
||||
|
||||
namespace UI {
|
||||
|
||||
MenuBar::MenuBar() : Panel("MenuBar") {}
|
||||
|
||||
void MenuBar::Render() {
|
||||
if (ImGui::BeginMainMenuBar()) {
|
||||
ShowFileMenu();
|
||||
ShowEditMenu();
|
||||
ShowViewMenu();
|
||||
ShowHelpMenu();
|
||||
ImGui::EndMainMenuBar();
|
||||
}
|
||||
}
|
||||
|
||||
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")) {}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("Exit", "Alt+F4")) {}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
||||
void MenuBar::ShowEditMenu() {
|
||||
if (ImGui::BeginMenu("Edit")) {
|
||||
if (ImGui::MenuItem("Undo", "Ctrl+Z")) {}
|
||||
if (ImGui::MenuItem("Redo", "Ctrl+Y")) {}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("Cut", "Ctrl+X")) {}
|
||||
if (ImGui::MenuItem("Copy", "Ctrl+C")) {}
|
||||
if (ImGui::MenuItem("Paste", "Ctrl+V")) {}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
||||
void MenuBar::ShowViewMenu() {
|
||||
if (ImGui::BeginMenu("View")) {
|
||||
if (ImGui::MenuItem("Reset Layout")) {}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
||||
void MenuBar::ShowHelpMenu() {
|
||||
if (ImGui::BeginMenu("Help")) {
|
||||
if (ImGui::MenuItem("About")) {}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
19
editor/src/panels/MenuBar.h
Normal file
19
editor/src/panels/MenuBar.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "Panel.h"
|
||||
|
||||
namespace UI {
|
||||
|
||||
class MenuBar : public Panel {
|
||||
public:
|
||||
MenuBar();
|
||||
void Render() override;
|
||||
|
||||
private:
|
||||
void ShowFileMenu();
|
||||
void ShowEditMenu();
|
||||
void ShowViewMenu();
|
||||
void ShowHelpMenu();
|
||||
};
|
||||
|
||||
}
|
||||
4
editor/src/panels/Panel.cpp
Normal file
4
editor/src/panels/Panel.cpp
Normal file
@@ -0,0 +1,4 @@
|
||||
#include "Panel.h"
|
||||
|
||||
namespace UI {
|
||||
}
|
||||
25
editor/src/panels/Panel.h
Normal file
25
editor/src/panels/Panel.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <imgui.h>
|
||||
|
||||
namespace UI {
|
||||
|
||||
class Panel {
|
||||
public:
|
||||
Panel(const std::string& name) : m_name(name), m_isOpen(true) {}
|
||||
virtual ~Panel() = default;
|
||||
|
||||
virtual void Render() = 0;
|
||||
|
||||
const std::string& GetName() const { return m_name; }
|
||||
bool IsOpen() const { return m_isOpen; }
|
||||
void SetOpen(bool open) { m_isOpen = open; }
|
||||
void Toggle() { m_isOpen = !m_isOpen; }
|
||||
|
||||
protected:
|
||||
std::string m_name;
|
||||
bool m_isOpen;
|
||||
};
|
||||
|
||||
}
|
||||
297
editor/src/panels/ProjectPanel.cpp
Normal file
297
editor/src/panels/ProjectPanel.cpp
Normal file
@@ -0,0 +1,297 @@
|
||||
#include "ProjectPanel.h"
|
||||
#include "Managers/ProjectManager.h"
|
||||
#include "Core/AssetItem.h"
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
|
||||
namespace UI {
|
||||
|
||||
const char* DRAG_DROP_TYPE = "ASSET_ITEM";
|
||||
|
||||
ProjectPanel::ProjectPanel() : Panel("Project") {
|
||||
}
|
||||
|
||||
void ProjectPanel::Initialize(const std::string& projectPath) {
|
||||
ProjectManager::Get().Initialize(projectPath);
|
||||
}
|
||||
|
||||
void ProjectPanel::Render() {
|
||||
const ImGuiPayload* payload = ImGui::GetDragDropPayload();
|
||||
if (payload && payload->IsDataType(DRAG_DROP_TYPE)) {
|
||||
m_draggingPath = (const char*)payload->Data;
|
||||
} else if (!ImGui::IsMouseDown(0)) {
|
||||
m_draggingPath.clear();
|
||||
}
|
||||
|
||||
ImGui::Begin(m_name.c_str(), nullptr, ImGuiWindowFlags_None);
|
||||
|
||||
auto& manager = ProjectManager::Get();
|
||||
|
||||
bool canGoBack = manager.CanNavigateBack();
|
||||
ImGui::BeginDisabled(!canGoBack);
|
||||
if (ImGui::Button("<")) {
|
||||
if (canGoBack) {
|
||||
manager.NavigateBack();
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0, 0, 0, 0));
|
||||
size_t pathDepth = manager.GetPathDepth();
|
||||
for (size_t i = 0; i < pathDepth; i++) {
|
||||
if (i > 0) {
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("/");
|
||||
ImGui::SameLine();
|
||||
}
|
||||
std::string name = manager.GetPathName(i);
|
||||
if (i < pathDepth - 1) {
|
||||
if (ImGui::Button(name.c_str())) {
|
||||
manager.NavigateToIndex(i);
|
||||
}
|
||||
} else {
|
||||
ImGui::Text("%s", name.c_str());
|
||||
}
|
||||
}
|
||||
ImGui::PopStyleColor(2);
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::SetCursorPosX(ImGui::GetWindowWidth() - 80.0f);
|
||||
if (ImGui::Button("Refresh")) {
|
||||
manager.RefreshCurrentFolder();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::PushItemWidth(-1);
|
||||
ImGui::InputTextWithHint("##Search", "Search...", m_searchBuffer, sizeof(m_searchBuffer));
|
||||
ImGui::PopItemWidth();
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10, 10));
|
||||
|
||||
float buttonWidth = 80.0f;
|
||||
float padding = 10.0f;
|
||||
float panelWidth = ImGui::GetContentRegionAvail().x;
|
||||
int columns = (int)(panelWidth / (buttonWidth + padding));
|
||||
if (columns < 1) columns = 1;
|
||||
|
||||
auto& items = manager.GetCurrentItems();
|
||||
std::string searchStr = m_searchBuffer;
|
||||
int itemIndex = 0;
|
||||
|
||||
for (int i = 0; i < (int)items.size(); i++) {
|
||||
if (!searchStr.empty()) {
|
||||
if (items[i]->name.find(searchStr) == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (itemIndex > 0 && itemIndex % columns != 0) {
|
||||
ImGui::SameLine();
|
||||
}
|
||||
RenderAssetItem(items[i], itemIndex);
|
||||
itemIndex++;
|
||||
}
|
||||
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered()) {
|
||||
manager.SetSelectedIndex(-1);
|
||||
}
|
||||
|
||||
if (ImGui::BeginPopup("ItemContextMenu")) {
|
||||
if (m_contextMenuIndex >= 0 && m_contextMenuIndex < (int)items.size()) {
|
||||
auto& item = items[m_contextMenuIndex];
|
||||
if (item->isFolder) {
|
||||
if (ImGui::MenuItem("Open")) {
|
||||
manager.NavigateToFolder(item);
|
||||
}
|
||||
ImGui::Separator();
|
||||
}
|
||||
if (ImGui::MenuItem("Delete")) {
|
||||
manager.DeleteItem(m_contextMenuIndex);
|
||||
m_contextMenuIndex = -1;
|
||||
}
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(1) && !ImGui::IsAnyItemHovered()) {
|
||||
ImGui::OpenPopup("EmptyContextMenu");
|
||||
}
|
||||
|
||||
if (ImGui::BeginPopup("EmptyContextMenu")) {
|
||||
if (ImGui::MenuItem("Create Folder")) {
|
||||
m_showCreateFolderPopup = true;
|
||||
strcpy_s(m_newFolderName, "NewFolder");
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("Refresh")) {
|
||||
manager.RefreshCurrentFolder();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
|
||||
if (m_showCreateFolderPopup) {
|
||||
ImGui::OpenPopup("Create Folder");
|
||||
m_showCreateFolderPopup = false;
|
||||
}
|
||||
|
||||
if (ImGui::BeginPopupModal("Create Folder", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
ImGui::InputText("Name", m_newFolderName, sizeof(m_newFolderName));
|
||||
ImGui::Separator();
|
||||
if (ImGui::Button("Create", ImVec2(80, 0))) {
|
||||
CreateNewFolder(m_newFolderName);
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Cancel", ImVec2(80, 0))) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectPanel::RenderAssetItem(const AssetItemPtr& item, int index) {
|
||||
auto& manager = ProjectManager::Get();
|
||||
bool isSelected = (manager.GetSelectedIndex() == index);
|
||||
|
||||
ImGui::PushID(index);
|
||||
|
||||
if (isSelected) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.40f, 0.40f, 0.40f, 0.50f));
|
||||
} else {
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.30f, 0.30f, 0.30f, 0.40f));
|
||||
}
|
||||
|
||||
ImVec2 buttonSize(80.0f, 90.0f);
|
||||
|
||||
if (ImGui::Button("##AssetBtn", buttonSize)) {
|
||||
manager.SetSelectedIndex(index);
|
||||
}
|
||||
|
||||
bool doubleClicked = false;
|
||||
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) {
|
||||
doubleClicked = true;
|
||||
}
|
||||
|
||||
bool openContextMenu = false;
|
||||
if (ImGui::IsItemClicked(1)) {
|
||||
manager.SetSelectedIndex(index);
|
||||
m_contextMenuIndex = index;
|
||||
openContextMenu = true;
|
||||
}
|
||||
|
||||
if (isSelected) {
|
||||
ImGui::PopStyleColor();
|
||||
} else {
|
||||
ImGui::PopStyleColor(2);
|
||||
}
|
||||
|
||||
ImVec2 min = ImGui::GetItemRectMin();
|
||||
ImVec2 max = ImVec2(min.x + buttonSize.x, min.y + buttonSize.y);
|
||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||
|
||||
if (!m_draggingPath.empty() && item->fullPath == m_draggingPath) {
|
||||
drawList->AddRectFilled(min, max, IM_COL32(0, 0, 0, 60), 0.0f);
|
||||
}
|
||||
|
||||
ImU32 iconColor;
|
||||
if (item->isFolder) {
|
||||
iconColor = IM_COL32(200, 180, 100, 255);
|
||||
} else if (item->type == "Texture") {
|
||||
iconColor = IM_COL32(150, 200, 150, 255);
|
||||
} else if (item->type == "Model") {
|
||||
iconColor = IM_COL32(150, 150, 200, 255);
|
||||
} else if (item->type == "Script") {
|
||||
iconColor = IM_COL32(200, 150, 150, 255);
|
||||
} else if (item->type == "Scene") {
|
||||
iconColor = IM_COL32(200, 200, 150, 255);
|
||||
} else {
|
||||
iconColor = IM_COL32(100, 150, 200, 255);
|
||||
}
|
||||
|
||||
float iconSize = 40.0f;
|
||||
ImVec2 iconMin(min.x + (80.0f - iconSize) * 0.5f, min.y + 10.0f);
|
||||
ImVec2 iconMax(iconMin.x + iconSize, iconMin.y + iconSize);
|
||||
drawList->AddRectFilled(iconMin, iconMax, iconColor, 4.0f);
|
||||
|
||||
ImVec4 textColor = isSelected ? ImVec4(1.0f, 1.0f, 1.0f, 1.0f) : ImVec4(0.8f, 0.8f, 0.8f, 1.0f);
|
||||
ImVec2 textSize = ImGui::CalcTextSize(item->name.c_str());
|
||||
float textOffset = std::max(0.0f, (80.0f - textSize.x) * 0.5f);
|
||||
|
||||
ImGui::PushClipRect(min, ImVec2(min.x + 80.0f, min.y + 90.0f), true);
|
||||
drawList->AddText(ImVec2(min.x + textOffset, min.y + 60.0f), ImGui::GetColorU32(textColor), item->name.c_str());
|
||||
ImGui::PopClipRect();
|
||||
|
||||
if (item->isFolder) {
|
||||
if (ImGui::BeginDragDropTarget()) {
|
||||
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(DRAG_DROP_TYPE)) {
|
||||
const char* draggedPath = (const char*)payload->Data;
|
||||
std::string sourcePath(draggedPath);
|
||||
manager.MoveItem(sourcePath, item->fullPath);
|
||||
}
|
||||
ImGui::EndDragDropTarget();
|
||||
}
|
||||
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) {
|
||||
ImDrawList* hoverDrawList = ImGui::GetWindowDrawList();
|
||||
hoverDrawList->AddRect(min, ImVec2(min.x + buttonSize.x, min.y + buttonSize.y), IM_COL32(255, 255, 255, 80), 4.0f);
|
||||
}
|
||||
}
|
||||
|
||||
if (!item->fullPath.empty()) {
|
||||
if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) {
|
||||
ImGui::SetDragDropPayload(DRAG_DROP_TYPE, item->fullPath.c_str(), item->fullPath.length() + 1);
|
||||
|
||||
ImU32 iconColor;
|
||||
if (item->isFolder) {
|
||||
iconColor = IM_COL32(200, 180, 100, 100);
|
||||
} else if (item->type == "Texture") {
|
||||
iconColor = IM_COL32(150, 200, 150, 100);
|
||||
} else if (item->type == "Model") {
|
||||
iconColor = IM_COL32(150, 150, 200, 100);
|
||||
} else if (item->type == "Script") {
|
||||
iconColor = IM_COL32(200, 150, 150, 100);
|
||||
} else if (item->type == "Scene") {
|
||||
iconColor = IM_COL32(200, 200, 150, 100);
|
||||
} else {
|
||||
iconColor = IM_COL32(100, 150, 200, 100);
|
||||
}
|
||||
|
||||
ImVec2 previewMin = ImGui::GetMousePos();
|
||||
ImVec2 previewMax = ImVec2(previewMin.x + 40, previewMin.y + 40);
|
||||
ImGui::GetForegroundDrawList()->AddRectFilled(previewMin, previewMax, iconColor, 4.0f);
|
||||
|
||||
ImGui::EndDragDropSource();
|
||||
}
|
||||
}
|
||||
|
||||
if (doubleClicked && item->isFolder) {
|
||||
manager.NavigateToFolder(item);
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
|
||||
if (openContextMenu) {
|
||||
ImGui::OpenPopup("ItemContextMenu");
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectPanel::CreateNewFolder(const std::string& name) {
|
||||
auto& manager = ProjectManager::Get();
|
||||
manager.CreateFolder(name);
|
||||
}
|
||||
|
||||
bool ProjectPanel::HandleDrop(const AssetItemPtr& targetFolder) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
26
editor/src/panels/ProjectPanel.h
Normal file
26
editor/src/panels/ProjectPanel.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "Panel.h"
|
||||
#include "Core/AssetItem.h"
|
||||
|
||||
namespace UI {
|
||||
|
||||
class ProjectPanel : public Panel {
|
||||
public:
|
||||
ProjectPanel();
|
||||
void Render() override;
|
||||
void Initialize(const std::string& projectPath);
|
||||
|
||||
private:
|
||||
void RenderAssetItem(const AssetItemPtr& item, int index);
|
||||
void CreateNewFolder(const std::string& name);
|
||||
bool HandleDrop(const AssetItemPtr& targetFolder);
|
||||
|
||||
char m_searchBuffer[256] = "";
|
||||
bool m_showCreateFolderPopup = false;
|
||||
char m_newFolderName[256] = "NewFolder";
|
||||
int m_contextMenuIndex = -1;
|
||||
std::string m_draggingPath;
|
||||
};
|
||||
|
||||
}
|
||||
54
editor/src/panels/SceneViewPanel.cpp
Normal file
54
editor/src/panels/SceneViewPanel.cpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#include "SceneViewPanel.h"
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
|
||||
namespace UI {
|
||||
|
||||
SceneViewPanel::SceneViewPanel() : Panel("Scene") {}
|
||||
|
||||
void SceneViewPanel::Render() {
|
||||
ImGui::Begin(m_name.c_str(), nullptr, ImGuiWindowFlags_None);
|
||||
|
||||
ImVec2 canvasSize = ImGui::GetContentRegionAvail();
|
||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||
ImVec2 canvasPos = ImGui::GetCursorScreenPos();
|
||||
|
||||
ImU32 bgColor = IM_COL32(30, 30, 30, 255);
|
||||
drawList->AddRectFilled(canvasPos, ImVec2(canvasPos.x + canvasSize.x, canvasPos.y + canvasSize.y), bgColor);
|
||||
|
||||
RenderGrid();
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void SceneViewPanel::RenderGrid() {
|
||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||
ImVec2 canvasPos = ImGui::GetCursorScreenPos();
|
||||
ImVec2 canvasSize = ImGui::GetContentRegionAvail();
|
||||
|
||||
float gridSize = 50.0f;
|
||||
ImU32 gridColor = IM_COL32(50, 50, 50, 255);
|
||||
|
||||
for (float x = fmodf(0, gridSize); x < canvasSize.x; x += gridSize) {
|
||||
drawList->AddLine(
|
||||
ImVec2(canvasPos.x + x, canvasPos.y),
|
||||
ImVec2(canvasPos.x + x, canvasPos.y + canvasSize.y),
|
||||
gridColor
|
||||
);
|
||||
}
|
||||
|
||||
for (float y = fmodf(0, gridSize); y < canvasSize.y; y += gridSize) {
|
||||
drawList->AddLine(
|
||||
ImVec2(canvasPos.x, canvasPos.y + y),
|
||||
ImVec2(canvasPos.x + canvasSize.x, canvasPos.y + y),
|
||||
gridColor
|
||||
);
|
||||
}
|
||||
|
||||
const char* label = "Scene View";
|
||||
ImVec2 labelSize = ImGui::CalcTextSize(label);
|
||||
ImVec2 labelPos(canvasPos.x + 10, canvasPos.y + 10);
|
||||
drawList->AddText(labelPos, IM_COL32(100, 100, 100, 255), label);
|
||||
}
|
||||
|
||||
}
|
||||
16
editor/src/panels/SceneViewPanel.h
Normal file
16
editor/src/panels/SceneViewPanel.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include "Panel.h"
|
||||
|
||||
namespace UI {
|
||||
|
||||
class SceneViewPanel : public Panel {
|
||||
public:
|
||||
SceneViewPanel();
|
||||
void Render() override;
|
||||
|
||||
private:
|
||||
void RenderGrid();
|
||||
};
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user