添加D3D12 ImGui编辑器UI框架

This commit is contained in:
2026-03-12 15:39:40 +08:00
parent e98093da94
commit 44880f03c0
20 changed files with 1241 additions and 0 deletions

62
ui/CMakeLists.txt Normal file
View File

@@ -0,0 +1,62 @@
cmake_minimum_required(VERSION 3.15)
project(XCVolumeRendererUI2 VERSION 1.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_definitions(-DUNICODE -D_UNICODE)
add_definitions(-DIMGUI_ENABLE_DOCKING)
include(FetchContent)
FetchContent_Declare(
imgui
GIT_REPOSITORY https://github.com/ocornut/imgui.git
GIT_TAG docking
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(imgui)
set(IMGUI_SOURCES
${imgui_SOURCE_DIR}/imgui.cpp
${imgui_SOURCE_DIR}/imgui_demo.cpp
${imgui_SOURCE_DIR}/imgui_draw.cpp
${imgui_SOURCE_DIR}/imgui_tables.cpp
${imgui_SOURCE_DIR}/imgui_widgets.cpp
${imgui_SOURCE_DIR}/backends/imgui_impl_win32.cpp
${imgui_SOURCE_DIR}/backends/imgui_impl_dx12.cpp
)
add_executable(${PROJECT_NAME} WIN32
src/main.cpp
src/Application.cpp
src/Theme.cpp
src/panels/Panel.cpp
src/panels/MenuBar.cpp
src/panels/HierarchyPanel.cpp
src/panels/SceneViewPanel.cpp
src/panels/InspectorPanel.cpp
src/panels/ConsolePanel.cpp
src/panels/ProjectPanel.cpp
${IMGUI_SOURCES}
)
target_include_directories(${PROJECT_NAME} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
${imgui_SOURCE_DIR}
${imgui_SOURCE_DIR}/backends
)
target_compile_definitions(${PROJECT_NAME} PRIVATE UNICODE _UNICODE)
target_compile_options(${PROJECT_NAME} PRIVATE /utf-8 /MT)
target_link_libraries(${PROJECT_NAME} PRIVATE
d3d12.lib
dxgi.lib
d3dcompiler.lib
)
set_target_properties(${PROJECT_NAME} PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin"
)

304
ui/src/Application.cpp Normal file
View File

@@ -0,0 +1,304 @@
#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;
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_inspectorPanel = std::make_unique<InspectorPanel>();
m_consolePanel = std::make_unique<ConsolePanel>();
m_projectPanel = std::make_unique<ProjectPanel>();
m_inspectorPanel->SetHierarchyPanel(m_hierarchyPanel.get());
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_None;
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("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_inspectorPanel->Render();
ImGui::Begin("BottomPanel", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse);
if (ImGui::Button("Console", ImVec2(80, 0))) {
m_currentBottomPanel = BottomPanel::Console;
}
ImGui::SameLine();
if (ImGui::Button("Project", ImVec2(80, 0))) {
m_currentBottomPanel = BottomPanel::Project;
}
ImGui::Separator();
ImGui::BeginChild("BottomContent");
if (m_currentBottomPanel == BottomPanel::Console) {
m_consolePanel->Render();
} else {
m_projectPanel->Render();
}
ImGui::EndChild();
ImGui::End();
}
}

67
ui/src/Application.h Normal file
View File

@@ -0,0 +1,67 @@
#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/InspectorPanel.h"
#include "panels/ConsolePanel.h"
#include "panels/ProjectPanel.h"
namespace UI {
enum class BottomPanel { Console, Project };
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<InspectorPanel> m_inspectorPanel;
std::unique_ptr<ConsolePanel> m_consolePanel;
std::unique_ptr<ProjectPanel> m_projectPanel;
BottomPanel m_currentBottomPanel = BottomPanel::Console;
};
}

