diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt new file mode 100644 index 00000000..3b1f6ec1 --- /dev/null +++ b/ui/CMakeLists.txt @@ -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" +) \ No newline at end of file diff --git a/ui/src/Application.cpp b/ui/src/Application.cpp new file mode 100644 index 00000000..3f8d46fc --- /dev/null +++ b/ui/src/Application.cpp @@ -0,0 +1,304 @@ +#include "Application.h" +#include +#include +#include +#include + +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(); + m_hierarchyPanel = std::make_unique(); + m_sceneViewPanel = std::make_unique(); + m_inspectorPanel = std::make_unique(); + m_consolePanel = std::make_unique(); + m_projectPanel = std::make_unique(); + + 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(); +} + +} \ No newline at end of file diff --git a/ui/src/Application.h b/ui/src/Application.h new file mode 100644 index 00000000..f420a66a --- /dev/null +++ b/ui/src/Application.h @@ -0,0 +1,67 @@ +#pragma once + +#include +#include +#include +#include + +#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 m_menuBar; + std::unique_ptr m_hierarchyPanel; + std::unique_ptr m_sceneViewPanel; + std::unique_ptr m_inspectorPanel; + std::unique_ptr m_consolePanel; + std::unique_ptr m_projectPanel; + + BottomPanel m_currentBottomPanel = BottomPanel::Console; +}; + +} \ No newline at end of file diff --git a/ui/src/Theme.cpp b/ui/src/Theme.cpp new file mode 100644 index 00000000..149ef2b1 --- /dev/null +++ b/ui/src/Theme.cpp @@ -0,0 +1,82 @@ +#include "Theme.h" +#include + +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); +} + +} \ No newline at end of file diff --git a/ui/src/Theme.h b/ui/src/Theme.h new file mode 100644 index 00000000..6915ee9c --- /dev/null +++ b/ui/src/Theme.h @@ -0,0 +1,7 @@ +#pragma once + +namespace UI { + +void ApplyUnityDarkTheme(); + +} \ No newline at end of file diff --git a/ui/src/main.cpp b/ui/src/main.cpp new file mode 100644 index 00000000..253643a2 --- /dev/null +++ b/ui/src/main.cpp @@ -0,0 +1,96 @@ +#include "Application.h" +#include +#include +#include + +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); +} \ No newline at end of file diff --git a/ui/src/panels/ConsolePanel.cpp b/ui/src/panels/ConsolePanel.cpp new file mode 100644 index 00000000..12077229 --- /dev/null +++ b/ui/src/panels/ConsolePanel.cpp @@ -0,0 +1,77 @@ +#include "ConsolePanel.h" +#include + +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(); +} + +} \ No newline at end of file diff --git a/ui/src/panels/ConsolePanel.h b/ui/src/panels/ConsolePanel.h new file mode 100644 index 00000000..a1e0ded3 --- /dev/null +++ b/ui/src/panels/ConsolePanel.h @@ -0,0 +1,28 @@ +#pragma once + +#include "Panel.h" +#include +#include + +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 m_logs; + bool m_scrollToBottom = false; +}; + +} \ No newline at end of file diff --git a/ui/src/panels/HierarchyPanel.cpp b/ui/src/panels/HierarchyPanel.cpp new file mode 100644 index 00000000..113b4d78 --- /dev/null +++ b/ui/src/panels/HierarchyPanel.cpp @@ -0,0 +1,75 @@ +#include "HierarchyPanel.h" +#include + +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(); + } +} + +} \ No newline at end of file diff --git a/ui/src/panels/HierarchyPanel.h b/ui/src/panels/HierarchyPanel.h new file mode 100644 index 00000000..40df5bd8 --- /dev/null +++ b/ui/src/panels/HierarchyPanel.h @@ -0,0 +1,30 @@ +#pragma once + +#include "Panel.h" +#include +#include + +namespace UI { + +struct GameObject { + std::string name; + std::vector 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 m_rootObjects; + GameObject* m_selectedObject = nullptr; +}; + +} \ No newline at end of file diff --git a/ui/src/panels/InspectorPanel.cpp b/ui/src/panels/InspectorPanel.cpp new file mode 100644 index 00000000..3fb2ed1b --- /dev/null +++ b/ui/src/panels/InspectorPanel.cpp @@ -0,0 +1,74 @@ +#include "InspectorPanel.h" +#include + +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); + } +} + +} \ No newline at end of file diff --git a/ui/src/panels/InspectorPanel.h b/ui/src/panels/InspectorPanel.h new file mode 100644 index 00000000..2c3a4fd5 --- /dev/null +++ b/ui/src/panels/InspectorPanel.h @@ -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}; +}; + +} \ No newline at end of file diff --git a/ui/src/panels/MenuBar.cpp b/ui/src/panels/MenuBar.cpp new file mode 100644 index 00000000..9376a06f --- /dev/null +++ b/ui/src/panels/MenuBar.cpp @@ -0,0 +1,55 @@ +#include "MenuBar.h" +#include + +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(); + } +} + +} \ No newline at end of file diff --git a/ui/src/panels/MenuBar.h b/ui/src/panels/MenuBar.h new file mode 100644 index 00000000..b4481422 --- /dev/null +++ b/ui/src/panels/MenuBar.h @@ -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(); +}; + +} \ No newline at end of file diff --git a/ui/src/panels/Panel.cpp b/ui/src/panels/Panel.cpp new file mode 100644 index 00000000..bd3d0c3b --- /dev/null +++ b/ui/src/panels/Panel.cpp @@ -0,0 +1,4 @@ +#include "Panel.h" + +namespace UI { +} \ No newline at end of file diff --git a/ui/src/panels/Panel.h b/ui/src/panels/Panel.h new file mode 100644 index 00000000..81c4b501 --- /dev/null +++ b/ui/src/panels/Panel.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +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; +}; + +} \ No newline at end of file diff --git a/ui/src/panels/ProjectPanel.cpp b/ui/src/panels/ProjectPanel.cpp new file mode 100644 index 00000000..e091d055 --- /dev/null +++ b/ui/src/panels/ProjectPanel.cpp @@ -0,0 +1,88 @@ +#include "ProjectPanel.h" +#include +#include + +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(); +} + +} \ No newline at end of file diff --git a/ui/src/panels/ProjectPanel.h b/ui/src/panels/ProjectPanel.h new file mode 100644 index 00000000..06f9aa8c --- /dev/null +++ b/ui/src/panels/ProjectPanel.h @@ -0,0 +1,28 @@ +#pragma once + +#include "Panel.h" +#include +#include + +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 m_folders; + std::vector m_items; + int m_selectedIndex = -1; +}; + +} \ No newline at end of file diff --git a/ui/src/panels/SceneViewPanel.cpp b/ui/src/panels/SceneViewPanel.cpp new file mode 100644 index 00000000..3698b703 --- /dev/null +++ b/ui/src/panels/SceneViewPanel.cpp @@ -0,0 +1,78 @@ +#include "SceneViewPanel.h" +#include +#include + +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); +} + +} \ No newline at end of file diff --git a/ui/src/panels/SceneViewPanel.h b/ui/src/panels/SceneViewPanel.h new file mode 100644 index 00000000..e0caede0 --- /dev/null +++ b/ui/src/panels/SceneViewPanel.h @@ -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(); +}; + +} \ No newline at end of file