82
ui/src/Theme.cpp Normal file
View 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.10f, 0.10f, 0.10f, 1.00f);
colors[ImGuiCol_TitleBgActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.10f, 0.10f, 0.10f, 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.26f, 0.59f, 0.98f, 1.00f);
colors[ImGuiCol_SliderGrab] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.36f, 0.69f, 1.00f, 1.00f);
colors[ImGuiCol_Button] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f);
colors[ImGuiCol_ButtonHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.60f);
colors[ImGuiCol_ButtonActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.80f);
colors[ImGuiCol_Header] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f);
colors[ImGuiCol_HeaderHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f);
colors[ImGuiCol_HeaderActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.60f);
colors[ImGuiCol_Separator] = ImVec4(0.08f, 0.08f, 0.08f, 1.00f);
colors[ImGuiCol_SeparatorHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.78f);
colors[ImGuiCol_SeparatorActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
colors[ImGuiCol_ResizeGrip] = ImVec4(0.26f, 0.59f, 0.98f, 0.20f);
colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.78f);
colors[ImGuiCol_ResizeGripActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
colors[ImGuiCol_Tab] = ImVec4(0.18f, 0.18f, 0.18f, 0.86f);
colors[ImGuiCol_TabHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.80f);
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.18f, 0.18f, 0.18f, 1.00f);
colors[ImGuiCol_DockingPreview] = ImVec4(0.26f, 0.59f, 0.98f, 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.26f, 0.59f, 0.98f, 0.35f);
colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);
colors[ImGuiCol_NavHighlight] = ImVec4(0.26f, 0.59f, 0.98f, 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
ui/src/Theme.h Normal file
View File

@@ -0,0 +1,7 @@
#pragma once
namespace UI {
void ApplyUnityDarkTheme();
}

96
ui/src/main.cpp Normal file
View 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);
}

View File

@@ -0,0 +1,77 @@
#include "ConsolePanel.h"
#include <imgui.h>
namespace UI {
ConsolePanel::ConsolePanel() : Panel("Console") {
m_logs.push_back({LogEntry::Level::Info, "Engine initialized successfully"});
m_logs.push_back({LogEntry::Level::Info, "Loading default scene..."});
m_logs.push_back({LogEntry::Level::Warning, "Missing material on object 'Cube'"});
m_logs.push_back({LogEntry::Level::Error, "Failed to load texture: 'Assets/Textures/missing.png'"});
m_logs.push_back({LogEntry::Level::Info, "Scene loaded successfully"});
}
void ConsolePanel::Render() {
ImGui::Begin(m_name.c_str(), &m_isOpen, ImGuiWindowFlags_None);
if (ImGui::Button("Clear")) {
Clear();
}
ImGui::SameLine();
if (ImGui::Button("Info")) {
AddLog(LogEntry::Level::Info, "Test info message");
}
ImGui::SameLine();
if (ImGui::Button("Warn")) {
AddLog(LogEntry::Level::Warning, "Test warning message");
}
ImGui::SameLine();
if (ImGui::Button("Error")) {
AddLog(LogEntry::Level::Error, "Test error message");
}
ImGui::Separator();
ImGui::BeginChild("LogScroll", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar);
for (const auto& log : m_logs) {
ImVec4 color;
const char* prefix;
switch (log.level) {
case LogEntry::Level::Info:
color = ImVec4(0.7f, 0.7f, 0.7f, 1.0f);
prefix = "[Info] ";
break;
case LogEntry::Level::Warning:
color = ImVec4(1.0f, 0.8f, 0.0f, 1.0f);
prefix = "[Warn] ";
break;
case LogEntry::Level::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();
}
void ConsolePanel::AddLog(LogEntry::Level level, const std::string& message) {
m_logs.push_back({level, message});
m_scrollToBottom = true;
}
void ConsolePanel::Clear() {
m_logs.clear();
}
}

View File

@@ -0,0 +1,28 @@
#pragma once
#include "Panel.h"
#include <vector>
#include <string>
namespace UI {
struct LogEntry {
enum class Level { Info, Warning, Error };
Level level;
std::string message;
};
class ConsolePanel : public Panel {
public:
ConsolePanel();
void Render() override;
void AddLog(LogEntry::Level level, const std::string& message);
private:
void Clear();
std::vector<LogEntry> m_logs;
bool m_scrollToBottom = false;
};
}

View File

@@ -0,0 +1,75 @@
#include "HierarchyPanel.h"
#include <imgui.h>
namespace UI {
HierarchyPanel::HierarchyPanel() : Panel("Hierarchy") {
InitDemoData();
}
void HierarchyPanel::InitDemoData() {
GameObject mainCamera;
mainCamera.name = "Main Camera";
GameObject directionalLight;
directionalLight.name = "Directional Light";
GameObject cube;
cube.name = "Cube";
GameObject sphere;
sphere.name = "Sphere";
GameObject player;
player.name = "Player";
GameObject weapon;
weapon.name = "Weapon";
player.children.push_back(weapon);
m_rootObjects.push_back(mainCamera);
m_rootObjects.push_back(directionalLight);
m_rootObjects.push_back(cube);
m_rootObjects.push_back(sphere);
m_rootObjects.push_back(player);
}
void HierarchyPanel::Render() {
ImGui::Begin(m_name.c_str(), &m_isOpen, ImGuiWindowFlags_None);
for (auto& obj : m_rootObjects) {
RenderGameObject(obj);
}
ImGui::End();
}
void HierarchyPanel::RenderGameObject(GameObject& obj) {
ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_SpanAvailWidth;
if (obj.children.empty()) {
flags |= ImGuiTreeNodeFlags_Leaf;
}
if (obj.selected) {
flags |= ImGuiTreeNodeFlags_Selected;
}
bool isOpen = ImGui::TreeNodeEx(obj.name.c_str(), flags);
if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen()) {
if (m_selectedObject) {
m_selectedObject->selected = false;
}
obj.selected = true;
m_selectedObject = &obj;
}
if (isOpen) {
for (auto& child : obj.children) {
RenderGameObject(child);
}
ImGui::TreePop();
}
}
}

View File

@@ -0,0 +1,30 @@
#pragma once
#include "Panel.h"
#include <vector>
#include <string>
namespace UI {
struct GameObject {
std::string name;
std::vector<GameObject> children;
bool isOpen = false;
bool selected = false;
};
class HierarchyPanel : public Panel {
public:
HierarchyPanel();
void Render() override;
GameObject* GetSelectedObject() { return m_selectedObject; }
private:
void RenderGameObject(GameObject& obj);
void InitDemoData();
std::vector<GameObject> m_rootObjects;
GameObject* m_selectedObject = nullptr;
};
}

View File

@@ -0,0 +1,74 @@
#include "InspectorPanel.h"
#include <imgui.h>
namespace UI {
InspectorPanel::InspectorPanel() : Panel("Inspector") {}
void InspectorPanel::Render() {
ImGui::Begin(m_name.c_str(), &m_isOpen, ImGuiWindowFlags_None);
GameObject* selected = nullptr;
if (m_hierarchyPanel) {
selected = m_hierarchyPanel->GetSelectedObject();
}
if (selected) {
ImGui::Text("%s", selected->name.c_str());
ImGui::Separator();
RenderTransformSection();
ImGui::Separator();
RenderMeshRendererSection();
} 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::RenderTransformSection() {
if (ImGui::CollapsingHeader("Transform", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Indent(10.0f);
ImGui::Text("Position");
ImGui::SameLine(80);
ImGui::SetNextItemWidth(180);
ImGui::DragFloat3("##Position", m_position, 0.1f);
ImGui::Text("Rotation");
ImGui::SameLine(80);
ImGui::SetNextItemWidth(180);
ImGui::DragFloat3("##Rotation", m_rotation, 1.0f);
ImGui::Text("Scale");
ImGui::SameLine(80);
ImGui::SetNextItemWidth(180);
ImGui::DragFloat3("##Scale", m_scale, 0.1f);
ImGui::Unindent(10.0f);
}
}
void InspectorPanel::RenderMeshRendererSection() {
if (ImGui::CollapsingHeader("Mesh Renderer", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Indent(10.0f);
ImGui::Text("Material");
ImGui::SameLine(80);
ImGui::SetNextItemWidth(180);
static char materialName[64] = "Default-Material";
ImGui::InputText("##Material", materialName, sizeof(materialName));
ImGui::Text("Mesh");
ImGui::SameLine(80);
ImGui::SetNextItemWidth(180);
static char meshName[64] = "Cube Mesh";
ImGui::InputText("##Mesh", meshName, sizeof(meshName));
ImGui::Unindent(10.0f);
}
}
}

View File

@@ -0,0 +1,25 @@
#pragma once
#include "Panel.h"
#include "HierarchyPanel.h"
namespace UI {
class InspectorPanel : public Panel {
public:
InspectorPanel();
void Render() override;
void SetHierarchyPanel(HierarchyPanel* panel) { m_hierarchyPanel = panel; }
private:
void RenderTransformSection();
void RenderMeshRendererSection();
HierarchyPanel* m_hierarchyPanel = nullptr;
float m_position[3] = {0.0f, 1.0f, -10.0f};
float m_rotation[3] = {0.0f, 0.0f, 0.0f};
float m_scale[3] = {1.0f, 1.0f, 1.0f};
};
}

55
ui/src/panels/MenuBar.cpp Normal file
View 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
ui/src/panels/MenuBar.h Normal file
View 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
ui/src/panels/Panel.cpp Normal file
View File

@@ -0,0 +1,4 @@
#include "Panel.h"
namespace UI {
}

25
ui/src/panels/Panel.h Normal file
View 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;
};
}

View File

@@ -0,0 +1,88 @@
#include "ProjectPanel.h"
#include <imgui.h>
#include <imgui_internal.h>
namespace UI {
ProjectPanel::ProjectPanel() : Panel("Project") {
m_folders = {"Assets", "Scenes", "Scripts", "Materials", "Prefabs"};
m_items = {
{"Cube", "Prefab", false},
{"Sphere", "Prefab", false},
{"Player", "Prefab", false},
{"MainScript", "Script", false},
{"DefaultMat", "Material", false},
{"Scene1", "Scene", false},
{"Textures", "Folder", true},
{"Models", "Folder", true},
};
}
void ProjectPanel::Render() {
ImGui::Begin(m_name.c_str(), &m_isOpen, ImGuiWindowFlags_None);
ImGui::Text("Assets/");
ImGui::SameLine();
ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "> Project");
ImGui::Separator();
ImGui::BeginChild("AssetList", ImVec2(0, 0), false);
float buttonWidth = 80.0f;
float buttonHeight = 90.0f;
float padding = 10.0f;
float panelWidth = ImGui::GetContentRegionAvail().x;
int columns = (int)(panelWidth / (buttonWidth + padding));
if (columns < 1) columns = 1;
for (int i = 0; i < m_items.size(); i++) {
if (i > 0 && i % columns != 0) {
ImGui::SameLine();
}
RenderAssetItem(m_items[i], i);
}
ImGui::EndChild();
ImGui::End();
}
void ProjectPanel::RenderAssetItem(const AssetItem& item, int index) {
ImGui::PushID(index);
bool isSelected = (m_selectedIndex == index);
if (isSelected) {
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.26f, 0.59f, 0.98f, 0.40f));
}
ImVec2 buttonSize(80.0f, 90.0f);
if (ImGui::Button("##Asset", buttonSize)) {
m_selectedIndex = index;
}
if (isSelected) {
ImGui::PopStyleColor();
}
ImVec2 min = ImGui::GetItemRectMin();
ImVec2 max = ImGui::GetItemRectMax();
ImDrawList* drawList = ImGui::GetWindowDrawList();
ImU32 iconColor = item.isFolder ? IM_COL32(200, 180, 100, 255) : 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);
ImVec2 textPos(min.x + 5.0f, min.y + 60.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 = (80.0f - textSize.x) * 0.5f;
drawList->AddText(ImVec2(min.x + textOffset, textPos.y), ImGui::GetColorU32(textColor), item.name.c_str());
ImGui::PopID();
}
}

View File

@@ -0,0 +1,28 @@
#pragma once
#include "Panel.h"
#include <vector>
#include <string>
namespace UI {
struct AssetItem {
std::string name;
std::string type;
bool isFolder;
};
class ProjectPanel : public Panel {
public:
ProjectPanel();
void Render() override;
private:
void RenderAssetItem(const AssetItem& item, int index);
std::vector<std::string> m_folders;
std::vector<AssetItem> m_items;
int m_selectedIndex = -1;
};
}

View File

@@ -0,0 +1,78 @@
#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(), &m_isOpen, ImGuiWindowFlags_None);
if (ImGui::BeginTabBar("SceneGameTabs")) {
if (ImGui::BeginTabItem("Scene")) {
m_isSceneView = true;
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::EndTabItem();
}
if (ImGui::BeginTabItem("Game")) {
m_isSceneView = false;
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);
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
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);
}
}

View File

@@ -0,0 +1,17 @@
#pragma once
#include "Panel.h"
namespace UI {
class SceneViewPanel : public Panel {
public:
SceneViewPanel();
void Render() override;
private:
bool m_isSceneView = true;
void RenderGrid();
};
}