diff --git a/CMakeLists.txt b/CMakeLists.txt index fb9e57c6a..0a71c272f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,14 +7,11 @@ # Set CMake policies for modern behavior cmake_policy(SET CMP0091 NEW) # Enable MSVC_RUNTIME_LIBRARY support -# Set consistent runtime library for all targets +# Set consistent runtime library for all targets using modern CMake (CMP0091) if(MSVC) set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL") - # Force specific runtime library settings to avoid conflicts - set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /MDd") - set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /MD") - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MDd") - set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MD") + # Note: With CMP0091 NEW, CMAKE_MSVC_RUNTIME_LIBRARY handles /MD and /MDd + # automatically. Do NOT manually add /MD or /MDd flags as they conflict. endif() # --------------------------------------------------------------------- diff --git a/Spark Engine/Source/Core/SparkEngine.h b/Spark Engine/Source/Core/SparkEngine.h index 3c539200f..a58eee886 100644 --- a/Spark Engine/Source/Core/SparkEngine.h +++ b/Spark Engine/Source/Core/SparkEngine.h @@ -25,7 +25,7 @@ class Timer; * Windows application instance handle used throughout the engine * for Win32 API calls and window management. */ -extern HINSTANCE hInst; +extern HINSTANCE g_hInst; /** * @brief Global graphics engine instance diff --git a/Spark Engine/Source/Game/Console.cpp b/Spark Engine/Source/Game/Console.cpp index d0be54e52..ffbef099b 100644 --- a/Spark Engine/Source/Game/Console.cpp +++ b/Spark Engine/Source/Game/Console.cpp @@ -45,9 +45,10 @@ bool Console::HandleKeyDown(WPARAM key) return true; case VK_RETURN: - ASSERT_MSG(!inputLine.empty(), "Executing empty command"); - ExecuteCommand(inputLine); - inputLine.clear(); + if (!inputLine.empty()) { + ExecuteCommand(inputLine); + inputLine.clear(); + } return true; case VK_ESCAPE: diff --git a/Spark Engine/Source/Game/Game.cpp b/Spark Engine/Source/Game/Game.cpp index a68592a74..1b4f44c93 100644 --- a/Spark Engine/Source/Game/Game.cpp +++ b/Spark Engine/Source/Game/Game.cpp @@ -177,12 +177,12 @@ void Game::Shutdown() --------------------------------------------------------------*/ void Game::Update(float dt) { - // **FIXED: Removed all per-frame logging to prevent console spam** - if (m_isPaused) { return; } + dt *= m_timeScale; + HandleInput(dt); UpdateCamera(dt); UpdateGameObjects(dt); @@ -216,12 +216,14 @@ void Game::Render() } // **CRITICAL: This is the ONLY place BeginFrame/EndFrame should be called** + bool frameStarted = false; try { m_graphics->BeginFrame(); - + frameStarted = true; + // Collect all renderable objects for unified rendering std::vector renderableObjects; - + // Add game objects for (auto& obj : m_gameObjects) { if (obj && obj->IsActive() && obj->IsVisible()) { @@ -241,51 +243,40 @@ void Game::Render() // **UNIFIED RENDERING: Use the complete modern graphics pipeline** XMMATRIX view = m_camera->GetViewMatrix(); XMMATRIX proj = m_camera->GetProjectionMatrix(); - + // Call the unified RenderScene method m_graphics->RenderScene(view, proj, renderableObjects); - + // **ENHANCED: Render player weapons in first-person view** if (m_player) { m_player->Render(view, proj); } - + if (m_projectilePool) { m_projectilePool->Render(view, proj); } - + // **SOLUTION: Single console rendering location - this is the ONLY place console renders** if (g_console.IsVisible()) { g_console.Render(m_graphics->GetContext()); } - - // **CRITICAL: Always EndFrame, even if errors occurred above** - m_graphics->EndFrame(); - + } catch (const std::exception& e) { - // Ensure we always call EndFrame even if rendering fails - try { - m_graphics->EndFrame(); - } catch (...) { - // If EndFrame also fails, log it but don't throw again - LOG_TO_CONSOLE_IMMEDIATE(L"Critical: EndFrame failed during error recovery", L"ERROR"); - } - - // Log the original error std::string errorMsg = "Rendering error: " + std::string(e.what()); std::wstring wErrorMsg(errorMsg.begin(), errorMsg.end()); LOG_TO_CONSOLE_IMMEDIATE(wErrorMsg, L"ERROR"); - + } catch (...) { - // Ensure we always call EndFrame even if rendering fails + LOG_TO_CONSOLE_IMMEDIATE(L"Unknown rendering error occurred", L"ERROR"); + } + + // Always call EndFrame exactly once if BeginFrame succeeded + if (frameStarted) { try { m_graphics->EndFrame(); } catch (...) { - // If EndFrame also fails, log it but don't throw again LOG_TO_CONSOLE_IMMEDIATE(L"Critical: EndFrame failed during error recovery", L"ERROR"); } - - LOG_TO_CONSOLE_IMMEDIATE(L"Unknown rendering error occurred", L"ERROR"); } } @@ -617,9 +608,9 @@ bool Game::DeleteObject(size_t index) { LOG_TO_CONSOLE_IMMEDIATE(L"Deleting object via console integration", L"INFO"); - if (index >= m_gameObjects.size()) { - std::wstring errorMsg = L"Invalid object index: " + std::to_wstring(index) + - L" (max: " + std::to_wstring(m_gameObjects.size() - 1) + L")"; + if (m_gameObjects.empty() || index >= m_gameObjects.size()) { + std::wstring errorMsg = L"Invalid object index: " + std::to_wstring(index) + + L" (total objects: " + std::to_wstring(m_gameObjects.size()) + L")"; LOG_TO_CONSOLE_IMMEDIATE(errorMsg, L"ERROR"); return false; } diff --git a/Spark Engine/Source/Game/ModelObject.cpp b/Spark Engine/Source/Game/ModelObject.cpp index 032256a65..a07e6b0df 100644 --- a/Spark Engine/Source/Game/ModelObject.cpp +++ b/Spark Engine/Source/Game/ModelObject.cpp @@ -42,12 +42,13 @@ void ModelObject::Render(const DirectX::XMMATRIX& view, const DirectX::XMMATRIX& ASSERT(m_context != nullptr); - // ? ENHANCED: Calculate world transformation matrix - DirectX::XMMATRIX world = DirectX::XMMatrixIdentity(); - - // Apply position (access m_position from GameObject base class) + // Build full world matrix with scale, rotation, and translation DirectX::XMFLOAT3 pos = GetPosition(); - world = DirectX::XMMatrixTranslation(pos.x, pos.y, pos.z); + DirectX::XMFLOAT3 rot = GetRotation(); + DirectX::XMFLOAT3 scl = GetScale(); + DirectX::XMMATRIX world = DirectX::XMMatrixScaling(scl.x, scl.y, scl.z) * + DirectX::XMMatrixRotationRollPitchYaw(rot.x, rot.y, rot.z) * + DirectX::XMMatrixTranslation(pos.x, pos.y, pos.z); // ? ENHANCED: Get graphics engine reference (you may need to adjust this based on how you access it) // For now, we'll try to get it from a global or pass it down from the render system diff --git a/Spark Engine/Source/Game/Player.cpp b/Spark Engine/Source/Game/Player.cpp index e4ff9a77f..b4d95b30c 100644 --- a/Spark Engine/Source/Game/Player.cpp +++ b/Spark Engine/Source/Game/Player.cpp @@ -102,7 +102,8 @@ HRESULT Player::Initialize(ID3D11Device* device, if (!m_projectilePool) { - m_projectilePool = new ProjectilePool(50); + m_ownedProjectilePool = std::make_unique(50); + m_projectilePool = m_ownedProjectilePool.get(); ASSERT_NOT_NULL(m_projectilePool); hr = m_projectilePool->Initialize(device, context); LOG_TO_CONSOLE_IMMEDIATE(L"Player projectile pool initialized. HR=0x" + std::to_wstring(hr), L"INFO"); @@ -339,8 +340,9 @@ void Player::OnHit(GameObject* target) { LOG_TO_CONSOLE(L"Player::OnHit called.", L"OPERATION"); ASSERT_NOT_NULL(target); - TakeDamage(m_currentWeapon.Damage); - LOG_TO_CONSOLE(L"Player hit target.", L"INFO"); + // Player was hit by target - take a default collision damage, not own weapon damage + TakeDamage(10.0f); + LOG_TO_CONSOLE(L"Player hit by target.", L"INFO"); } void Player::OnHitWorld(const XMFLOAT3& hitPoint, const XMFLOAT3& normal) @@ -348,7 +350,7 @@ void Player::OnHitWorld(const XMFLOAT3& hitPoint, const XMFLOAT3& normal) LOG_TO_CONSOLE(L"Player::OnHitWorld called.", L"OPERATION"); ASSERT_MSG(std::isfinite(hitPoint.x) && std::isfinite(hitPoint.y) && std::isfinite(hitPoint.z), "Invalid hitPoint"); - TakeDamage(m_currentWeapon.Damage); + // World collision - no damage from hitting world geometry LOG_TO_CONSOLE(L"Player hit world.", L"INFO"); } @@ -521,7 +523,7 @@ XMFLOAT3 Player::CalculateFireDirection() void Player::Console_SetHealth(float health) { - std::lock_guard lock(m_stateMutex); + std::lock_guard lock(m_stateMutex); m_health = std::max(0.0f, std::min(m_maxHealth, health)); if (m_health <= 0.0f) { SetActive(false); @@ -534,7 +536,7 @@ void Player::Console_SetHealth(float health) void Player::Console_SetArmor(float armor) { - std::lock_guard lock(m_stateMutex); + std::lock_guard lock(m_stateMutex); m_armor = std::max(0.0f, std::min(m_maxArmor, armor)); NotifyStateChange(); LOG_TO_CONSOLE_IMMEDIATE(L"Player armor set to " + std::to_wstring(m_armor) + L" via console", L"SUCCESS"); @@ -542,7 +544,7 @@ void Player::Console_SetArmor(float armor) void Player::Console_SetMaxHealth(float maxHealth) { - std::lock_guard lock(m_stateMutex); + std::lock_guard lock(m_stateMutex); if (maxHealth > 0.0f && maxHealth <= 9999.0f) { m_maxHealth = maxHealth; // Ensure current health doesn't exceed new maximum @@ -558,7 +560,7 @@ void Player::Console_SetMaxHealth(float maxHealth) void Player::Console_SetSpeed(float speed) { - std::lock_guard lock(m_stateMutex); + std::lock_guard lock(m_stateMutex); if (speed > 0.0f && speed <= 100.0f) { m_speed = speed; NotifyStateChange(); @@ -570,7 +572,7 @@ void Player::Console_SetSpeed(float speed) void Player::Console_SetJumpHeight(float height) { - std::lock_guard lock(m_stateMutex); + std::lock_guard lock(m_stateMutex); if (height > 0.0f && height <= 50.0f) { m_jumpHeight = height; NotifyStateChange(); @@ -582,7 +584,7 @@ void Player::Console_SetJumpHeight(float height) void Player::Console_SetPosition(float x, float y, float z) { - std::lock_guard lock(m_stateMutex); + std::lock_guard lock(m_stateMutex); DirectX::XMFLOAT3 newPos = { x, y, z }; SetPosition(newPos); @@ -601,7 +603,7 @@ void Player::Console_SetPosition(float x, float y, float z) void Player::Console_SetGodMode(bool enabled) { - std::lock_guard lock(m_stateMutex); + std::lock_guard lock(m_stateMutex); m_godModeEnabled = enabled; NotifyStateChange(); LOG_TO_CONSOLE_IMMEDIATE(L"Player god mode " + std::wstring(enabled ? L"enabled" : L"disabled") + L" via console", L"SUCCESS"); @@ -609,7 +611,7 @@ void Player::Console_SetGodMode(bool enabled) void Player::Console_SetNoclip(bool enabled) { - std::lock_guard lock(m_stateMutex); + std::lock_guard lock(m_stateMutex); m_noclipEnabled = enabled; NotifyStateChange(); LOG_TO_CONSOLE_IMMEDIATE(L"Player noclip " + std::wstring(enabled ? L"enabled" : L"disabled") + L" via console", L"SUCCESS"); @@ -617,7 +619,7 @@ void Player::Console_SetNoclip(bool enabled) void Player::Console_SetInfiniteAmmo(bool enabled) { - std::lock_guard lock(m_stateMutex); + std::lock_guard lock(m_stateMutex); m_infiniteAmmoEnabled = enabled; NotifyStateChange(); LOG_TO_CONSOLE_IMMEDIATE(L"Player infinite ammo " + std::wstring(enabled ? L"enabled" : L"disabled") + L" via console", L"SUCCESS"); @@ -625,7 +627,7 @@ void Player::Console_SetInfiniteAmmo(bool enabled) void Player::Console_GiveAmmo(int amount) { - std::lock_guard lock(m_stateMutex); + std::lock_guard lock(m_stateMutex); if (amount > 0 && amount <= 9999) { m_currentAmmo = std::min(m_currentWeapon.MagazineSize, m_currentAmmo + amount); NotifyStateChange(); @@ -638,7 +640,7 @@ void Player::Console_GiveAmmo(int amount) void Player::Console_ChangeWeapon(WeaponType weaponType) { - std::lock_guard lock(m_stateMutex); + std::lock_guard lock(m_stateMutex); ChangeWeapon(weaponType); NotifyStateChange(); LOG_TO_CONSOLE_IMMEDIATE(L"Player weapon changed via console", L"SUCCESS"); @@ -651,14 +653,14 @@ Player::PlayerState Player::Console_GetState() const void Player::Console_RegisterStateCallback(std::function callback) { - std::lock_guard lock(m_stateMutex); + std::lock_guard lock(m_stateMutex); m_stateCallback = callback; LOG_TO_CONSOLE_IMMEDIATE(L"Player state callback registered", L"INFO"); } void Player::Console_ApplyPhysicsSettings(float gravity, float friction) { - std::lock_guard lock(m_stateMutex); + std::lock_guard lock(m_stateMutex); if (gravity >= -100.0f && gravity <= 100.0f) { m_gravityForce = gravity; } @@ -679,7 +681,7 @@ void Player::NotifyStateChange() Player::PlayerState Player::GetStateThreadSafe() const { - std::lock_guard lock(m_stateMutex); + std::lock_guard lock(m_stateMutex); PlayerState state; state.health = m_health; state.maxHealth = m_maxHealth; diff --git a/Spark Engine/Source/Game/Player.h b/Spark Engine/Source/Game/Player.h index 45e3ab563..d82df1dd6 100644 --- a/Spark Engine/Source/Game/Player.h +++ b/Spark Engine/Source/Game/Player.h @@ -432,12 +432,13 @@ class Player : public GameObject // Console callback system std::function m_stateCallback; - mutable std::mutex m_stateMutex; ///< Thread safety for state access + mutable std::recursive_mutex m_stateMutex; ///< Thread safety for state access // External references (not owned) SparkEngineCamera* m_camera{ nullptr }; ///< Reference to camera system InputManager* m_input{ nullptr }; ///< Reference to input manager ProjectilePool* m_projectilePool{ nullptr }; ///< Reference to projectile pool + std::unique_ptr m_ownedProjectilePool; ///< Owned pool if created by Player // Collision & animation BoundingSphere m_collisionSphere; ///< Collision bounds for player diff --git a/Spark Engine/Source/Game/Terrain.h b/Spark Engine/Source/Game/Terrain.h index 5218ee110..37360bb27 100644 --- a/Spark Engine/Source/Game/Terrain.h +++ b/Spark Engine/Source/Game/Terrain.h @@ -17,6 +17,11 @@ struct TerrainVertex { class Terrain { public: + ~Terrain() { + if (m_vb) { m_vb->Release(); m_vb = nullptr; } + if (m_ib) { m_ib->Release(); m_ib = nullptr; } + } + // Load heightmap from 8-bit BMP; build vertex/index buffers HRESULT Initialize(ID3D11Device* device, ID3D11DeviceContext* ctx, diff --git a/Spark Engine/Source/Graphics/GraphicsEngine.cpp b/Spark Engine/Source/Graphics/GraphicsEngine.cpp index 37d404c09..3a3b52b4e 100644 --- a/Spark Engine/Source/Graphics/GraphicsEngine.cpp +++ b/Spark Engine/Source/Graphics/GraphicsEngine.cpp @@ -94,6 +94,10 @@ using Microsoft::WRL::ComPtr; GraphicsEngine::GraphicsEngine() : m_windowWidth(1280) , m_windowHeight(720) + , m_width(1280) + , m_height(720) + , m_fullscreen(false) + , m_hwnd(nullptr) , m_textureMemoryUsage(0) , m_bufferMemoryUsage(0) , m_frameInProgress(false) @@ -1105,6 +1109,9 @@ void GraphicsEngine::ApplyGraphicsState() void GraphicsEngine::UpdateMetrics() { + static int frameCount = 0; + frameCount++; + auto now = std::chrono::high_resolution_clock::now(); static auto lastUpdate = now; auto elapsed = std::chrono::duration_cast(now - lastUpdate); @@ -1112,8 +1119,6 @@ void GraphicsEngine::UpdateMetrics() if (elapsed.count() >= 1000) { std::lock_guard lock(m_metricsMutex); - static int frameCount = 0; - frameCount++; m_statistics.fps = static_cast(frameCount * 1000.0f / elapsed.count()); frameCount = 0; lastUpdate = now; @@ -1593,7 +1598,9 @@ void GraphicsEngine::UpdateFrameConstants(const XMMATRIX& view, const XMMATRIX& frameConstants.Time = 0.0f; // You could track actual time here frameConstants.DeltaTime = 0.016f; // Approximate 60 FPS frameConstants.ScreenResolution = XMFLOAT2(static_cast(m_windowWidth), static_cast(m_windowHeight)); - frameConstants.InvScreenResolution = XMFLOAT2(1.0f / m_windowWidth, 1.0f / m_windowHeight); + frameConstants.InvScreenResolution = XMFLOAT2( + (m_windowWidth > 0) ? (1.0f / m_windowWidth) : 0.0f, + (m_windowHeight > 0) ? (1.0f / m_windowHeight) : 0.0f); // Set up basic directional lighting frameConstants.DirectionalLightDir = XMFLOAT3(0.3f, -0.7f, 0.6f); // Pointing down and slightly forward diff --git a/Spark Engine/Source/Graphics/GraphicsEngine.h b/Spark Engine/Source/Graphics/GraphicsEngine.h index 14aa1f4be..ee0be9c78 100644 --- a/Spark Engine/Source/Graphics/GraphicsEngine.h +++ b/Spark Engine/Source/Graphics/GraphicsEngine.h @@ -190,46 +190,46 @@ struct GraphicsSettings struct RenderStatistics { // Performance - float frameTime; ///< Total frame time (ms) - float cpuTime; ///< CPU time (ms) - float gpuTime; ///< GPU time (ms) - uint32_t fps; ///< Frames per second - + float frameTime = 0.0f; ///< Total frame time (ms) + float cpuTime = 0.0f; ///< CPU time (ms) + float gpuTime = 0.0f; ///< GPU time (ms) + uint32_t fps = 0; ///< Frames per second + // Rendering - uint32_t drawCalls; ///< Draw calls per frame - uint32_t triangles; ///< Triangles rendered - uint32_t vertices; ///< Vertices processed - uint32_t textureBinds; ///< Texture binds per frame - uint32_t materialSwitches; ///< Material switches per frame - + uint32_t drawCalls = 0; ///< Draw calls per frame + uint32_t triangles = 0; ///< Triangles rendered + uint32_t vertices = 0; ///< Vertices processed + uint32_t textureBinds = 0; ///< Texture binds per frame + uint32_t materialSwitches = 0; ///< Material switches per frame + // Culling - uint32_t totalObjects; ///< Total objects in scene - uint32_t visibleObjects; ///< Objects after culling - uint32_t culledObjects; ///< Objects culled - float cullingTime; ///< Culling time (ms) - + uint32_t totalObjects = 0; ///< Total objects in scene + uint32_t visibleObjects = 0; ///< Objects after culling + uint32_t culledObjects = 0; ///< Objects culled + float cullingTime = 0.0f; ///< Culling time (ms) + // Memory - size_t textureMemory; ///< Texture memory usage (bytes) - size_t meshMemory; ///< Mesh memory usage (bytes) - size_t totalGPUMemory; ///< Total GPU memory usage (bytes) - + size_t textureMemory = 0; ///< Texture memory usage (bytes) + size_t meshMemory = 0; ///< Mesh memory usage (bytes) + size_t totalGPUMemory = 0; ///< Total GPU memory usage (bytes) + // Lighting - uint32_t activeLights; ///< Active lights - uint32_t shadowUpdates; ///< Shadow map updates - float lightCullingTime; ///< Light culling time (ms) - + uint32_t activeLights = 0; ///< Active lights + uint32_t shadowUpdates = 0; ///< Shadow map updates + float lightCullingTime = 0.0f; ///< Light culling time (ms) + // Post-processing - float postProcessTime; ///< Post-processing time (ms) - uint32_t postProcessPasses; ///< Post-processing passes - + float postProcessTime = 0.0f; ///< Post-processing time (ms) + uint32_t postProcessPasses = 0;///< Post-processing passes + // Legacy compatibility - float renderTime; ///< Legacy render time - float presentTime; ///< Legacy present time - size_t bufferMemory; ///< Legacy buffer memory - float gpuUsage; ///< Legacy GPU usage - bool vsyncEnabled; ///< Legacy VSync state - bool wireframeMode; ///< Legacy wireframe state - bool debugMode; ///< Legacy debug state + float renderTime = 0.0f; ///< Legacy render time + float presentTime = 0.0f; ///< Legacy present time + size_t bufferMemory = 0; ///< Legacy buffer memory + float gpuUsage = 0.0f; ///< Legacy GPU usage + bool vsyncEnabled = false; ///< Legacy VSync state + bool wireframeMode = false; ///< Legacy wireframe state + bool debugMode = false; ///< Legacy debug state }; /** diff --git a/Spark Engine/Source/Graphics/LightingSystem.cpp b/Spark Engine/Source/Graphics/LightingSystem.cpp index 5945d87d9..a11a3c1fc 100644 --- a/Spark Engine/Source/Graphics/LightingSystem.cpp +++ b/Spark Engine/Source/Graphics/LightingSystem.cpp @@ -433,14 +433,22 @@ void LightingSystem::GenerateIBLTextures() // For now, just log the operation Spark::SimpleConsole::GetInstance().LogInfo("Generating IBL textures"); - HRESULT hr = S_OK; - hr = GenerateIrradianceMap(m_environmentLighting.environmentMap.Get()); + HRESULT hr = GenerateIrradianceMap(m_environmentLighting.environmentMap.Get()); + if (FAILED(hr)) { + Spark::SimpleConsole::GetInstance().LogError("Failed to generate irradiance map"); + return; + } hr = GeneratePrefilterMap(m_environmentLighting.environmentMap.Get()); + if (FAILED(hr)) { + Spark::SimpleConsole::GetInstance().LogError("Failed to generate prefilter map"); + return; + } hr = GenerateBRDFLUT(); - - if (SUCCEEDED(hr)) { - Spark::SimpleConsole::GetInstance().LogSuccess("IBL textures generated successfully"); + if (FAILED(hr)) { + Spark::SimpleConsole::GetInstance().LogError("Failed to generate BRDF LUT"); + return; } + Spark::SimpleConsole::GetInstance().LogSuccess("IBL textures generated successfully"); } // ============================================================================ diff --git a/Spark Engine/Source/Graphics/LightingSystem.h b/Spark Engine/Source/Graphics/LightingSystem.h index b51875ccb..da9a79687 100644 --- a/Spark Engine/Source/Graphics/LightingSystem.h +++ b/Spark Engine/Source/Graphics/LightingSystem.h @@ -207,7 +207,7 @@ struct ShadowMap ComPtr texture; ///< Shadow map texture ComPtr dsv; ///< Depth stencil view ComPtr srv; ///< Shader resource view - uint32_t size; ///< Shadow map size + uint32_t size = 0; ///< Shadow map size XMMATRIX lightMatrix; ///< Light projection matrix XMMATRIX shadowMatrix; ///< Shadow transformation matrix }; @@ -237,14 +237,14 @@ class LightingSystem */ struct LightingMetrics { - uint32_t activeLights; ///< Number of active lights - uint32_t shadowCastingLights; ///< Number of shadow casting lights - uint32_t shadowMapUpdates; ///< Shadow map updates per frame - float shadowMapMemory; ///< Shadow map memory usage (MB) - float lightCullingTime; ///< Light culling time (ms) - float shadowRenderTime; ///< Shadow rendering time (ms) - uint32_t visibleLights; ///< Lights visible to camera - uint32_t culledLights; ///< Lights culled this frame + uint32_t activeLights = 0; ///< Number of active lights + uint32_t shadowCastingLights = 0; ///< Number of shadow casting lights + uint32_t shadowMapUpdates = 0; ///< Shadow map updates per frame + float shadowMapMemory = 0.0f; ///< Shadow map memory usage (MB) + float lightCullingTime = 0.0f; ///< Light culling time (ms) + float shadowRenderTime = 0.0f; ///< Shadow rendering time (ms) + uint32_t visibleLights = 0; ///< Lights visible to camera + uint32_t culledLights = 0; ///< Lights culled this frame }; LightingSystem(); @@ -367,8 +367,8 @@ class LightingSystem void Console_ReloadIBL(); private: - ID3D11Device* m_device; - ID3D11DeviceContext* m_context; + ID3D11Device* m_device = nullptr; + ID3D11DeviceContext* m_context = nullptr; // Light storage std::vector> m_lights; diff --git a/Spark Engine/Source/Graphics/Mesh.cpp b/Spark Engine/Source/Graphics/Mesh.cpp index 591166a5d..de9ab0413 100644 --- a/Spark Engine/Source/Graphics/Mesh.cpp +++ b/Spark Engine/Source/Graphics/Mesh.cpp @@ -486,6 +486,10 @@ HRESULT Mesh::CreateBuffers() { ASSERT(m_device); ASSERT(!m_vertices.empty() && !m_indices.empty()); + // Release existing buffers before creating new ones to prevent COM leaks + if (m_vb) { m_vb->Release(); m_vb = nullptr; } + if (m_ib) { m_ib->Release(); m_ib = nullptr; } + // Vertex buffer D3D11_BUFFER_DESC vbd{}; vbd.Usage = D3D11_USAGE_DEFAULT; diff --git a/Spark Engine/Source/Graphics/PostProcessing.h b/Spark Engine/Source/Graphics/PostProcessing.h index 0ef87987f..2315fbe9a 100644 --- a/Spark Engine/Source/Graphics/PostProcessing.h +++ b/Spark Engine/Source/Graphics/PostProcessing.h @@ -115,12 +115,12 @@ class PostProcessingSystem */ struct PostProcessMetrics { - float totalRenderTime; - float bloomTime; - float toneMappingTime; - float colorGradingTime; - uint32_t activeEffects; - float memoryUsage; + float totalRenderTime = 0.0f; + float bloomTime = 0.0f; + float toneMappingTime = 0.0f; + float colorGradingTime = 0.0f; + uint32_t activeEffects = 0; + float memoryUsage = 0.0f; }; PostProcessingSystem(); @@ -148,11 +148,11 @@ class PostProcessingSystem void Console_SetBloomParams(float threshold, float intensity, float radius); private: - ID3D11Device* m_device; - ID3D11DeviceContext* m_context; + ID3D11Device* m_device = nullptr; + ID3D11DeviceContext* m_context = nullptr; bool m_hdrEnabled = true; - uint32_t m_width, m_height; + uint32_t m_width = 0, m_height = 0; BloomSettings m_bloomSettings; ToneMappingSettings m_toneMappingSettings; diff --git a/Spark Engine/Source/Graphics/Shader.h b/Spark Engine/Source/Graphics/Shader.h index d2f2cc32b..4001c09c9 100644 --- a/Spark Engine/Source/Graphics/Shader.h +++ b/Spark Engine/Source/Graphics/Shader.h @@ -532,8 +532,8 @@ class Shader ShaderMetrics GetMetricsThreadSafe() const; // DirectX resources - ID3D11Device* m_device; - ID3D11DeviceContext* m_context; + ID3D11Device* m_device = nullptr; + ID3D11DeviceContext* m_context = nullptr; // Shader resources std::unique_ptr m_vertexShader; @@ -549,7 +549,7 @@ class Shader // Shader management std::unordered_map> m_shaderCache; std::vector> m_shaderVariants; - int m_activeVariant; + int m_activeVariant = 0; // File monitoring for hot reload std::vector m_watchedFiles; @@ -562,14 +562,14 @@ class Shader // Configuration ShaderCompilationFlags m_defaultFlags; - bool m_hotReloadEnabled; - bool m_validationEnabled; + bool m_hotReloadEnabled = false; + bool m_validationEnabled = false; // Additional members for implementation std::vector m_variants; ///< Shader variants std::string m_filePath; ///< Current shader file path - FILETIME m_lastModified; ///< Last modification time - ShaderType m_type; ///< Current shader type - bool m_isCompiled; ///< Compilation status - ID3D11DeviceChild* m_shader; ///< Generic shader interface + FILETIME m_lastModified = {}; ///< Last modification time + ShaderType m_type = ShaderType::VERTEX; ///< Current shader type + bool m_isCompiled = false; ///< Compilation status + ID3D11DeviceChild* m_shader = nullptr; ///< Generic shader interface }; \ No newline at end of file diff --git a/Spark Engine/Source/Input/InputManager.cpp b/Spark Engine/Source/Input/InputManager.cpp index 926bf6d16..5835a6116 100644 --- a/Spark Engine/Source/Input/InputManager.cpp +++ b/Spark Engine/Source/Input/InputManager.cpp @@ -586,14 +586,14 @@ int InputManager::KeyNameToVirtualKey(const std::string& keyName) const { {"Y", 'Y'}, {"Z", 'Z'}, {"0", '0'}, {"1", '1'}, {"2", '2'}, {"3", '3'}, {"4", '4'}, {"5", '5'}, {"6", '6'}, {"7", '7'}, {"8", '8'}, {"9", '9'}, - {"Space", VK_SPACE}, {"Enter", VK_RETURN}, {"Escape", VK_ESCAPE}, - {"Tab", VK_TAB}, {"Shift", VK_SHIFT}, {"Ctrl", VK_CONTROL}, {"Alt", VK_MENU}, + {"SPACE", VK_SPACE}, {"ENTER", VK_RETURN}, {"ESCAPE", VK_ESCAPE}, + {"TAB", VK_TAB}, {"SHIFT", VK_SHIFT}, {"CTRL", VK_CONTROL}, {"ALT", VK_MENU}, {"F1", VK_F1}, {"F2", VK_F2}, {"F3", VK_F3}, {"F4", VK_F4}, {"F5", VK_F5}, {"F6", VK_F6}, {"F7", VK_F7}, {"F8", VK_F8}, {"F9", VK_F9}, {"F10", VK_F10}, {"F11", VK_F11}, {"F12", VK_F12}, - {"Up", VK_UP}, {"Down", VK_DOWN}, {"Left", VK_LEFT}, {"Right", VK_RIGHT} + {"UP", VK_UP}, {"DOWN", VK_DOWN}, {"LEFT", VK_LEFT}, {"RIGHT", VK_RIGHT} }; - + std::string upperKeyName = keyName; std::transform(upperKeyName.begin(), upperKeyName.end(), upperKeyName.begin(), ::toupper); diff --git a/Spark Engine/Source/Physics/CollisionSystem.cpp b/Spark Engine/Source/Physics/CollisionSystem.cpp index ac6d718e2..4a4a8cfb0 100644 --- a/Spark Engine/Source/Physics/CollisionSystem.cpp +++ b/Spark Engine/Source/Physics/CollisionSystem.cpp @@ -181,7 +181,10 @@ CollisionResult CollisionSystem::RayVsBox(const Ray& ray, const BoundingBox& box // Manual component intersection float ox = ray.Origin.x, oy = ray.Origin.y, oz = ray.Origin.z; float dx = ray.Direction.x, dy = ray.Direction.y, dz = ray.Direction.z; - float invX = 1.0f / dx, invY = 1.0f / dy, invZ = 1.0f / dz; + constexpr float epsilon = 1e-8f; + float invX = (fabsf(dx) > epsilon) ? (1.0f / dx) : ((dx >= 0.0f) ? (1.0f / epsilon) : (-1.0f / epsilon)); + float invY = (fabsf(dy) > epsilon) ? (1.0f / dy) : ((dy >= 0.0f) ? (1.0f / epsilon) : (-1.0f / epsilon)); + float invZ = (fabsf(dz) > epsilon) ? (1.0f / dz) : ((dz >= 0.0f) ? (1.0f / epsilon) : (-1.0f / epsilon)); float t1x = (box.Min.x - ox) * invX; float t2x = (box.Max.x - ox) * invX; diff --git a/Spark Engine/Source/Projectiles/Bullet.cpp b/Spark Engine/Source/Projectiles/Bullet.cpp index 24ae42375..15e0c0add 100644 --- a/Spark Engine/Source/Projectiles/Bullet.cpp +++ b/Spark Engine/Source/Projectiles/Bullet.cpp @@ -38,6 +38,7 @@ void Bullet::Update(float deltaTime) void Bullet::Render(const XMMATRIX& view, const XMMATRIX& projection) { + if (!m_active) return; ASSERT_MSG(m_mesh != nullptr, "Bullet mesh not initialized"); Projectile::Render(view, projection); } \ No newline at end of file diff --git a/Spark Engine/Source/Projectiles/Grenade.cpp b/Spark Engine/Source/Projectiles/Grenade.cpp index 49b7839c7..270f68a78 100644 --- a/Spark Engine/Source/Projectiles/Grenade.cpp +++ b/Spark Engine/Source/Projectiles/Grenade.cpp @@ -43,27 +43,33 @@ void Grenade::Update(float deltaTime) if (!m_active) return; - // Fuse countdown - m_lifeTime += deltaTime; + // Use base physics/lifetime/collision (this also increments m_lifeTime) + Projectile::Update(deltaTime); + + // Check fuse after base update (m_lifeTime is already incremented by Projectile::Update) if (m_lifeTime >= m_fuseTime && !m_hasExploded) { Explode(); return; } - - // Use base physics/lifetime/collision - Projectile::Update(deltaTime); } void Grenade::Render(const XMMATRIX& view, const XMMATRIX& projection) { + if (!m_active) return; ASSERT_MSG(m_mesh != nullptr, "Grenade mesh not initialized"); Projectile::Render(view, projection); } +void Grenade::Fire(const XMFLOAT3& startPosition, const XMFLOAT3& direction, float speed) +{ + m_hasExploded = false; + Projectile::Fire(startPosition, direction, speed); +} + void Grenade::Explode() { - ASSERT_MSG(!m_hasExploded, "Grenade exploded multiple times"); + if (m_hasExploded) return; m_hasExploded = true; // TODO: spawn explosion effect at current position diff --git a/Spark Engine/Source/Projectiles/Grenade.h b/Spark Engine/Source/Projectiles/Grenade.h index 1923acf73..98b04d097 100644 --- a/Spark Engine/Source/Projectiles/Grenade.h +++ b/Spark Engine/Source/Projectiles/Grenade.h @@ -17,6 +17,7 @@ class Grenade : public Projectile HRESULT Initialize(ID3D11Device* device, ID3D11DeviceContext* context) override; void Update(float deltaTime) override; void Render(const XMMATRIX& view, const XMMATRIX& projection) override; + void Fire(const XMFLOAT3& startPosition, const XMFLOAT3& direction, float speed) override; private: void Explode(); diff --git a/Spark Engine/Source/Projectiles/Projectile.cpp b/Spark Engine/Source/Projectiles/Projectile.cpp index 5eb9238ad..38f118dbe 100644 --- a/Spark Engine/Source/Projectiles/Projectile.cpp +++ b/Spark Engine/Source/Projectiles/Projectile.cpp @@ -159,9 +159,9 @@ void Projectile::UpdatePhysics(float deltaTime) if (m_hasGravity) m_velocity.y += -9.8f * m_gravityScale * deltaTime; - // Simple drag + // Frame-rate independent drag XMVECTOR v = XMLoadFloat3(&m_velocity); - v = XMVectorScale(v, 0.98f); + v = XMVectorScale(v, powf(0.98f, deltaTime * 60.0f)); XMStoreFloat3(&m_velocity, v); } diff --git a/Spark Engine/Source/Projectiles/ProjectilePool.h b/Spark Engine/Source/Projectiles/ProjectilePool.h index 517b0eea9..f8780b0b9 100644 --- a/Spark Engine/Source/Projectiles/ProjectilePool.h +++ b/Spark Engine/Source/Projectiles/ProjectilePool.h @@ -149,8 +149,8 @@ class ProjectilePool void CreateProjectiles(); size_t m_poolSize; ///< Maximum pool size - ID3D11Device* m_device; ///< DirectX device reference - ID3D11DeviceContext* m_context; ///< DirectX context reference + ID3D11Device* m_device{ nullptr }; ///< DirectX device reference + ID3D11DeviceContext* m_context{ nullptr }; ///< DirectX context reference std::vector> m_projectiles; ///< All projectile objects std::queue m_availableProjectiles; ///< Queue of available projectiles }; \ No newline at end of file diff --git a/Spark Engine/Source/Projectiles/Rocket.cpp b/Spark Engine/Source/Projectiles/Rocket.cpp index f9f811f10..67564fe63 100644 --- a/Spark Engine/Source/Projectiles/Rocket.cpp +++ b/Spark Engine/Source/Projectiles/Rocket.cpp @@ -43,10 +43,17 @@ void Rocket::Update(float deltaTime) void Rocket::Render(const XMMATRIX& view, const XMMATRIX& projection) { + if (!m_active) return; ASSERT_MSG(m_mesh != nullptr, "Rocket mesh not initialized"); Projectile::Render(view, projection); } +void Rocket::Fire(const XMFLOAT3& startPosition, const XMFLOAT3& direction, float speed) +{ + m_hasExploded = false; + Projectile::Fire(startPosition, direction, speed); +} + void Rocket::OnHit(GameObject* target) { ASSERT_MSG(target != nullptr, "Rocket::OnHit target is null"); diff --git a/Spark Engine/Source/Projectiles/Rocket.h b/Spark Engine/Source/Projectiles/Rocket.h index 5f28aa4d6..9b102aae4 100644 --- a/Spark Engine/Source/Projectiles/Rocket.h +++ b/Spark Engine/Source/Projectiles/Rocket.h @@ -17,6 +17,7 @@ class Rocket : public Projectile HRESULT Initialize(ID3D11Device* device, ID3D11DeviceContext* context) override; void Update(float deltaTime) override; void Render(const XMMATRIX& view, const XMMATRIX& projection) override; + void Fire(const XMFLOAT3& startPosition, const XMFLOAT3& direction, float speed) override; void OnHit(GameObject* target) override; void OnHitWorld(const XMFLOAT3& hitPoint, const XMFLOAT3& normal) override; diff --git a/SparkEditor/CMakeLists.txt b/SparkEditor/CMakeLists.txt index b3bc482dd..0a5715c60 100644 --- a/SparkEditor/CMakeLists.txt +++ b/SparkEditor/CMakeLists.txt @@ -19,41 +19,44 @@ find_path(IMGUI_INCLUDE_DIR if(IMGUI_INCLUDE_DIR) message(STATUS "Found Dear ImGui at: ${IMGUI_INCLUDE_DIR}") - - add_library(imgui STATIC - "${IMGUI_INCLUDE_DIR}/imgui.cpp" - "${IMGUI_INCLUDE_DIR}/imgui_demo.cpp" - "${IMGUI_INCLUDE_DIR}/imgui_draw.cpp" - "${IMGUI_INCLUDE_DIR}/imgui_tables.cpp" - "${IMGUI_INCLUDE_DIR}/imgui_widgets.cpp" - "${IMGUI_INCLUDE_DIR}/backends/imgui_impl_win32.cpp" - "${IMGUI_INCLUDE_DIR}/backends/imgui_impl_dx11.cpp" - ) - - target_include_directories(imgui PUBLIC - "${IMGUI_INCLUDE_DIR}" - "${IMGUI_INCLUDE_DIR}/backends" - ) - - target_compile_definitions(imgui PUBLIC - IMGUI_IMPL_WIN32_DISABLE_GAMEPAD - IMGUI_IMPL_WIN32_DISABLE_LINKING_XINPUT - ) - - target_link_libraries(imgui PUBLIC - d3d11 dxgi user32 gdi32 shell32 ole32 oleaut32 uuid comdlg32 advapi32 - ) - - if(MSVC) - target_compile_options(imgui PRIVATE /W2) - target_compile_definitions(imgui PRIVATE - WIN32_LEAN_AND_MEAN - NOMINMAX - _CRT_SECURE_NO_WARNINGS + + # Only create the imgui target if it doesn't already exist (may be defined in ThirdParty) + if(NOT TARGET imgui) + add_library(imgui STATIC + "${IMGUI_INCLUDE_DIR}/imgui.cpp" + "${IMGUI_INCLUDE_DIR}/imgui_demo.cpp" + "${IMGUI_INCLUDE_DIR}/imgui_draw.cpp" + "${IMGUI_INCLUDE_DIR}/imgui_tables.cpp" + "${IMGUI_INCLUDE_DIR}/imgui_widgets.cpp" + "${IMGUI_INCLUDE_DIR}/backends/imgui_impl_win32.cpp" + "${IMGUI_INCLUDE_DIR}/backends/imgui_impl_dx11.cpp" + ) + + target_include_directories(imgui PUBLIC + "${IMGUI_INCLUDE_DIR}" + "${IMGUI_INCLUDE_DIR}/backends" + ) + + target_compile_definitions(imgui PUBLIC + IMGUI_IMPL_WIN32_DISABLE_GAMEPAD + IMGUI_IMPL_WIN32_DISABLE_LINKING_XINPUT ) - set_property(TARGET imgui PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL") + + target_link_libraries(imgui PUBLIC + d3d11 dxgi user32 gdi32 shell32 ole32 oleaut32 uuid comdlg32 advapi32 + ) + + if(MSVC) + target_compile_options(imgui PRIVATE /W2) + target_compile_definitions(imgui PRIVATE + WIN32_LEAN_AND_MEAN + NOMINMAX + _CRT_SECURE_NO_WARNINGS + ) + set_property(TARGET imgui PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL") + endif() endif() - + set(IMGUI_FOUND TRUE) else() message(FATAL_ERROR "Dear ImGui is required for SparkEditor but was not found!") @@ -164,9 +167,20 @@ add_custom_command(TARGET SparkEditor POST_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory "$/EditorAssets/Layouts" COMMAND ${CMAKE_COMMAND} -E make_directory "$/Projects" COMMAND ${CMAKE_COMMAND} -E make_directory "$/Temp" + COMMAND ${CMAKE_COMMAND} -E make_directory "$/EditorAssets/Fonts" COMMENT "Creating SparkEditor directory structure" ) +# Copy fonts to output directory +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/Fonts") + add_custom_command(TARGET SparkEditor POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_CURRENT_SOURCE_DIR}/Fonts" + "$/EditorAssets/Fonts" + COMMENT "Copying editor fonts" + ) +endif() + # Dependencies if(TARGET SparkEngine) add_dependencies(SparkEditor SparkEngine) diff --git a/SparkEditor/Fonts/JetBrainsMono-Regular.ttf b/SparkEditor/Fonts/JetBrainsMono-Regular.ttf new file mode 100644 index 000000000..dff66cc50 Binary files /dev/null and b/SparkEditor/Fonts/JetBrainsMono-Regular.ttf differ diff --git a/SparkEditor/Fonts/Roboto-Bold.ttf b/SparkEditor/Fonts/Roboto-Bold.ttf new file mode 100644 index 000000000..8869666f2 Binary files /dev/null and b/SparkEditor/Fonts/Roboto-Bold.ttf differ diff --git a/SparkEditor/Fonts/Roboto-Regular.ttf b/SparkEditor/Fonts/Roboto-Regular.ttf new file mode 100644 index 000000000..ddee473e0 Binary files /dev/null and b/SparkEditor/Fonts/Roboto-Regular.ttf differ diff --git a/SparkEditor/Fonts/fa-solid-900.ttf b/SparkEditor/Fonts/fa-solid-900.ttf new file mode 100644 index 000000000..a0414182d Binary files /dev/null and b/SparkEditor/Fonts/fa-solid-900.ttf differ diff --git a/SparkEditor/Source/Core/EditorApplication.cpp b/SparkEditor/Source/Core/EditorApplication.cpp index 93ceb02ed..0cdb19ff3 100644 --- a/SparkEditor/Source/Core/EditorApplication.cpp +++ b/SparkEditor/Source/Core/EditorApplication.cpp @@ -6,8 +6,9 @@ */ #include "EditorApplication.h" -#include "EditorUI.h" -#include "EditorCrashHandler.h" +#include "EditorUI.h" +#include "EditorFonts.h" +#include "EditorCrashHandler.h" #include "../Utils/SparkConsole.h" #include #include @@ -230,11 +231,17 @@ bool EditorApplication::InitializeImGui() ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); - // Enable keyboard controls + // Enable keyboard controls and docking io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; - // Note: Docking features require a newer ImGui version - // io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; - + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; + + // Docking configuration + io.ConfigDockingWithShift = false; // Dock without holding Shift + io.ConfigWindowsResizeFromEdges = true; + + // Load custom fonts before backend initialization + EditorFonts::LoadFonts(15.0f); + // Setup Platform/Renderer backends if (!ImGui_ImplWin32_Init(m_hwnd)) { std::cerr << "Failed to initialize ImGui Win32 backend\n"; diff --git a/SparkEditor/Source/Core/EditorFonts.cpp b/SparkEditor/Source/Core/EditorFonts.cpp new file mode 100644 index 000000000..1048e27e1 --- /dev/null +++ b/SparkEditor/Source/Core/EditorFonts.cpp @@ -0,0 +1,104 @@ +/** + * @file EditorFonts.cpp + * @brief Font loading implementation for the Spark Engine Editor + * @author Spark Engine Team + * @date 2025 + */ + +#include "EditorFonts.h" +#include "EditorIcons.h" +#include +#include + +namespace SparkEditor { + +ImFont* EditorFonts::s_defaultFont = nullptr; +ImFont* EditorFonts::s_boldFont = nullptr; +ImFont* EditorFonts::s_monoFont = nullptr; +ImFont* EditorFonts::s_largeFont = nullptr; +bool EditorFonts::s_customFontsLoaded = false; + +void EditorFonts::LoadFonts(float baseFontSize) +{ + ImGuiIO& io = ImGui::GetIO(); + + // Font search paths (relative to executable) + const char* fontPaths[] = { + "EditorAssets/Fonts/", + "Fonts/", + "../SparkEditor/Fonts/", + "../Fonts/", + }; + + std::string fontDir; + for (const char* path : fontPaths) { + if (std::filesystem::exists(std::string(path) + "Roboto-Regular.ttf")) { + fontDir = path; + break; + } + } + + if (fontDir.empty()) { + std::cout << "[EditorFonts] Custom fonts not found, using ImGui default font\n"; + s_defaultFont = io.Fonts->AddFontDefault(); + return; + } + + std::cout << "[EditorFonts] Loading fonts from: " << fontDir << "\n"; + + // Icon font config (merged into each primary font) + static const ImWchar iconRanges[] = { ICON_FA_MIN, ICON_FA_MAX, 0 }; + std::string iconFontPath = fontDir + "fa-solid-900.ttf"; + bool hasIconFont = std::filesystem::exists(iconFontPath); + + // --- Default font: Roboto Regular + FontAwesome --- + ImFontConfig defaultConfig; + defaultConfig.OversampleH = 2; + defaultConfig.OversampleV = 1; + defaultConfig.PixelSnapH = true; + s_defaultFont = io.Fonts->AddFontFromFileTTF( + (fontDir + "Roboto-Regular.ttf").c_str(), baseFontSize, &defaultConfig); + + if (s_defaultFont && hasIconFont) { + ImFontConfig iconConfig; + iconConfig.MergeMode = true; + iconConfig.GlyphMinAdvanceX = baseFontSize; + iconConfig.PixelSnapH = true; + io.Fonts->AddFontFromFileTTF(iconFontPath.c_str(), baseFontSize - 1.0f, &iconConfig, iconRanges); + } + + // --- Bold font --- + std::string boldPath = fontDir + "Roboto-Bold.ttf"; + if (std::filesystem::exists(boldPath)) { + ImFontConfig boldConfig; + boldConfig.OversampleH = 2; + s_boldFont = io.Fonts->AddFontFromFileTTF(boldPath.c_str(), baseFontSize, &boldConfig); + } + + // --- Monospace font --- + std::string monoPath = fontDir + "JetBrainsMono-Regular.ttf"; + if (std::filesystem::exists(monoPath)) { + ImFontConfig monoConfig; + monoConfig.OversampleH = 2; + s_monoFont = io.Fonts->AddFontFromFileTTF(monoPath.c_str(), baseFontSize - 1.0f, &monoConfig); + } + + // --- Large font (for titles/headers) --- + ImFontConfig largeConfig; + largeConfig.OversampleH = 2; + s_largeFont = io.Fonts->AddFontFromFileTTF( + (fontDir + "Roboto-Bold.ttf").c_str(), baseFontSize + 5.0f, &largeConfig); + + if (s_largeFont && hasIconFont) { + ImFontConfig iconLargeConfig; + iconLargeConfig.MergeMode = true; + iconLargeConfig.GlyphMinAdvanceX = baseFontSize + 5.0f; + iconLargeConfig.PixelSnapH = true; + io.Fonts->AddFontFromFileTTF(iconFontPath.c_str(), baseFontSize + 4.0f, &iconLargeConfig, iconRanges); + } + + s_customFontsLoaded = (s_defaultFont != nullptr); + std::cout << "[EditorFonts] Fonts loaded successfully (custom=" << s_customFontsLoaded << ")\n"; +} + +} // namespace SparkEditor diff --git a/SparkEditor/Source/Core/EditorFonts.h b/SparkEditor/Source/Core/EditorFonts.h new file mode 100644 index 000000000..952046063 --- /dev/null +++ b/SparkEditor/Source/Core/EditorFonts.h @@ -0,0 +1,55 @@ +/** + * @file EditorFonts.h + * @brief Font management for the Spark Engine Editor + * @author Spark Engine Team + * @date 2025 + */ + +#pragma once + +#include +#include + +namespace SparkEditor { + +/** + * @brief Centralized font manager for the editor + * + * Loads and provides access to themed fonts including: + * - Default: Roboto Regular + merged FontAwesome icons + * - Bold: Roboto Bold (for headers and emphasis) + * - Monospace: JetBrains Mono (for console and code) + * - Large: Roboto at larger size (for titles) + */ +class EditorFonts { +public: + /** + * @brief Load all editor fonts. Must be called before ImGui backends initialize fonts. + * @param baseFontSize Base font size in pixels (default 15.0f) + */ + static void LoadFonts(float baseFontSize = 15.0f); + + /** @brief Get the default font (Roboto Regular + FontAwesome icons merged) */ + static ImFont* GetDefault() { return s_defaultFont; } + + /** @brief Get the bold font (for headers and panel titles) */ + static ImFont* GetBold() { return s_boldFont ? s_boldFont : s_defaultFont; } + + /** @brief Get the monospace font (for console, code, and logs) */ + static ImFont* GetMonospace() { return s_monoFont ? s_monoFont : s_defaultFont; } + + /** @brief Get the large font (for section headers and titles) */ + static ImFont* GetLarge() { return s_largeFont ? s_largeFont : s_defaultFont; } + + /** @brief Check if custom fonts were loaded successfully */ + static bool AreCustomFontsLoaded() { return s_customFontsLoaded; } + +private: + static ImFont* s_defaultFont; + static ImFont* s_boldFont; + static ImFont* s_monoFont; + static ImFont* s_largeFont; + static bool s_customFontsLoaded; +}; + +} // namespace SparkEditor diff --git a/SparkEditor/Source/Core/EditorIcons.h b/SparkEditor/Source/Core/EditorIcons.h new file mode 100644 index 000000000..a007bbc5a --- /dev/null +++ b/SparkEditor/Source/Core/EditorIcons.h @@ -0,0 +1,113 @@ +/** + * @file EditorIcons.h + * @brief FontAwesome 6 icon constants for the Spark Engine Editor + * @author Spark Engine Team + * @date 2025 + * + * Icon codepoints from FontAwesome 6 Free Solid (fa-solid-900.ttf). + * These map to UTF-8 encoded strings for use with ImGui text rendering. + */ + +#pragma once + +// FontAwesome 6 glyph range +#define ICON_FA_MIN 0xf000 +#define ICON_FA_MAX 0xf8ff + +// === Transport / Play Controls === +#define ICON_FA_PLAY "\xef\x81\x8b" // U+F04B +#define ICON_FA_PAUSE "\xef\x81\x8c" // U+F04C +#define ICON_FA_STOP "\xef\x81\x8d" // U+F04D +#define ICON_FA_FORWARD "\xef\x81\x8e" // U+F04E +#define ICON_FA_BACKWARD "\xef\x81\x8a" // U+F04A +#define ICON_FA_STEP_FORWARD "\xef\x81\x91" // U+F051 + +// === Transform Tools === +#define ICON_FA_ARROWS_ALT "\xef\x81\xb2" // U+F0B2 (move) +#define ICON_FA_SYNC_ALT "\xef\x80\xa1" // U+F021 (rotate) +#define ICON_FA_EXPAND_ALT "\xef\x81\xa4" // U+F064 (scale - expand) +#define ICON_FA_EXPAND "\xef\x81\xa5" // U+F065 + +// === Scene Objects === +#define ICON_FA_CUBE "\xef\x86\xb2" // U+F1B2 +#define ICON_FA_CAMERA "\xef\x80\xb0" // U+F030 +#define ICON_FA_LIGHTBULB "\xef\x83\xab" // U+F0EB +#define ICON_FA_SUN "\xef\x86\x85" // U+F185 +#define ICON_FA_GLOBE "\xef\x82\xac" // U+F0AC +#define ICON_FA_CROSSHAIRS "\xef\x81\x9b" // U+F05B +#define ICON_FA_USER "\xef\x80\x87" // U+F007 +#define ICON_FA_MOUNTAIN "\xef\x9b\xbc" // U+F6FC +#define ICON_FA_TREE "\xef\x86\xbb" // U+F1BB +#define ICON_FA_CIRCLE "\xef\x84\x91" // U+F111 +#define ICON_FA_VECTOR_SQUARE "\xef\x97\xab" // U+F5CB + +// === File System === +#define ICON_FA_FOLDER "\xef\x81\xbc" // U+F07C +#define ICON_FA_FOLDER_OPEN "\xef\x81\x96" // U+F076 (alt open folder) +#define ICON_FA_FILE "\xef\x85\x9b" // U+F15B +#define ICON_FA_FILE_CODE "\xef\x87\x89" // U+F1C9 +#define ICON_FA_FILE_IMAGE "\xef\x87\x85" // U+F1C5 +#define ICON_FA_FILE_AUDIO "\xef\x87\x87" // U+F1C7 +#define ICON_FA_IMAGE "\xef\x80\xbe" // U+F03E + +// === Actions === +#define ICON_FA_SAVE "\xef\x83\x87" // U+F0C7 +#define ICON_FA_UNDO "\xef\x83\xa2" // U+F0E2 +#define ICON_FA_REDO "\xef\x80\x9e" // U+F01E +#define ICON_FA_TRASH "\xef\x87\xb8" // U+F1F8 +#define ICON_FA_COPY "\xef\x83\x85" // U+F0C5 +#define ICON_FA_PLUS "\xef\x81\xa7" // U+F067 +#define ICON_FA_MINUS "\xef\x81\xa8" // U+F068 +#define ICON_FA_SEARCH "\xef\x80\x82" // U+F002 +#define ICON_FA_COG "\xef\x80\x93" // U+F013 +#define ICON_FA_COGS "\xef\x82\x85" // U+F085 +#define ICON_FA_DOWNLOAD "\xef\x80\x99" // U+F019 +#define ICON_FA_UPLOAD "\xef\x82\x93" // U+F093 + +// === View / Visibility === +#define ICON_FA_EYE "\xef\x81\xae" // U+F06E +#define ICON_FA_EYE_SLASH "\xef\x81\xb0" // U+F070 +#define ICON_FA_LOCK "\xef\x80\xa3" // U+F023 +#define ICON_FA_UNLOCK "\xef\x82\x9c" // U+F09C +#define ICON_FA_TH "\xef\x80\x8a" // U+F00A (grid) +#define ICON_FA_BORDER_ALL "\xef\xa1\x8c" // U+F84C + +// === Status / Feedback === +#define ICON_FA_CHECK "\xef\x80\x8c" // U+F00C +#define ICON_FA_TIMES "\xef\x80\x8d" // U+F00D +#define ICON_FA_EXCLAMATION "\xef\x84\xaa" // U+F12A +#define ICON_FA_INFO_CIRCLE "\xef\x81\x9a" // U+F05A +#define ICON_FA_BUG "\xef\x86\x88" // U+F188 +#define ICON_FA_BOLT "\xef\x83\xa7" // U+F0E7 (spark!) +#define ICON_FA_FIRE "\xef\x81\xad" // U+F06D + +// === Panels === +#define ICON_FA_SITEMAP "\xef\x83\xa8" // U+F0E8 (hierarchy) +#define ICON_FA_SLIDERS "\xef\x87\x9e" // U+F1DE (inspector/properties) +#define ICON_FA_TERMINAL "\xef\x84\xa0" // U+F120 (console) +#define ICON_FA_CHART_BAR "\xef\x82\x80" // U+F080 (profiler) +#define ICON_FA_PALETTE "\xef\x96\xbf" // U+F53F (material editor) +#define ICON_FA_MAP "\xef\x89\xb9" // U+F279 (terrain) +#define ICON_FA_GAMEPAD "\xef\x84\x9b" // U+F11B (game view) +#define ICON_FA_HAMMER "\xef\x9b\xa3" // U+F6E3 (build) + +// === FPS-Specific === +#define ICON_FA_BOMB "\xef\x87\xa2" // U+F1E2 (explosive) +#define ICON_FA_BULLSEYE "\xef\x85\x80" // U+F140 (target/objective) +#define ICON_FA_FLAG "\xef\x80\xa4" // U+F024 (spawn/flag) +#define ICON_FA_SHIELD "\xef\x84\xb2" // U+F132 (armor) +#define ICON_FA_ROCKET "\xef\x84\xb5" // U+F135 +#define ICON_FA_LOCATION_ARROW "\xef\x84\xa4" // U+F124 + +// === Arrows / Navigation === +#define ICON_FA_CHEVRON_RIGHT "\xef\x81\x94" // U+F054 +#define ICON_FA_CHEVRON_DOWN "\xef\x81\xb8" // U+F078 +#define ICON_FA_CHEVRON_LEFT "\xef\x81\x93" // U+F053 +#define ICON_FA_ANGLE_RIGHT "\xef\x84\x85" // U+F105 +#define ICON_FA_ANGLE_DOWN "\xef\x84\x87" // U+F107 +#define ICON_FA_BARS "\xef\x83\x89" // U+F0C9 (hamburger menu) + +// === Layout / Window === +#define ICON_FA_COLUMNS "\xef\x83\x9b" // U+F0DB +#define ICON_FA_WINDOW_MAXIMIZE "\xef\x8b\x90" // U+F2D0 +#define ICON_FA_COMPRESS "\xef\x81\xa6" // U+F066 diff --git a/SparkEditor/Source/Core/EditorPanel.cpp b/SparkEditor/Source/Core/EditorPanel.cpp index 682d249c8..9520e61fd 100644 --- a/SparkEditor/Source/Core/EditorPanel.cpp +++ b/SparkEditor/Source/Core/EditorPanel.cpp @@ -28,8 +28,16 @@ bool EditorPanel::BeginPanel() flags |= ImGuiWindowFlags_NoCollapse; } + // Compose display title with icon, but use stable ###ID for docking + std::string displayTitle; + if (m_icon.empty()) { + displayTitle = m_title + "###" + m_id; + } else { + displayTitle = m_icon + " " + m_title + "###" + m_id; + } + bool visible = m_isVisible; - bool result = ImGui::Begin(m_title.c_str(), m_isClosable ? &visible : nullptr, flags); + bool result = ImGui::Begin(displayTitle.c_str(), m_isClosable ? &visible : nullptr, flags); if (visible != m_isVisible) { SetVisible(visible); diff --git a/SparkEditor/Source/Core/EditorPanel.h b/SparkEditor/Source/Core/EditorPanel.h index 349919c24..651145e28 100644 --- a/SparkEditor/Source/Core/EditorPanel.h +++ b/SparkEditor/Source/Core/EditorPanel.h @@ -250,8 +250,17 @@ class EditorPanel { bool m_isModified = false; ///< Panel modification state bool m_visibleInMenu = true; ///< Visibility in view menu - + + std::string m_icon; ///< FontAwesome icon for panel header + std::function m_stateChangeCallback; ///< State change callback + +public: + /** @brief Set icon for the panel title bar */ + void SetIcon(const std::string& icon) { m_icon = icon; } + + /** @brief Get icon string */ + const std::string& GetIcon() const { return m_icon; } }; } // namespace SparkEditor \ No newline at end of file diff --git a/SparkEditor/Source/Core/EditorTheme.cpp b/SparkEditor/Source/Core/EditorTheme.cpp new file mode 100644 index 000000000..6fd3f713d --- /dev/null +++ b/SparkEditor/Source/Core/EditorTheme.cpp @@ -0,0 +1,920 @@ +/** + * @file EditorTheme.cpp + * @brief Theme system implementation for the Spark Engine Editor + * @author Spark Engine Team + * @date 2025 + */ + +#include "EditorTheme.h" +#include +#include +#include +#include +#include + +namespace SparkEditor { + +// Static member initialization +std::unordered_map EditorTheme::s_registeredThemes; +std::string EditorTheme::s_currentThemeName; +bool EditorTheme::s_enhancementsEnabled = false; +bool EditorTheme::s_customFontsLoaded = false; + +// =================================================================== +// ThemeColor implementations +// =================================================================== + +ImVec4 ThemeColor::ToImVec4() const { + return ImVec4(r, g, b, a); +} + +ThemeColor ThemeColor::FromRGB(int red, int green, int blue, int alpha) { + return ThemeColor(red / 255.0f, green / 255.0f, blue / 255.0f, alpha / 255.0f); +} + +ThemeColor ThemeColor::FromHex(const std::string& hex) { + std::string h = hex; + if (!h.empty() && h[0] == '#') h = h.substr(1); + + unsigned int val = 0; + std::istringstream iss(h); + iss >> std::hex >> val; + + if (h.length() == 8) { + return ThemeColor( + ((val >> 24) & 0xFF) / 255.0f, + ((val >> 16) & 0xFF) / 255.0f, + ((val >> 8) & 0xFF) / 255.0f, + (val & 0xFF) / 255.0f); + } + return ThemeColor( + ((val >> 16) & 0xFF) / 255.0f, + ((val >> 8) & 0xFF) / 255.0f, + (val & 0xFF) / 255.0f, + 1.0f); +} + +ThemeColor ThemeColor::Lerp(const ThemeColor& other, float t) const { + t = std::clamp(t, 0.0f, 1.0f); + return ThemeColor( + r + (other.r - r) * t, + g + (other.g - g) * t, + b + (other.b - b) * t, + a + (other.a - a) * t); +} + +ThemeColor ThemeColor::Darken(float amount) const { + float factor = 1.0f - std::clamp(amount, 0.0f, 1.0f); + return ThemeColor(r * factor, g * factor, b * factor, a); +} + +ThemeColor ThemeColor::Lighten(float amount) const { + float factor = std::clamp(amount, 0.0f, 1.0f); + return ThemeColor( + r + (1.0f - r) * factor, + g + (1.0f - g) * factor, + b + (1.0f - b) * factor, a); +} + +ThemeColor ThemeColor::Desaturate(float amount) const { + float gray = r * 0.299f + g * 0.587f + b * 0.114f; + float t = std::clamp(amount, 0.0f, 1.0f); + return ThemeColor(r + (gray - r) * t, g + (gray - g) * t, b + (gray - b) * t, a); +} + +ThemeColor ThemeColor::WithAlpha(float alpha) const { + return ThemeColor(r, g, b, alpha); +} + +// =================================================================== +// EditorTheme implementations +// =================================================================== + +bool EditorTheme::ApplyTheme(const std::string& themeName) { + if (s_registeredThemes.empty()) { + InitializeDefaultThemes(); + } + + auto it = s_registeredThemes.find(themeName); + if (it == s_registeredThemes.end()) { + std::cout << "[EditorTheme] Theme '" << themeName << "' not found, applying Spark Professional\n"; + it = s_registeredThemes.find("Spark Professional"); + if (it == s_registeredThemes.end()) return false; + } + + ApplyToImGui(it->second); + s_currentThemeName = it->first; + std::cout << "[EditorTheme] Applied theme: " << s_currentThemeName << "\n"; + return true; +} + +bool EditorTheme::ApplyTheme(const EditorThemeData& theme) { + ApplyToImGui(theme); + s_currentThemeName = theme.name; + return true; +} + +std::vector EditorTheme::GetAvailableThemes() { + if (s_registeredThemes.empty()) InitializeDefaultThemes(); + std::vector names; + names.reserve(s_registeredThemes.size()); + for (const auto& [name, _] : s_registeredThemes) { + names.push_back(name); + } + return names; +} + +const EditorThemeData* EditorTheme::GetTheme(const std::string& themeName) { + if (s_registeredThemes.empty()) InitializeDefaultThemes(); + auto it = s_registeredThemes.find(themeName); + return (it != s_registeredThemes.end()) ? &it->second : nullptr; +} + +bool EditorTheme::RegisterTheme(const EditorThemeData& theme) { + s_registeredThemes[theme.name] = theme; + return true; +} + +const std::string& EditorTheme::GetCurrentThemeName() { + return s_currentThemeName; +} + +bool EditorTheme::CreateBlendedTheme(const std::string& theme1, const std::string& theme2, + float blend, const std::string& resultName) { + auto* t1 = GetTheme(theme1); + auto* t2 = GetTheme(theme2); + if (!t1 || !t2) return false; + + EditorThemeData blended; + blended.name = resultName; + blended.background = t1->background.Lerp(t2->background, blend); + blended.text = t1->text.Lerp(t2->text, blend); + blended.accent = t1->accent.Lerp(t2->accent, blend); + // Simplified — only blend key colors + RegisterTheme(blended); + return true; +} + +void EditorTheme::ApplyProfessionalEnhancements() { + s_enhancementsEnabled = true; +} + +void EditorTheme::ApplyCustomFonts() { + s_customFontsLoaded = true; +} + +// =================================================================== +// Core theme application — maps EditorThemeData to ImGui colors +// =================================================================== + +void EditorTheme::ApplyToImGui(const EditorThemeData& theme) { + ImGuiStyle& style = ImGui::GetStyle(); + + // --- Style values --- + style.WindowRounding = theme.windowRounding; + style.ChildRounding = theme.childRounding; + style.FrameRounding = theme.frameRounding; + style.PopupRounding = theme.popupRounding; + style.ScrollbarRounding = theme.scrollbarRounding; + style.GrabRounding = theme.grabRounding; + style.TabRounding = theme.tabRounding; + + style.WindowBorderSize = theme.windowBorderSize; + style.ChildBorderSize = theme.childBorderSize; + style.PopupBorderSize = theme.popupBorderSize; + style.FrameBorderSize = theme.frameBorderSize; + + style.IndentSpacing = theme.indentSpacing; + style.ScrollbarSize = theme.scrollbarSize; + style.GrabMinSize = theme.grabMinSize; + + style.WindowPadding = ImVec2(theme.windowPaddingX, theme.windowPaddingY); + style.FramePadding = ImVec2(theme.framePaddingX, theme.framePaddingY); + style.ItemSpacing = ImVec2(theme.itemSpacingX, theme.itemSpacingY); + style.ItemInnerSpacing = ImVec2(theme.itemInnerSpacingX, theme.itemInnerSpacingY); + + // --- Color mapping --- + ImVec4* c = style.Colors; + + // Window / Background + c[ImGuiCol_WindowBg] = theme.background.ToImVec4(); + c[ImGuiCol_ChildBg] = theme.backgroundDark.ToImVec4(); + c[ImGuiCol_PopupBg] = theme.backgroundLight.WithAlpha(0.95f).ToImVec4(); + + // Text + c[ImGuiCol_Text] = theme.text.ToImVec4(); + c[ImGuiCol_TextDisabled] = theme.textDisabled.ToImVec4(); + + // Borders + c[ImGuiCol_Border] = theme.border.ToImVec4(); + c[ImGuiCol_BorderShadow] = ThemeColor(0, 0, 0, 0).ToImVec4(); + + // Frame (input fields, checkboxes, etc.) + c[ImGuiCol_FrameBg] = theme.frame.ToImVec4(); + c[ImGuiCol_FrameBgHovered] = theme.frameHovered.ToImVec4(); + c[ImGuiCol_FrameBgActive] = theme.frameActive.ToImVec4(); + + // Title bar + c[ImGuiCol_TitleBg] = theme.titleBar.ToImVec4(); + c[ImGuiCol_TitleBgActive] = theme.titleBarActive.ToImVec4(); + c[ImGuiCol_TitleBgCollapsed] = theme.titleBar.WithAlpha(0.6f).ToImVec4(); + + // Menu bar + c[ImGuiCol_MenuBarBg] = theme.menuBar.ToImVec4(); + + // Scrollbar + c[ImGuiCol_ScrollbarBg] = theme.scrollbar.ToImVec4(); + c[ImGuiCol_ScrollbarGrab] = theme.scrollbarGrab.ToImVec4(); + c[ImGuiCol_ScrollbarGrabHovered] = theme.scrollbarGrabHovered.ToImVec4(); + c[ImGuiCol_ScrollbarGrabActive] = theme.scrollbarGrabActive.ToImVec4(); + + // Checkbox / Radio + c[ImGuiCol_CheckMark] = theme.accent.ToImVec4(); + + // Slider + c[ImGuiCol_SliderGrab] = theme.accent.WithAlpha(0.8f).ToImVec4(); + c[ImGuiCol_SliderGrabActive] = theme.accent.ToImVec4(); + + // Button + c[ImGuiCol_Button] = theme.button.ToImVec4(); + c[ImGuiCol_ButtonHovered] = theme.buttonHovered.ToImVec4(); + c[ImGuiCol_ButtonActive] = theme.buttonActive.ToImVec4(); + + // Header (collapsing headers, tree nodes, selectables) + c[ImGuiCol_Header] = theme.backgroundHeader.ToImVec4(); + c[ImGuiCol_HeaderHovered] = theme.backgroundHover.ToImVec4(); + c[ImGuiCol_HeaderActive] = theme.backgroundActive.ToImVec4(); + + // Separator + c[ImGuiCol_Separator] = theme.borderSeparator.ToImVec4(); + c[ImGuiCol_SeparatorHovered] = theme.accent.WithAlpha(0.6f).ToImVec4(); + c[ImGuiCol_SeparatorActive] = theme.accent.ToImVec4(); + + // Resize grip + c[ImGuiCol_ResizeGrip] = theme.accent.WithAlpha(0.2f).ToImVec4(); + c[ImGuiCol_ResizeGripHovered] = theme.accent.WithAlpha(0.5f).ToImVec4(); + c[ImGuiCol_ResizeGripActive] = theme.accent.WithAlpha(0.8f).ToImVec4(); + + // Tabs + c[ImGuiCol_Tab] = theme.tab.ToImVec4(); + c[ImGuiCol_TabHovered] = theme.tabHovered.ToImVec4(); + c[ImGuiCol_TabSelected] = theme.tabActive.ToImVec4(); + c[ImGuiCol_TabDimmed] = theme.tabUnfocused.ToImVec4(); + c[ImGuiCol_TabDimmedSelected] = theme.tabActive.Darken(0.3f).ToImVec4(); + + // Docking + c[ImGuiCol_DockingPreview] = theme.accent.WithAlpha(0.7f).ToImVec4(); + c[ImGuiCol_DockingEmptyBg] = theme.backgroundDark.ToImVec4(); + + // Plot + c[ImGuiCol_PlotLines] = theme.graph1.ToImVec4(); + c[ImGuiCol_PlotLinesHovered] = theme.accent.ToImVec4(); + c[ImGuiCol_PlotHistogram] = theme.graph2.ToImVec4(); + c[ImGuiCol_PlotHistogramHovered] = theme.accentSecondary.ToImVec4(); + + // Tables + c[ImGuiCol_TableHeaderBg] = theme.backgroundHeader.ToImVec4(); + c[ImGuiCol_TableBorderStrong] = theme.border.ToImVec4(); + c[ImGuiCol_TableBorderLight] = theme.borderLight.ToImVec4(); + c[ImGuiCol_TableRowBg] = ThemeColor(0, 0, 0, 0).ToImVec4(); + c[ImGuiCol_TableRowBgAlt] = theme.backgroundDark.WithAlpha(0.3f).ToImVec4(); + + // Text selection + c[ImGuiCol_TextSelectedBg] = theme.selection.ToImVec4(); + + // Drag / Drop + c[ImGuiCol_DragDropTarget] = theme.drop.ToImVec4(); + + // Nav highlight + c[ImGuiCol_NavHighlight] = theme.focus.ToImVec4(); + c[ImGuiCol_NavWindowingHighlight] = theme.accent.WithAlpha(0.7f).ToImVec4(); + c[ImGuiCol_NavWindowingDimBg] = ThemeColor(0.2f, 0.2f, 0.2f, 0.2f).ToImVec4(); + + // Modal dimming + c[ImGuiCol_ModalWindowDimBg] = ThemeColor(0.0f, 0.0f, 0.0f, 0.5f).ToImVec4(); +} + +// =================================================================== +// Predefined Themes +// =================================================================== + +void EditorTheme::InitializeDefaultThemes() { + RegisterTheme(CreateSparkTheme()); + RegisterTheme(CreateUnityProTheme()); + RegisterTheme(CreateUnrealProTheme()); + RegisterTheme(CreateVSProTheme()); + RegisterTheme(CreateJetBrainsTheme()); + RegisterTheme(CreateProfessionalLightTheme()); + RegisterTheme(CreateHighContrastTheme()); + RegisterTheme(CreateBlueAccentTheme()); + RegisterTheme(CreateOrangeAccentTheme()); +} + +// ------------------------------------------------------------------- +// SPARK PROFESSIONAL — Signature theme +// Dark blue-gray base, electric blue accent, orange-amber secondary +// Military-tech aesthetic for FPS game engine +// ------------------------------------------------------------------- +static EditorThemeData CreateSparkThemeImpl() { + EditorThemeData t; + t.name = "Spark Professional"; + t.description = "Signature dark theme with electric blue and orange-amber accents"; + t.author = "Spark Engine Team"; + + // Backgrounds + t.background = ThemeColor::FromHex("#1A1D23"); + t.backgroundDark = ThemeColor::FromHex("#141619"); + t.backgroundLight = ThemeColor::FromHex("#22262E"); + t.backgroundAccent = ThemeColor::FromHex("#1E3A5F"); + t.backgroundHeader = ThemeColor::FromHex("#1E2128"); + t.backgroundActive = ThemeColor::FromHex("#2D8CF0").WithAlpha(0.3f); + t.backgroundHover = ThemeColor::FromHex("#2A2E36"); + t.backgroundSelected = ThemeColor::FromHex("#2D8CF0").WithAlpha(0.2f); + + // Text + t.text = ThemeColor::FromHex("#E0E4EA"); + t.textDisabled = ThemeColor::FromHex("#555B66"); + t.textSecondary = ThemeColor::FromHex("#8B929E"); + t.textAccent = ThemeColor::FromHex("#2D8CF0"); + t.textWarning = ThemeColor::FromHex("#F5A623"); + t.textError = ThemeColor::FromHex("#E53935"); + t.textSuccess = ThemeColor::FromHex("#4CAF50"); + + // Buttons + t.button = ThemeColor::FromHex("#2A2E36"); + t.buttonHovered = ThemeColor::FromHex("#353A44"); + t.buttonActive = ThemeColor::FromHex("#2D8CF0"); + t.buttonDisabled = ThemeColor::FromHex("#22262E"); + + // Frames (input fields) + t.frame = ThemeColor::FromHex("#1E2128"); + t.frameHovered = ThemeColor::FromHex("#262B33"); + t.frameActive = ThemeColor::FromHex("#2D8CF0").WithAlpha(0.4f); + + // Borders + t.border = ThemeColor::FromHex("#2A2E36"); + t.borderLight = ThemeColor::FromHex("#333842"); + t.borderAccent = ThemeColor::FromHex("#2D8CF0"); + t.borderSeparator = ThemeColor::FromHex("#262B33"); + + // Title bar + t.titleBar = ThemeColor::FromHex("#141619"); + t.titleBarActive = ThemeColor::FromHex("#1A3A5F"); + t.titleBarText = ThemeColor::FromHex("#E0E4EA"); + + // Menu bar + t.menuBar = ThemeColor::FromHex("#1A1D23"); + t.menuItem = ThemeColor(0, 0, 0, 0); + t.menuItemHovered = ThemeColor::FromHex("#2D8CF0").WithAlpha(0.5f); + + // Scrollbar + t.scrollbar = ThemeColor::FromHex("#141619"); + t.scrollbarGrab = ThemeColor::FromHex("#3A3F48"); + t.scrollbarGrabHovered = ThemeColor::FromHex("#4A4F58"); + t.scrollbarGrabActive = ThemeColor::FromHex("#2D8CF0"); + + // Tabs + t.tab = ThemeColor::FromHex("#1A1D23"); + t.tabHovered = ThemeColor::FromHex("#2D8CF0").WithAlpha(0.4f); + t.tabActive = ThemeColor::FromHex("#2D8CF0"); + t.tabUnfocused = ThemeColor::FromHex("#141619"); + + // Accent colors + t.accent = ThemeColor::FromHex("#2D8CF0"); // Electric blue + t.accentSecondary = ThemeColor::FromHex("#F5A623"); // Orange-amber + t.focus = ThemeColor::FromHex("#2D8CF0"); + t.selection = ThemeColor::FromHex("#2D8CF0").WithAlpha(0.3f); + t.drop = ThemeColor::FromHex("#F5A623").WithAlpha(0.8f); + + // Graph colors + t.graph1 = ThemeColor::FromHex("#2D8CF0"); + t.graph2 = ThemeColor::FromHex("#F5A623"); + t.graph3 = ThemeColor::FromHex("#4CAF50"); + t.graph4 = ThemeColor::FromHex("#E53935"); + t.graph5 = ThemeColor::FromHex("#9C27B0"); + + // Style values + t.windowRounding = 2.0f; + t.childRounding = 2.0f; + t.frameRounding = 2.0f; + t.popupRounding = 3.0f; + t.scrollbarRounding = 12.0f; + t.grabRounding = 2.0f; + t.tabRounding = 3.0f; + + t.windowBorderSize = 1.0f; + t.childBorderSize = 1.0f; + t.popupBorderSize = 1.0f; + t.frameBorderSize = 1.0f; + + t.windowPaddingX = 8.0f; + t.windowPaddingY = 8.0f; + t.framePaddingX = 6.0f; + t.framePaddingY = 4.0f; + t.itemSpacingX = 8.0f; + t.itemSpacingY = 4.0f; + t.itemInnerSpacingX = 4.0f; + t.itemInnerSpacingY = 4.0f; + + t.indentSpacing = 21.0f; + t.scrollbarSize = 14.0f; + t.grabMinSize = 10.0f; + + t.fontSize = 15.0f; + t.fontScale = 1.0f; + t.fontFamily = "Roboto"; + + return t; +} + +EditorThemeData EditorTheme::CreateUnityProTheme() { + EditorThemeData t; + t.name = "Unity Pro"; + t.description = "Unity-inspired professional dark theme"; + t.author = "Spark Engine Team"; + + t.background = ThemeColor::FromRGB(56, 56, 56); + t.backgroundDark = ThemeColor::FromRGB(48, 48, 48); + t.backgroundLight = ThemeColor::FromRGB(70, 70, 70); + t.backgroundAccent = ThemeColor::FromRGB(62, 95, 150); + t.backgroundHeader = ThemeColor::FromRGB(60, 60, 60); + t.backgroundActive = ThemeColor::FromRGB(62, 95, 150, 128); + t.backgroundHover = ThemeColor::FromRGB(75, 75, 75); + t.backgroundSelected = ThemeColor::FromRGB(62, 95, 150, 100); + + t.text = ThemeColor::FromRGB(210, 210, 210); + t.textDisabled = ThemeColor::FromRGB(128, 128, 128); + t.textSecondary = ThemeColor::FromRGB(170, 170, 170); + t.textAccent = ThemeColor::FromRGB(62, 125, 200); + t.textWarning = ThemeColor::FromRGB(230, 180, 50); + t.textError = ThemeColor::FromRGB(200, 60, 60); + t.textSuccess = ThemeColor::FromRGB(60, 180, 80); + + t.button = ThemeColor::FromRGB(72, 72, 72); + t.buttonHovered = ThemeColor::FromRGB(90, 90, 90); + t.buttonActive = ThemeColor::FromRGB(62, 95, 150); + t.buttonDisabled = ThemeColor::FromRGB(56, 56, 56); + + t.frame = ThemeColor::FromRGB(42, 42, 42); + t.frameHovered = ThemeColor::FromRGB(52, 52, 52); + t.frameActive = ThemeColor::FromRGB(62, 95, 150, 120); + + t.border = ThemeColor::FromRGB(35, 35, 35); + t.borderLight = ThemeColor::FromRGB(65, 65, 65); + t.borderAccent = ThemeColor::FromRGB(62, 95, 150); + t.borderSeparator = ThemeColor::FromRGB(40, 40, 40); + + t.titleBar = ThemeColor::FromRGB(48, 48, 48); + t.titleBarActive = ThemeColor::FromRGB(50, 50, 50); + t.titleBarText = ThemeColor::FromRGB(200, 200, 200); + + t.menuBar = ThemeColor::FromRGB(56, 56, 56); + t.menuItem = ThemeColor(0, 0, 0, 0); + t.menuItemHovered = ThemeColor::FromRGB(62, 95, 150, 180); + + t.scrollbar = ThemeColor::FromRGB(40, 40, 40); + t.scrollbarGrab = ThemeColor::FromRGB(80, 80, 80); + t.scrollbarGrabHovered = ThemeColor::FromRGB(100, 100, 100); + t.scrollbarGrabActive = ThemeColor::FromRGB(120, 120, 120); + + t.tab = ThemeColor::FromRGB(48, 48, 48); + t.tabHovered = ThemeColor::FromRGB(70, 70, 70); + t.tabActive = ThemeColor::FromRGB(56, 56, 56); + t.tabUnfocused = ThemeColor::FromRGB(42, 42, 42); + + t.accent = ThemeColor::FromRGB(62, 125, 200); + t.accentSecondary = ThemeColor::FromRGB(62, 95, 150); + t.focus = ThemeColor::FromRGB(62, 125, 200); + t.selection = ThemeColor::FromRGB(62, 95, 150, 100); + t.drop = ThemeColor::FromRGB(62, 125, 200, 200); + + t.graph1 = ThemeColor::FromRGB(62, 125, 200); + t.graph2 = ThemeColor::FromRGB(230, 180, 50); + t.graph3 = ThemeColor::FromRGB(60, 180, 80); + t.graph4 = ThemeColor::FromRGB(200, 60, 60); + t.graph5 = ThemeColor::FromRGB(150, 80, 200); + + t.windowRounding = 0.0f; + t.frameRounding = 2.0f; + t.tabRounding = 2.0f; + t.frameBorderSize = 0.0f; + + return t; +} + +EditorThemeData EditorTheme::CreateUnrealProTheme() { + EditorThemeData t; + t.name = "Unreal Pro"; + t.description = "Unreal Engine-inspired dark theme"; + t.author = "Spark Engine Team"; + + t.background = ThemeColor::FromRGB(36, 36, 36); + t.backgroundDark = ThemeColor::FromRGB(24, 24, 24); + t.backgroundLight = ThemeColor::FromRGB(50, 50, 50); + t.backgroundAccent = ThemeColor::FromRGB(0, 90, 180); + t.backgroundHeader = ThemeColor::FromRGB(30, 30, 30); + t.backgroundActive = ThemeColor::FromRGB(0, 90, 180, 128); + t.backgroundHover = ThemeColor::FromRGB(55, 55, 55); + t.backgroundSelected = ThemeColor::FromRGB(0, 90, 180, 80); + + t.text = ThemeColor::FromRGB(220, 220, 220); + t.textDisabled = ThemeColor::FromRGB(100, 100, 100); + t.textSecondary = ThemeColor::FromRGB(170, 170, 170); + t.textAccent = ThemeColor::FromRGB(0, 140, 220); + t.textWarning = ThemeColor::FromRGB(240, 200, 50); + t.textError = ThemeColor::FromRGB(220, 50, 50); + t.textSuccess = ThemeColor::FromRGB(50, 200, 70); + + t.button = ThemeColor::FromRGB(50, 50, 50); + t.buttonHovered = ThemeColor::FromRGB(65, 65, 65); + t.buttonActive = ThemeColor::FromRGB(0, 90, 180); + t.buttonDisabled = ThemeColor::FromRGB(40, 40, 40); + + t.frame = ThemeColor::FromRGB(20, 20, 20); + t.frameHovered = ThemeColor::FromRGB(30, 30, 30); + t.frameActive = ThemeColor::FromRGB(0, 90, 180, 120); + + t.border = ThemeColor::FromRGB(10, 10, 10); + t.borderLight = ThemeColor::FromRGB(50, 50, 50); + t.borderAccent = ThemeColor::FromRGB(0, 90, 180); + t.borderSeparator = ThemeColor::FromRGB(15, 15, 15); + + t.titleBar = ThemeColor::FromRGB(24, 24, 24); + t.titleBarActive = ThemeColor::FromRGB(0, 60, 120); + t.titleBarText = ThemeColor::FromRGB(200, 200, 200); + + t.menuBar = ThemeColor::FromRGB(36, 36, 36); + t.menuItem = ThemeColor(0, 0, 0, 0); + t.menuItemHovered = ThemeColor::FromRGB(0, 90, 180, 180); + + t.scrollbar = ThemeColor::FromRGB(20, 20, 20); + t.scrollbarGrab = ThemeColor::FromRGB(60, 60, 60); + t.scrollbarGrabHovered = ThemeColor::FromRGB(80, 80, 80); + t.scrollbarGrabActive = ThemeColor::FromRGB(100, 100, 100); + + t.tab = ThemeColor::FromRGB(24, 24, 24); + t.tabHovered = ThemeColor::FromRGB(50, 50, 50); + t.tabActive = ThemeColor::FromRGB(36, 36, 36); + t.tabUnfocused = ThemeColor::FromRGB(18, 18, 18); + + t.accent = ThemeColor::FromRGB(0, 120, 215); + t.accentSecondary = ThemeColor::FromRGB(0, 90, 180); + t.focus = ThemeColor::FromRGB(0, 120, 215); + t.selection = ThemeColor::FromRGB(0, 90, 180, 100); + t.drop = ThemeColor::FromRGB(0, 120, 215, 200); + + t.graph1 = ThemeColor::FromRGB(0, 120, 215); + t.graph2 = ThemeColor::FromRGB(240, 200, 50); + t.graph3 = ThemeColor::FromRGB(50, 200, 70); + t.graph4 = ThemeColor::FromRGB(220, 50, 50); + t.graph5 = ThemeColor::FromRGB(140, 70, 210); + + t.windowRounding = 0.0f; + t.frameRounding = 1.0f; + t.tabRounding = 0.0f; + t.frameBorderSize = 1.0f; + + return t; +} + +EditorThemeData EditorTheme::CreateVSProTheme() { + EditorThemeData t; + t.name = "VS Pro"; + t.description = "Visual Studio-inspired dark theme"; + + t.background = ThemeColor::FromRGB(30, 30, 30); + t.backgroundDark = ThemeColor::FromRGB(25, 25, 25); + t.backgroundLight = ThemeColor::FromRGB(45, 45, 48); + t.backgroundAccent = ThemeColor::FromRGB(0, 122, 204); + t.backgroundHeader = ThemeColor::FromRGB(37, 37, 38); + t.backgroundActive = ThemeColor::FromRGB(0, 122, 204, 120); + t.backgroundHover = ThemeColor::FromRGB(51, 51, 52); + t.backgroundSelected = ThemeColor::FromRGB(0, 122, 204, 80); + + t.text = ThemeColor::FromRGB(220, 220, 220); + t.textDisabled = ThemeColor::FromRGB(110, 110, 110); + t.textSecondary = ThemeColor::FromRGB(160, 160, 160); + t.textAccent = ThemeColor::FromRGB(0, 122, 204); + t.textWarning = ThemeColor::FromRGB(230, 180, 50); + t.textError = ThemeColor::FromRGB(210, 50, 50); + t.textSuccess = ThemeColor::FromRGB(50, 180, 70); + + t.button = ThemeColor::FromRGB(51, 51, 55); + t.buttonHovered = ThemeColor::FromRGB(63, 63, 70); + t.buttonActive = ThemeColor::FromRGB(0, 122, 204); + t.buttonDisabled = ThemeColor::FromRGB(40, 40, 42); + + t.frame = ThemeColor::FromRGB(51, 51, 55); + t.frameHovered = ThemeColor::FromRGB(63, 63, 70); + t.frameActive = ThemeColor::FromRGB(0, 122, 204, 120); + + t.border = ThemeColor::FromRGB(45, 45, 48); + t.borderLight = ThemeColor::FromRGB(63, 63, 70); + t.borderAccent = ThemeColor::FromRGB(0, 122, 204); + t.borderSeparator = ThemeColor::FromRGB(45, 45, 48); + + t.titleBar = ThemeColor::FromRGB(45, 45, 48); + t.titleBarActive = ThemeColor::FromRGB(0, 122, 204); + t.titleBarText = ThemeColor::FromRGB(220, 220, 220); + t.menuBar = ThemeColor::FromRGB(45, 45, 48); + t.menuItem = ThemeColor(0, 0, 0, 0); + t.menuItemHovered = ThemeColor::FromRGB(0, 122, 204, 180); + + t.scrollbar = ThemeColor::FromRGB(30, 30, 30); + t.scrollbarGrab = ThemeColor::FromRGB(80, 80, 80); + t.scrollbarGrabHovered = ThemeColor::FromRGB(100, 100, 100); + t.scrollbarGrabActive = ThemeColor::FromRGB(120, 120, 120); + + t.tab = ThemeColor::FromRGB(45, 45, 48); + t.tabHovered = ThemeColor::FromRGB(28, 151, 234); + t.tabActive = ThemeColor::FromRGB(0, 122, 204); + t.tabUnfocused = ThemeColor::FromRGB(37, 37, 38); + + t.accent = ThemeColor::FromRGB(0, 122, 204); + t.accentSecondary = ThemeColor::FromRGB(28, 151, 234); + t.focus = ThemeColor::FromRGB(0, 122, 204); + t.selection = ThemeColor::FromRGB(0, 122, 204, 100); + t.drop = ThemeColor::FromRGB(0, 122, 204, 200); + t.graph1 = t.accent; t.graph2 = t.accentSecondary; + t.graph3 = t.textSuccess; t.graph4 = t.textError; t.graph5 = ThemeColor::FromRGB(140, 70, 210); + + t.windowRounding = 0.0f; + t.frameRounding = 0.0f; + t.tabRounding = 0.0f; + t.frameBorderSize = 1.0f; + return t; +} + +EditorThemeData EditorTheme::CreateJetBrainsTheme() { + EditorThemeData t; + t.name = "JetBrains"; + t.description = "JetBrains-inspired dark theme"; + + t.background = ThemeColor::FromRGB(43, 43, 43); + t.backgroundDark = ThemeColor::FromRGB(30, 30, 30); + t.backgroundLight = ThemeColor::FromRGB(60, 63, 65); + t.backgroundAccent = ThemeColor::FromRGB(75, 110, 175); + t.backgroundHeader = ThemeColor::FromRGB(49, 51, 53); + t.backgroundActive = ThemeColor::FromRGB(75, 110, 175, 128); + t.backgroundHover = ThemeColor::FromRGB(69, 73, 74); + t.backgroundSelected = ThemeColor::FromRGB(75, 110, 175, 80); + + t.text = ThemeColor::FromRGB(187, 187, 187); + t.textDisabled = ThemeColor::FromRGB(100, 100, 100); + t.textSecondary = ThemeColor::FromRGB(150, 150, 150); + t.textAccent = ThemeColor::FromRGB(104, 151, 187); + t.textWarning = ThemeColor::FromRGB(187, 181, 41); + t.textError = ThemeColor::FromRGB(188, 63, 60); + t.textSuccess = ThemeColor::FromRGB(106, 135, 89); + + t.button = ThemeColor::FromRGB(60, 63, 65); + t.buttonHovered = ThemeColor::FromRGB(75, 78, 80); + t.buttonActive = ThemeColor::FromRGB(75, 110, 175); + t.buttonDisabled = ThemeColor::FromRGB(50, 52, 54); + t.frame = ThemeColor::FromRGB(69, 73, 74); + t.frameHovered = ThemeColor::FromRGB(80, 84, 85); + t.frameActive = ThemeColor::FromRGB(75, 110, 175, 120); + t.border = ThemeColor::FromRGB(50, 50, 50); + t.borderLight = ThemeColor::FromRGB(70, 70, 70); + t.borderAccent = ThemeColor::FromRGB(75, 110, 175); + t.borderSeparator = ThemeColor::FromRGB(50, 50, 50); + t.titleBar = ThemeColor::FromRGB(60, 63, 65); + t.titleBarActive = ThemeColor::FromRGB(75, 110, 175); + t.titleBarText = ThemeColor::FromRGB(187, 187, 187); + t.menuBar = ThemeColor::FromRGB(43, 43, 43); + t.menuItem = ThemeColor(0, 0, 0, 0); + t.menuItemHovered = ThemeColor::FromRGB(75, 110, 175, 180); + t.scrollbar = ThemeColor::FromRGB(43, 43, 43); + t.scrollbarGrab = ThemeColor::FromRGB(80, 80, 80); + t.scrollbarGrabHovered = ThemeColor::FromRGB(100, 100, 100); + t.scrollbarGrabActive = ThemeColor::FromRGB(120, 120, 120); + t.tab = ThemeColor::FromRGB(43, 43, 43); + t.tabHovered = ThemeColor::FromRGB(60, 63, 65); + t.tabActive = ThemeColor::FromRGB(49, 51, 53); + t.tabUnfocused = ThemeColor::FromRGB(38, 38, 38); + t.accent = ThemeColor::FromRGB(75, 110, 175); + t.accentSecondary = ThemeColor::FromRGB(104, 151, 187); + t.focus = ThemeColor::FromRGB(75, 110, 175); + t.selection = ThemeColor::FromRGB(33, 66, 131, 120); + t.drop = ThemeColor::FromRGB(75, 110, 175, 200); + t.graph1 = t.accent; t.graph2 = t.textWarning; + t.graph3 = t.textSuccess; t.graph4 = t.textError; t.graph5 = ThemeColor::FromRGB(140, 70, 210); + + t.windowRounding = 4.0f; + t.frameRounding = 3.0f; + t.tabRounding = 4.0f; + t.frameBorderSize = 0.0f; + return t; +} + +EditorThemeData EditorTheme::CreateProfessionalLightTheme() { + EditorThemeData t; + t.name = "Professional Light"; + t.description = "Clean, professional light theme"; + + t.background = ThemeColor::FromRGB(240, 240, 240); + t.backgroundDark = ThemeColor::FromRGB(230, 230, 230); + t.backgroundLight = ThemeColor::FromRGB(255, 255, 255); + t.backgroundAccent = ThemeColor::FromRGB(0, 120, 215); + t.backgroundHeader = ThemeColor::FromRGB(235, 235, 235); + t.backgroundActive = ThemeColor::FromRGB(0, 120, 215, 50); + t.backgroundHover = ThemeColor::FromRGB(225, 225, 225); + t.backgroundSelected = ThemeColor::FromRGB(0, 120, 215, 30); + + t.text = ThemeColor::FromRGB(30, 30, 30); + t.textDisabled = ThemeColor::FromRGB(160, 160, 160); + t.textSecondary = ThemeColor::FromRGB(100, 100, 100); + t.textAccent = ThemeColor::FromRGB(0, 100, 200); + t.textWarning = ThemeColor::FromRGB(180, 130, 0); + t.textError = ThemeColor::FromRGB(190, 40, 40); + t.textSuccess = ThemeColor::FromRGB(40, 150, 60); + + t.button = ThemeColor::FromRGB(225, 225, 225); + t.buttonHovered = ThemeColor::FromRGB(210, 210, 210); + t.buttonActive = ThemeColor::FromRGB(0, 120, 215); + t.buttonDisabled = ThemeColor::FromRGB(235, 235, 235); + t.frame = ThemeColor::FromRGB(255, 255, 255); + t.frameHovered = ThemeColor::FromRGB(245, 245, 245); + t.frameActive = ThemeColor::FromRGB(0, 120, 215, 80); + t.border = ThemeColor::FromRGB(200, 200, 200); + t.borderLight = ThemeColor::FromRGB(220, 220, 220); + t.borderAccent = ThemeColor::FromRGB(0, 120, 215); + t.borderSeparator = ThemeColor::FromRGB(210, 210, 210); + t.titleBar = ThemeColor::FromRGB(230, 230, 230); + t.titleBarActive = ThemeColor::FromRGB(0, 120, 215); + t.titleBarText = ThemeColor::FromRGB(30, 30, 30); + t.menuBar = ThemeColor::FromRGB(240, 240, 240); + t.menuItem = ThemeColor(0, 0, 0, 0); + t.menuItemHovered = ThemeColor::FromRGB(0, 120, 215, 80); + t.scrollbar = ThemeColor::FromRGB(240, 240, 240); + t.scrollbarGrab = ThemeColor::FromRGB(180, 180, 180); + t.scrollbarGrabHovered = ThemeColor::FromRGB(160, 160, 160); + t.scrollbarGrabActive = ThemeColor::FromRGB(140, 140, 140); + t.tab = ThemeColor::FromRGB(230, 230, 230); + t.tabHovered = ThemeColor::FromRGB(200, 220, 240); + t.tabActive = ThemeColor::FromRGB(255, 255, 255); + t.tabUnfocused = ThemeColor::FromRGB(235, 235, 235); + t.accent = ThemeColor::FromRGB(0, 120, 215); + t.accentSecondary = ThemeColor::FromRGB(0, 90, 180); + t.focus = ThemeColor::FromRGB(0, 120, 215); + t.selection = ThemeColor::FromRGB(0, 120, 215, 60); + t.drop = ThemeColor::FromRGB(0, 120, 215, 150); + t.graph1 = t.accent; t.graph2 = t.textWarning; + t.graph3 = t.textSuccess; t.graph4 = t.textError; t.graph5 = ThemeColor::FromRGB(120, 60, 190); + + t.windowRounding = 2.0f; + t.frameRounding = 2.0f; + t.tabRounding = 3.0f; + t.frameBorderSize = 1.0f; + return t; +} + +EditorThemeData EditorTheme::CreateHighContrastTheme() { + EditorThemeData t; + t.name = "High Contrast"; + t.description = "High contrast accessibility theme"; + + t.background = ThemeColor::FromRGB(0, 0, 0); + t.backgroundDark = ThemeColor::FromRGB(0, 0, 0); + t.backgroundLight = ThemeColor::FromRGB(20, 20, 20); + t.backgroundAccent = ThemeColor::FromRGB(0, 120, 255); + t.backgroundHeader = ThemeColor::FromRGB(15, 15, 15); + t.backgroundActive = ThemeColor::FromRGB(0, 120, 255, 150); + t.backgroundHover = ThemeColor::FromRGB(30, 30, 30); + t.backgroundSelected = ThemeColor::FromRGB(0, 120, 255, 100); + + t.text = ThemeColor::FromRGB(255, 255, 255); + t.textDisabled = ThemeColor::FromRGB(128, 128, 128); + t.textSecondary = ThemeColor::FromRGB(200, 200, 200); + t.textAccent = ThemeColor::FromRGB(0, 200, 255); + t.textWarning = ThemeColor::FromRGB(255, 220, 0); + t.textError = ThemeColor::FromRGB(255, 50, 50); + t.textSuccess = ThemeColor::FromRGB(0, 255, 100); + + t.button = ThemeColor::FromRGB(30, 30, 30); + t.buttonHovered = ThemeColor::FromRGB(50, 50, 50); + t.buttonActive = ThemeColor::FromRGB(0, 120, 255); + t.buttonDisabled = ThemeColor::FromRGB(20, 20, 20); + t.frame = ThemeColor::FromRGB(10, 10, 10); + t.frameHovered = ThemeColor::FromRGB(30, 30, 30); + t.frameActive = ThemeColor::FromRGB(0, 120, 255, 150); + t.border = ThemeColor::FromRGB(200, 200, 200); + t.borderLight = ThemeColor::FromRGB(150, 150, 150); + t.borderAccent = ThemeColor::FromRGB(0, 200, 255); + t.borderSeparator = ThemeColor::FromRGB(150, 150, 150); + t.titleBar = ThemeColor::FromRGB(0, 0, 0); + t.titleBarActive = ThemeColor::FromRGB(0, 120, 255); + t.titleBarText = ThemeColor::FromRGB(255, 255, 255); + t.menuBar = ThemeColor::FromRGB(0, 0, 0); + t.menuItem = ThemeColor(0, 0, 0, 0); + t.menuItemHovered = ThemeColor::FromRGB(0, 120, 255, 200); + t.scrollbar = ThemeColor::FromRGB(0, 0, 0); + t.scrollbarGrab = ThemeColor::FromRGB(100, 100, 100); + t.scrollbarGrabHovered = ThemeColor::FromRGB(150, 150, 150); + t.scrollbarGrabActive = ThemeColor::FromRGB(200, 200, 200); + t.tab = ThemeColor::FromRGB(0, 0, 0); + t.tabHovered = ThemeColor::FromRGB(30, 30, 30); + t.tabActive = ThemeColor::FromRGB(0, 120, 255); + t.tabUnfocused = ThemeColor::FromRGB(0, 0, 0); + t.accent = ThemeColor::FromRGB(0, 200, 255); + t.accentSecondary = ThemeColor::FromRGB(0, 120, 255); + t.focus = ThemeColor::FromRGB(0, 200, 255); + t.selection = ThemeColor::FromRGB(0, 120, 255, 120); + t.drop = ThemeColor::FromRGB(0, 200, 255, 200); + t.graph1 = t.accent; t.graph2 = t.textWarning; + t.graph3 = t.textSuccess; t.graph4 = t.textError; t.graph5 = ThemeColor::FromRGB(200, 100, 255); + + t.windowRounding = 0.0f; + t.frameRounding = 0.0f; + t.tabRounding = 0.0f; + t.frameBorderSize = 2.0f; + t.windowBorderSize = 2.0f; + return t; +} + +EditorThemeData EditorTheme::CreateBlueAccentTheme() { + EditorThemeData t = CreateSparkThemeImpl(); + t.name = "Blue Accent"; + t.description = "Dark theme with bold blue accent"; + t.accent = ThemeColor::FromRGB(30, 136, 229); + t.accentSecondary = ThemeColor::FromRGB(66, 165, 245); + t.tabActive = t.accent; + t.titleBarActive = t.accent.Darken(0.3f); + t.buttonActive = t.accent; + return t; +} + +EditorThemeData EditorTheme::CreateOrangeAccentTheme() { + EditorThemeData t = CreateSparkThemeImpl(); + t.name = "Orange Accent"; + t.description = "Dark theme with warm orange accent"; + t.accent = ThemeColor::FromRGB(245, 166, 35); + t.accentSecondary = ThemeColor::FromRGB(45, 140, 240); + t.tabActive = t.accent; + t.titleBarActive = t.accent.Darken(0.4f); + t.buttonActive = t.accent; + t.textAccent = t.accent; + return t; +} + +// =================================================================== +// Private helpers +// =================================================================== + +ThemeColor EditorTheme::GetSystemAccentColor() { + return ThemeColor::FromHex("#2D8CF0"); +} + +ThemeColor EditorTheme::CreateComplementaryColor(const ThemeColor& base) { + return ThemeColor(1.0f - base.r, 1.0f - base.g, 1.0f - base.b, base.a); +} + +std::vector EditorTheme::CreateColorPalette(const ThemeColor& base) { + return { + base, + base.Lighten(0.2f), + base.Darken(0.2f), + base.Desaturate(0.3f), + base.Lighten(0.4f) + }; +} + +unsigned int EditorTheme::ColorToImGui(const ThemeColor& color) { + return IM_COL32( + (int)(color.r * 255.0f), + (int)(color.g * 255.0f), + (int)(color.b * 255.0f), + (int)(color.a * 255.0f)); +} + +// Bridge function — the header declares CreateSparkTheme as a future addition +// but it's currently named via the static helper. We add the public wrapper. +EditorThemeData CreateSparkTheme() { + return CreateSparkThemeImpl(); +} + +// =================================================================== +// ThemeCustomizer stubs +// =================================================================== + +void ThemeCustomizer::ShowThemeEditor() { + // TODO: Live theme customization UI +} + +bool ThemeCustomizer::ExportTheme(const EditorThemeData& theme, const std::string& filepath) { + // TODO: JSON export + return false; +} + +bool ThemeCustomizer::ImportTheme(const std::string& filepath, EditorThemeData& outTheme) { + // TODO: JSON import + return false; +} + +std::vector ThemeCustomizer::GenerateThemeVariations(const EditorThemeData& baseTheme) { + std::vector variations; + // TODO: Generate warm/cool/bright/muted variations + return variations; +} + +} // namespace SparkEditor diff --git a/SparkEditor/Source/Core/EditorUI.cpp b/SparkEditor/Source/Core/EditorUI.cpp index dfb86e089..4a5b2ae4d 100644 --- a/SparkEditor/Source/Core/EditorUI.cpp +++ b/SparkEditor/Source/Core/EditorUI.cpp @@ -6,15 +6,21 @@ */ #include "EditorUI.h" -#include "../Utils/SparkConsole.h" // Use local SparkConsole instead of engine version +#include "EditorTheme.h" +#include "EditorFonts.h" +#include "EditorIcons.h" +#include "../Utils/SparkConsole.h" #include "../Panels/SceneViewPanel.h" #include "../Panels/SimpleConsolePanel.h" #include "../Panels/SimpleHierarchyPanel.h" #include "../Panels/InspectorPanel.h" #include "../Panels/AssetBrowserPanel.h" +#include "../Panels/GameViewPanel.h" +#include "../Profiler/PerformanceProfiler.h" #include "EditorCrashHandler.h" -#include "EditorApplication.h" // For EditorConfig definition +#include "EditorApplication.h" #include +#include #include #include #include @@ -60,7 +66,12 @@ bool EditorUI::Initialize(const EditorConfig& config) { console.LogInfo("Creating editor panels..."); CreatePanels(); console.LogSuccess("Panels created successfully"); - + + // Apply the Spark Professional theme + console.LogInfo("Applying Spark Professional theme..."); + ApplyTheme("Spark Professional"); + console.LogSuccess("Theme applied"); + m_isInitialized = true; console.LogSuccess("Enhanced EditorUI initialized successfully"); return true; @@ -94,20 +105,77 @@ void EditorUI::Update(float deltaTime) { void EditorUI::Render() { if (!m_isInitialized) return; - - // Render main UI components + + // === Full-screen DockSpace === + ImGuiViewport* viewport = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(viewport->WorkPos); + ImGui::SetNextWindowSize(viewport->WorkSize); + ImGui::SetNextWindowViewport(viewport->ID); + + ImGuiWindowFlags dockspaceFlags = + ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking | + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | + ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus; + + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + ImGui::Begin("##SparkEditorDockSpace", nullptr, dockspaceFlags); + ImGui::PopStyleVar(3); + + ImGuiID dockspaceId = ImGui::GetID("SparkDockSpace"); + ImGui::DockSpace(dockspaceId, ImVec2(0.0f, 0.0f), ImGuiDockNodeFlags_None); + + // Set up default layout on first frame + if (m_firstFrame) { + SetupDefaultDockLayout(dockspaceId); + m_firstFrame = false; + } + + // Menu bar is rendered inside the dockspace window RenderMainMenuBar(); + + ImGui::End(); // End dockspace window + + // Render toolbar, panels, status bar, notifications + RenderToolbar(); RenderPanels(); RenderStatusBar(); RenderNotifications(); RenderModalDialogs(); - - // Demo window for testing + if (m_showDemoWindow) { ImGui::ShowDemoWindow(&m_showDemoWindow); } } +void EditorUI::SetupDefaultDockLayout(ImGuiID dockspaceId) { + ImGui::DockBuilderRemoveNode(dockspaceId); + ImGui::DockBuilderAddNode(dockspaceId, ImGuiDockNodeFlags_DockSpace); + ImGui::DockBuilderSetNodeSize(dockspaceId, ImGui::GetMainViewport()->WorkSize); + + ImGuiID dockMain = dockspaceId; + ImGuiID dockLeft, dockRight, dockBottom, dockCenter; + + // Split: left 20% for Hierarchy + ImGui::DockBuilderSplitNode(dockMain, ImGuiDir_Left, 0.20f, &dockLeft, &dockMain); + // Split: right 25% for Inspector + ImGui::DockBuilderSplitNode(dockMain, ImGuiDir_Right, 0.25f, &dockRight, &dockMain); + // Split: bottom 28% for Console + Asset Browser (tabbed) + ImGui::DockBuilderSplitNode(dockMain, ImGuiDir_Down, 0.28f, &dockBottom, &dockCenter); + + // Dock panels — use ###panel_id to match stable IDs from BeginPanel() + ImGui::DockBuilderDockWindow("###simple_hierarchy_panel", dockLeft); + ImGui::DockBuilderDockWindow("###inspector_panel", dockRight); + ImGui::DockBuilderDockWindow("###scene_view_panel", dockCenter); + ImGui::DockBuilderDockWindow("##Toolbar", dockCenter); + ImGui::DockBuilderDockWindow("###simple_console_panel", dockBottom); + ImGui::DockBuilderDockWindow("###asset_browser_panel", dockBottom); + + ImGui::DockBuilderFinish(dockspaceId); +} + void EditorUI::Shutdown() { auto& console = Spark::SimpleConsole::GetInstance(); console.LogInfo("Shutting down EditorUI..."); @@ -229,9 +297,29 @@ void EditorUI::CreatePanels() { console.LogError("Failed to create Asset Browser panel: " + std::string(e.what())); } + // Create Game View Panel (FPS player camera) + try { + console.LogInfo("Creating Game View panel..."); + auto gameViewPanel = std::shared_ptr(new GameViewPanel()); + m_panels["GameView"] = gameViewPanel; + console.LogSuccess("Created Game View panel"); + } catch (const std::exception& e) { + console.LogError("Failed to create Game View panel: " + std::string(e.what())); + } + + // Create Performance Profiler Panel + try { + console.LogInfo("Creating Performance Profiler panel..."); + auto profilerPanel = std::shared_ptr(new PerformanceProfiler()); + m_panels["Profiler"] = profilerPanel; + console.LogSuccess("Created Performance Profiler panel"); + } catch (const std::exception& e) { + console.LogError("Failed to create Profiler panel: " + std::string(e.what())); + } + // SKIP SimpleBuildSystem in all modes since it's causing the hang console.LogWarning("SKIPPING Simple Build System panel (known to cause hangs)"); - + // Initialize all panels for (auto& [name, panel] : m_panels) { try { @@ -247,6 +335,15 @@ void EditorUI::CreatePanels() { } } + // Assign panel icons (FontAwesome) + if (m_panels.count("SceneView")) m_panels["SceneView"]->SetIcon(ICON_FA_CAMERA); + if (m_panels.count("Console")) m_panels["Console"]->SetIcon(ICON_FA_TERMINAL); + if (m_panels.count("Hierarchy")) m_panels["Hierarchy"]->SetIcon(ICON_FA_SITEMAP); + if (m_panels.count("Inspector")) m_panels["Inspector"]->SetIcon(ICON_FA_SLIDERS); + if (m_panels.count("AssetBrowser")) m_panels["AssetBrowser"]->SetIcon(ICON_FA_FOLDER); + if (m_panels.count("GameView")) m_panels["GameView"]->SetIcon(ICON_FA_GAMEPAD); + if (m_panels.count("Profiler")) m_panels["Profiler"]->SetIcon(ICON_FA_CHART_BAR); + console.LogSuccess("Created " + std::to_string(m_panels.size()) + " editor panels"); } @@ -365,8 +462,11 @@ void EditorUI::RenderMainMenuBar() { if (ImGui::MenuItem("Console", nullptr, IsPanelVisible("Console"))) { SetPanelVisible("Console", !IsPanelVisible("Console")); } - if (ImGui::MenuItem("Build System", nullptr, IsPanelVisible("BuildSystem"))) { - SetPanelVisible("BuildSystem", !IsPanelVisible("BuildSystem")); + if (ImGui::MenuItem("Game View", nullptr, IsPanelVisible("GameView"))) { + SetPanelVisible("GameView", !IsPanelVisible("GameView")); + } + if (ImGui::MenuItem("Profiler", nullptr, IsPanelVisible("Profiler"))) { + SetPanelVisible("Profiler", !IsPanelVisible("Profiler")); } ImGui::Separator(); if (ImGui::MenuItem("Reset Layout")) { @@ -380,84 +480,289 @@ void EditorUI::RenderMainMenuBar() { ImGui::EndMenu(); } + if (ImGui::BeginMenu("FPS Tools")) { + if (ImGui::MenuItem(ICON_FA_CROSSHAIRS " Weapon Editor")) { + ShowNotification("Weapon Editor coming soon!", "info"); + } + if (ImGui::MenuItem(ICON_FA_FLAG " Spawn Points")) { + ShowNotification("Spawn Point editor coming soon!", "info"); + } + if (ImGui::MenuItem(ICON_FA_BULLSEYE " Objectives")) { + ShowNotification("Objective editor coming soon!", "info"); + } + ImGui::Separator(); + if (ImGui::MenuItem(ICON_FA_BOMB " Explosive Volumes")) { + ShowNotification("Explosive volumes editor coming soon!", "info"); + } + if (ImGui::MenuItem(ICON_FA_SHIELD " Cover Points")) { + ShowNotification("Cover point editor coming soon!", "info"); + } + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu("Build")) { + if (ImGui::MenuItem(ICON_FA_LIGHTBULB " Build Lighting")) { + ShowNotification("Build Lighting started...", "info"); + } + if (ImGui::MenuItem(ICON_FA_MAP " Build NavMesh")) { + ShowNotification("Build NavMesh started...", "info"); + } + ImGui::Separator(); + if (ImGui::MenuItem(ICON_FA_HAMMER " Build All")) { + ShowNotification("Build All started...", "info"); + } + if (ImGui::MenuItem(ICON_FA_ROCKET " Deploy")) { + ShowNotification("Deploy pipeline coming soon!", "info"); + } + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Help")) { if (ImGui::MenuItem("Show Demo Window", nullptr, m_showDemoWindow)) { m_showDemoWindow = !m_showDemoWindow; } + if (ImGui::BeginMenu("Themes")) { + auto themes = EditorTheme::GetAvailableThemes(); + for (const auto& name : themes) { + bool isSelected = (m_currentTheme == name); + if (ImGui::MenuItem(name.c_str(), nullptr, isSelected)) { + ApplyTheme(name); + ShowNotification("Theme: " + name, "success", 2.0f); + } + } + ImGui::EndMenu(); + } + ImGui::Separator(); if (ImGui::MenuItem("About")) { - ShowNotification("Spark Engine Editor v1.0", "info", 5.0f); + ShowNotification(ICON_FA_BOLT " Spark Engine Editor v1.0 — FPS Game Engine", "info", 5.0f); } if (ImGui::MenuItem("Documentation")) { ShowNotification("Documentation coming soon!", "info"); } ImGui::EndMenu(); } - + ImGui::EndMainMenuBar(); } } +void EditorUI::RenderToolbar() { + ImGuiWindowFlags toolbarFlags = + ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | + ImGuiWindowFlags_NoCollapse; + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(6, 4)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 4)); + + if (ImGui::Begin("##Toolbar", nullptr, toolbarFlags)) { + float btnSize = 28.0f; + ImVec2 btnDim(btnSize, btnSize); + + // Accent colors + ImVec4 accentBlue(0.176f, 0.549f, 0.941f, 1.0f); // #2D8CF0 + ImVec4 accentAmber(0.961f, 0.651f, 0.137f, 1.0f); // #F5A623 + ImVec4 playGreen(0.298f, 0.686f, 0.314f, 1.0f); // #4CAF50 + ImVec4 stopRed(0.898f, 0.224f, 0.208f, 1.0f); // #E53935 + + // === Transform Tools === + auto ToolButton = [&](const char* icon, TransformTool tool, const char* tooltip) { + bool active = (m_currentTool == tool); + if (active) ImGui::PushStyleColor(ImGuiCol_Button, accentBlue); + if (ImGui::Button(icon, btnDim)) m_currentTool = tool; + if (active) ImGui::PopStyleColor(); + if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", tooltip); + ImGui::SameLine(); + }; + + ToolButton(ICON_FA_ARROWS_ALT, TransformTool::Move, "Move (W)"); + ToolButton(ICON_FA_SYNC_ALT, TransformTool::Rotate, "Rotate (E)"); + ToolButton(ICON_FA_EXPAND, TransformTool::Scale, "Scale (R)"); + + ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); + ImGui::SameLine(); + + // === Space Toggle === + bool isLocal = (m_transformSpace == TransformSpace::Local); + if (ImGui::Button(isLocal ? "Local" : "World", ImVec2(50, btnSize))) { + m_transformSpace = isLocal ? TransformSpace::World : TransformSpace::Local; + } + if (ImGui::IsItemHovered()) ImGui::SetTooltip("Toggle World/Local space"); + + ImGui::SameLine(); + ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); + ImGui::SameLine(); + + // === Play Controls (centered) === + float windowWidth = ImGui::GetWindowContentRegionMax().x; + float playWidth = btnSize * 3 + 8; + float cursorX = (windowWidth - playWidth) * 0.5f; + if (cursorX > ImGui::GetCursorPosX()) ImGui::SetCursorPosX(cursorX); + + // Play + bool isPlaying = (m_playMode == PlayMode::Playing); + if (isPlaying) ImGui::PushStyleColor(ImGuiCol_Button, playGreen); + if (ImGui::Button(ICON_FA_PLAY, btnDim)) { + m_playMode = (m_playMode == PlayMode::Playing) ? PlayMode::Stopped : PlayMode::Playing; + ShowNotification(isPlaying ? "Stopped" : "Playing...", isPlaying ? "info" : "success", 2.0f); + } + if (isPlaying) ImGui::PopStyleColor(); + if (ImGui::IsItemHovered()) ImGui::SetTooltip("Play (F5)"); + ImGui::SameLine(); + + // Pause + bool isPaused = (m_playMode == PlayMode::Paused); + if (isPaused) ImGui::PushStyleColor(ImGuiCol_Button, accentAmber); + if (ImGui::Button(ICON_FA_PAUSE, btnDim)) { + if (m_playMode == PlayMode::Playing) m_playMode = PlayMode::Paused; + else if (m_playMode == PlayMode::Paused) m_playMode = PlayMode::Playing; + } + if (isPaused) ImGui::PopStyleColor(); + if (ImGui::IsItemHovered()) ImGui::SetTooltip("Pause"); + ImGui::SameLine(); + + // Stop + if (ImGui::Button(ICON_FA_STOP, btnDim)) { + if (m_playMode != PlayMode::Stopped) { + m_playMode = PlayMode::Stopped; + ShowNotification("Stopped", "info", 2.0f); + } + } + if (ImGui::IsItemHovered()) ImGui::SetTooltip("Stop (Shift+F5)"); + + ImGui::SameLine(); + ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); + ImGui::SameLine(); + + // === Snap Controls (right side) === + ImGui::Checkbox("Snap", &m_snapEnabled); + if (m_snapEnabled) { + ImGui::SameLine(); + ImGui::SetNextItemWidth(60); + ImGui::DragFloat("##SnapVal", &m_snapValue, 0.1f, 0.1f, 100.0f, "%.1f"); + } + } + ImGui::End(); + ImGui::PopStyleVar(2); +} + void EditorUI::RenderStatusBar() { ImGuiViewport* viewport = ImGui::GetMainViewport(); - ImVec2 statusBarSize = ImVec2(viewport->Size.x, 20); - ImVec2 statusBarPos = ImVec2(viewport->Pos.x, viewport->Pos.y + viewport->Size.y - statusBarSize.y); - + float statusBarHeight = 24.0f; + ImVec2 statusBarPos(viewport->WorkPos.x, viewport->WorkPos.y + viewport->WorkSize.y - statusBarHeight); + ImVec2 statusBarSize(viewport->WorkSize.x, statusBarHeight); + ImGui::SetNextWindowPos(statusBarPos); ImGui::SetNextWindowSize(statusBarSize); - ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | - ImGuiWindowFlags_NoSavedSettings; - - if (ImGui::Begin("StatusBar", nullptr, flags)) { - ImGui::Text("Engine: %s | FPS: %.1f | Frame: %llu", - m_engineConnected ? "Connected" : "Disconnected", - m_stats.frameTime > 0 ? 1000.0f / m_stats.frameTime : 0, - m_frameNumber); - - ImGui::SameLine(ImGui::GetWindowWidth() - 200); - ImGui::Text("Objects: %d | Assets: %d", m_sceneObjectCount, m_assetDatabaseSize); + ImGuiWindowFlags flags = + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoDocking; + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8, 3)); + if (ImGui::Begin("##StatusBar", nullptr, flags)) { + // Left: engine connection status + ImVec4 statusColor = m_engineConnected + ? ImVec4(0.3f, 0.8f, 0.3f, 1.0f) + : ImVec4(0.8f, 0.3f, 0.3f, 1.0f); + ImGui::TextColored(statusColor, ICON_FA_CIRCLE); + ImGui::SameLine(); + ImGui::Text("Engine: %s", m_engineConnected ? "Connected" : "Disconnected"); + + ImGui::SameLine(); + ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); + ImGui::SameLine(); + + // Center: tool + selection + const char* toolNames[] = { "Move", "Rotate", "Scale" }; + ImGui::Text(ICON_FA_ARROWS_ALT " %s | Objects: %d | Selected: %d", + toolNames[(int)m_currentTool], m_sceneObjectCount, m_selectedObjectCount); + + // Right: FPS + frame info + float fps = m_stats.frameTime > 0.001f ? 1000.0f / m_stats.frameTime : 0.0f; + ImVec4 fpsColor = fps >= 60.0f ? ImVec4(0.3f, 0.8f, 0.3f, 1.0f) + : fps >= 30.0f ? ImVec4(0.9f, 0.9f, 0.3f, 1.0f) + : ImVec4(0.9f, 0.3f, 0.3f, 1.0f); + + float rightOffset = ImGui::GetWindowWidth() - 360; + if (rightOffset > ImGui::GetCursorPosX()) { + ImGui::SameLine(rightOffset); + } + ImGui::TextColored(fpsColor, "%.0f FPS", fps); + ImGui::SameLine(); + ImGui::Text("| %.1fms | Assets: %d | Frame: %llu", + m_stats.frameTime, m_assetDatabaseSize, (unsigned long long)m_frameNumber); } ImGui::End(); + ImGui::PopStyleVar(); } void EditorUI::RenderNotifications() { - const float NOTIFICATION_WIDTH = 300.0f; - const float NOTIFICATION_HEIGHT = 60.0f; - const float NOTIFICATION_SPACING = 10.0f; - + const float NOTIFICATION_WIDTH = 320.0f; + const float NOTIFICATION_HEIGHT = 52.0f; + const float NOTIFICATION_SPACING = 6.0f; + ImGuiViewport* viewport = ImGui::GetMainViewport(); - float yOffset = viewport->Pos.y + 30.0f; // Below menu bar - + float yOffset = viewport->WorkPos.y + 8.0f; + for (size_t i = 0; i < m_notifications.size(); ++i) { const auto& notification = m_notifications[i]; - - ImVec2 notificationPos = ImVec2( - viewport->Pos.x + viewport->Size.x - NOTIFICATION_WIDTH - 10.0f, - yOffset + i * (NOTIFICATION_HEIGHT + NOTIFICATION_SPACING) - ); - + + // Fade out in last 0.5 seconds + float alpha = 1.0f; + if (notification.duration > 0.0f && notification.timeLeft < 0.5f) { + alpha = std::max(0.0f, notification.timeLeft / 0.5f); + } + + ImVec2 notificationPos( + viewport->WorkPos.x + viewport->WorkSize.x - NOTIFICATION_WIDTH - 12.0f, + yOffset + i * (NOTIFICATION_HEIGHT + NOTIFICATION_SPACING)); + ImGui::SetNextWindowPos(notificationPos); ImGui::SetNextWindowSize(ImVec2(NOTIFICATION_WIDTH, NOTIFICATION_HEIGHT)); - - std::string windowName = "Notification##" + std::to_string(i); - ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar; - + ImGui::SetNextWindowBgAlpha(0.92f * alpha); + + std::string windowName = "##Notification" + std::to_string(i); + ImGuiWindowFlags flags = + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoFocusOnAppearing | + ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoSavedSettings; + + // Determine stripe and icon color + ImVec4 stripeColor(0.176f, 0.549f, 0.941f, alpha); // blue default + const char* icon = ICON_FA_INFO_CIRCLE; + if (notification.type == "error") { + stripeColor = ImVec4(0.898f, 0.224f, 0.208f, alpha); + icon = ICON_FA_TIMES; + } else if (notification.type == "warning") { + stripeColor = ImVec4(0.961f, 0.651f, 0.137f, alpha); + icon = ICON_FA_EXCLAMATION; + } else if (notification.type == "success") { + stripeColor = ImVec4(0.298f, 0.686f, 0.314f, alpha); + icon = ICON_FA_CHECK; + } + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 4.0f); if (ImGui::Begin(windowName.c_str(), nullptr, flags)) { - // Color based on type - ImVec4 color = ImVec4(1, 1, 1, 1); - if (notification.type == "error") color = ImVec4(1, 0.4f, 0.4f, 1); - else if (notification.type == "warning") color = ImVec4(1, 1, 0.4f, 1); - else if (notification.type == "success") color = ImVec4(0.4f, 1, 0.4f, 1); - - ImGui::TextColored(color, "%s", notification.message.c_str()); - - if (notification.duration > 0.0f) { - float progress = 1.0f - (notification.timeLeft / notification.duration); - ImGui::ProgressBar(progress, ImVec2(-1, 4)); - } + ImDrawList* dl = ImGui::GetWindowDrawList(); + ImVec2 wp = ImGui::GetWindowPos(); + ImVec2 ws = ImGui::GetWindowSize(); + + // Colored left stripe + dl->AddRectFilled(wp, ImVec2(wp.x + 4, wp.y + ws.y), + ImGui::ColorConvertFloat4ToU32(stripeColor), 4.0f, ImDrawFlags_RoundCornersLeft); + + // Content with padding past the stripe + ImGui::SetCursorPos(ImVec2(12, (NOTIFICATION_HEIGHT - ImGui::GetTextLineHeight()) * 0.5f)); + ImGui::TextColored(ImVec4(stripeColor.x, stripeColor.y, stripeColor.z, alpha), "%s", icon); + ImGui::SameLine(); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.88f, 0.89f, 0.92f, alpha)); + ImGui::TextWrapped("%s", notification.message.c_str()); + ImGui::PopStyleColor(); } ImGui::End(); + ImGui::PopStyleVar(2); } } @@ -602,38 +907,11 @@ void EditorUI::ResetToDefaultLayout() { void EditorUI::ApplyTheme(const std::string& themeName) { m_currentTheme = themeName; - - // Apply theme to ImGui style - ImGuiStyle& style = ImGui::GetStyle(); - - if (themeName == "Dark") { - ImGui::StyleColorsDark(&style); - - // Customize dark theme with available colors - style.Colors[ImGuiCol_WindowBg] = ImVec4(0.1f, 0.1f, 0.1f, 1.0f); - style.Colors[ImGuiCol_Tab] = ImVec4(0.2f, 0.2f, 0.2f, 1.0f); - style.Colors[ImGuiCol_TabActive] = ImVec4(0.3f, 0.3f, 0.3f, 1.0f); - - } else if (themeName == "Light") { - ImGui::StyleColorsLight(&style); - - // Customize light theme with available colors - style.Colors[ImGuiCol_WindowBg] = ImVec4(0.95f, 0.95f, 0.95f, 1.0f); - - } else if (themeName == "Classic") { - ImGui::StyleColorsClassic(&style); - - } else { - // Default to dark theme - ImGui::StyleColorsDark(&style); + + if (!EditorTheme::ApplyTheme(themeName)) { + // Fallback to basic ImGui dark style + ImGui::StyleColorsDark(); } - - // Apply consistent style settings - style.WindowRounding = 5.0f; - style.ChildRounding = 5.0f; - style.FrameRounding = 3.0f; - style.GrabRounding = 3.0f; - style.TabRounding = 3.0f; } void EditorUI::ShowNotification(const std::string& message, const std::string& type, float duration) { diff --git a/SparkEditor/Source/Core/EditorUI.h b/SparkEditor/Source/Core/EditorUI.h index 60615ffea..216568661 100644 --- a/SparkEditor/Source/Core/EditorUI.h +++ b/SparkEditor/Source/Core/EditorUI.h @@ -169,12 +169,27 @@ class EditorUI { // Additional member variable for selected objects count int m_selectedObjectCount = 0; + // Toolbar state + enum class PlayMode { Stopped, Playing, Paused }; + PlayMode m_playMode = PlayMode::Stopped; + + enum class TransformTool { Move, Rotate, Scale }; + TransformTool m_currentTool = TransformTool::Move; + + enum class TransformSpace { World, Local }; + TransformSpace m_transformSpace = TransformSpace::World; + + bool m_snapEnabled = false; + float m_snapValue = 1.0f; + // Helper methods void RenderMainMenuBar(); + void RenderToolbar(); void RenderStatusBar(); void RenderNotifications(); void RenderPanels(); void RenderModalDialogs(); + void SetupDefaultDockLayout(ImGuiID dockspaceId); void UpdateStats(float deltaTime); void CreatePanels(); }; diff --git a/SparkEditor/Source/Enums/AudioSystemEnums.h b/SparkEditor/Source/Enums/AudioSystemEnums.h index 7db8a40e4..33b979f2e 100644 --- a/SparkEditor/Source/Enums/AudioSystemEnums.h +++ b/SparkEditor/Source/Enums/AudioSystemEnums.h @@ -27,7 +27,7 @@ enum class AudioFormat { /** * @brief Audio playback states */ -enum class PlaybackState { +enum class AudioPlaybackState { STOPPED = 0, ///< Audio is stopped PLAYING = 1, ///< Audio is playing PAUSED = 2, ///< Audio is paused @@ -94,7 +94,7 @@ enum class Audio3DMode { /** * @brief Audio compression types */ -enum class CompressionType { +enum class AudioCompressionType { NONE = 0, ///< No compression LOSSLESS = 1, ///< Lossless compression LOSSY_LOW = 2, ///< Low quality lossy compression diff --git a/SparkEditor/Source/Enums/BuildSystemEnums.h b/SparkEditor/Source/Enums/BuildSystemEnums.h index 2f5acbcf5..625444914 100644 --- a/SparkEditor/Source/Enums/BuildSystemEnums.h +++ b/SparkEditor/Source/Enums/BuildSystemEnums.h @@ -96,7 +96,7 @@ enum class CodeSigningType { /** * @brief Compression types for packaging */ -enum class CompressionType { +enum class PackageCompressionType { NONE = 0, ///< No compression ZIP = 1, ///< ZIP compression GZIP = 2, ///< GZIP compression diff --git a/SparkEditor/Source/Enums/LevelStreamingEnums.h b/SparkEditor/Source/Enums/LevelStreamingEnums.h index 0dc68039c..9f43b8fe9 100644 --- a/SparkEditor/Source/Enums/LevelStreamingEnums.h +++ b/SparkEditor/Source/Enums/LevelStreamingEnums.h @@ -38,10 +38,11 @@ enum class LoadingPriority { * @brief Streaming methods */ enum class StreamingMethod { - DISTANCE_BASED = 0, ///< Distance-based streaming - FRUSTUM_BASED = 1, ///< View frustum-based streaming + DISTANCE_BASED = 0, ///< Stream based on distance from player + TRIGGER_BASED = 1, ///< Stream when entering trigger volumes MANUAL = 2, ///< Manual streaming control - HYBRID = 3 ///< Hybrid approach + PRIORITY_BASED = 3, ///< Stream based on priority and memory budget + PREDICTIVE = 4 ///< Predictive streaming based on player movement }; /** diff --git a/SparkEditor/Source/Gizmos/GizmoSystem.h b/SparkEditor/Source/Gizmos/GizmoSystem.h index 79fa6d383..961249cf2 100644 --- a/SparkEditor/Source/Gizmos/GizmoSystem.h +++ b/SparkEditor/Source/Gizmos/GizmoSystem.h @@ -84,8 +84,8 @@ struct Ray { struct GizmoInteraction { bool isActive = false; ///< Whether interaction is active GizmoAxis activeAxis = GizmoAxis::NONE; ///< Currently active axis - XMFLOAT3 startPosition; ///< Starting position of interaction - XMFLOAT3 currentDelta; ///< Current delta from start + XMFLOAT3 startPosition = {0, 0, 0}; ///< Starting position of interaction + XMFLOAT3 currentDelta = {0, 0, 0}; ///< Current delta from start float totalDelta = 0.0f; ///< Total magnitude of change bool isDragging = false; ///< Whether currently dragging }; @@ -406,8 +406,8 @@ class GizmoSystem { float m_gizmoSize = 1.0f; ///< Gizmo size scale // Interaction state - XMFLOAT3 m_lastMouseWorldPos; ///< Last mouse world position - XMFLOAT3 m_interactionStartPos; ///< Interaction start position + XMFLOAT3 m_lastMouseWorldPos = {0, 0, 0}; ///< Last mouse world position + XMFLOAT3 m_interactionStartPos = {0, 0, 0}; ///< Interaction start position bool m_isDragging = false; ///< Currently dragging GizmoAxis m_hoveredAxis = GizmoAxis::NONE; ///< Currently hovered axis @@ -415,7 +415,7 @@ class GizmoSystem { struct GizmoGeometry { Microsoft::WRL::ComPtr vertexBuffer; Microsoft::WRL::ComPtr indexBuffer; - UINT indexCount; + UINT indexCount = 0; }; GizmoGeometry m_arrowGeometry; ///< Arrow geometry for translation diff --git a/SparkEditor/Source/LevelStreaming/LevelStreamingSystem.h b/SparkEditor/Source/LevelStreaming/LevelStreamingSystem.h index c2af9ed8f..d49e9d221 100644 --- a/SparkEditor/Source/LevelStreaming/LevelStreamingSystem.h +++ b/SparkEditor/Source/LevelStreaming/LevelStreamingSystem.h @@ -13,6 +13,7 @@ #include "../Core/EditorPanel.h" #include "../SceneSystem/SceneFile.h" +#include "../Enums/LevelStreamingEnums.h" #include #include #include @@ -52,16 +53,8 @@ enum class LODLevel { LOD_COUNT = 5 }; -/** - * @brief Streaming method for levels - */ -enum class StreamingMethod { - DISTANCE_BASED = 0, ///< Stream based on distance from player - TRIGGER_BASED = 1, ///< Stream when entering trigger volumes - MANUAL = 2, ///< Manual streaming control - PRIORITY_BASED = 3, ///< Stream based on priority and memory budget - PREDICTIVE = 4 ///< Predictive streaming based on player movement -}; +// StreamingMethod is defined in ../Enums/LevelStreamingEnums.h (included via SceneFile.h or directly) +// Using the canonical definition from LevelStreamingEnums.h /** * @brief World tile information diff --git a/SparkEditor/Source/Panels/AssetBrowserPanel.cpp b/SparkEditor/Source/Panels/AssetBrowserPanel.cpp index 0bb220dd2..93c8f8e22 100644 --- a/SparkEditor/Source/Panels/AssetBrowserPanel.cpp +++ b/SparkEditor/Source/Panels/AssetBrowserPanel.cpp @@ -6,9 +6,12 @@ */ #include "AssetBrowserPanel.h" +#include "../Core/EditorIcons.h" +#include "../Core/EditorFonts.h" #include #include #include +#include namespace SparkEditor { @@ -25,38 +28,94 @@ void AssetBrowserPanel::Update(float deltaTime) { // Update asset browser logic } +// Helper to get icon for file extension +static const char* GetFileTypeIcon(const std::string& ext) { + if (ext == ".fbx" || ext == ".obj" || ext == ".gltf" || ext == ".glb") return ICON_FA_CUBE; + if (ext == ".png" || ext == ".jpg" || ext == ".jpeg" || ext == ".tga" || ext == ".bmp" || ext == ".dds") return ICON_FA_IMAGE; + if (ext == ".wav" || ext == ".mp3" || ext == ".ogg") return ICON_FA_VOLUME_UP; + if (ext == ".cpp" || ext == ".h" || ext == ".hpp" || ext == ".lua") return ICON_FA_CODE; + if (ext == ".hlsl" || ext == ".glsl" || ext == ".shader") return ICON_FA_PAINT_BRUSH; + if (ext == ".mat" || ext == ".material") return ICON_FA_CIRCLE; + if (ext == ".scene" || ext == ".map") return ICON_FA_MAP; + if (ext == ".prefab") return ICON_FA_SHAPES; + if (ext == ".ttf" || ext == ".otf") return ICON_FA_FONT; + if (ext == ".json" || ext == ".xml" || ext == ".ini" || ext == ".cfg") return ICON_FA_COG; + return ICON_FA_FILE; +} + +static ImU32 GetFileTypeColor(const std::string& ext) { + if (ext == ".fbx" || ext == ".obj" || ext == ".gltf" || ext == ".glb") return IM_COL32(100, 200, 255, 255); + if (ext == ".png" || ext == ".jpg" || ext == ".jpeg" || ext == ".tga") return IM_COL32(200, 150, 255, 255); + if (ext == ".wav" || ext == ".mp3" || ext == ".ogg") return IM_COL32(255, 200, 100, 255); + if (ext == ".cpp" || ext == ".h" || ext == ".hpp" || ext == ".lua") return IM_COL32(100, 255, 150, 255); + if (ext == ".hlsl" || ext == ".glsl" || ext == ".shader") return IM_COL32(255, 150, 150, 255); + return IM_COL32(180, 180, 180, 255); +} + void AssetBrowserPanel::Render() { if (!IsVisible()) return; if (BeginPanel()) { - // Toolbar - if (ImGui::Button("Import")) { + // Toolbar with icons + if (ImGui::Button(ICON_FA_DOWNLOAD " Import")) { // Open file dialog for importing assets } ImGui::SameLine(); - if (ImGui::Button("Refresh")) { + if (ImGui::Button(ICON_FA_SYNC_ALT " Refresh")) { RefreshAssets(); } ImGui::SameLine(); - ImGui::SliderFloat("Size", &m_thumbnailSize, 32.0f, 128.0f); - + ImGui::SetNextItemWidth(100); + ImGui::SliderFloat("##Size", &m_thumbnailSize, 32.0f, 128.0f, "%.0f px"); + if (ImGui::IsItemHovered()) ImGui::SetTooltip("Thumbnail Size"); + + // Breadcrumb navigation + ImGui::SameLine(); + ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); + ImGui::SameLine(); + + // Build breadcrumb path + std::filesystem::path currentPath(m_currentFolder); + std::filesystem::path rootPath(m_projectPath); + std::vector breadcrumbs; + std::filesystem::path tempPath = currentPath; + while (tempPath != rootPath && tempPath.has_parent_path() && tempPath != tempPath.parent_path()) { + breadcrumbs.push_back(tempPath); + tempPath = tempPath.parent_path(); + } + breadcrumbs.push_back(rootPath); + std::reverse(breadcrumbs.begin(), breadcrumbs.end()); + + for (size_t i = 0; i < breadcrumbs.size(); ++i) { + if (i > 0) { + ImGui::SameLine(0, 2); + ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), ICON_FA_CHEVRON_RIGHT); + ImGui::SameLine(0, 2); + } + std::string label = (i == 0) ? ICON_FA_HOME " Assets" : breadcrumbs[i].filename().string(); + if (ImGui::SmallButton(label.c_str())) { + m_currentFolder = breadcrumbs[i].string(); + RefreshAssets(); + } + } + ImGui::Separator(); - - // Split view: folder tree on left, asset grid on right + + // Split view if (ImGui::BeginTable("AssetBrowserTable", 2, ImGuiTableFlags_Resizable)) { ImGui::TableSetupColumn("Folders", ImGuiTableColumnFlags_WidthFixed, 200.0f); ImGui::TableSetupColumn("Assets", ImGuiTableColumnFlags_WidthStretch); - + ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); RenderFolderTree(); - + ImGui::TableSetColumnIndex(1); RenderAssetGrid(); - + ImGui::EndTable(); } - + ImGui::Separator(); RenderAssetDetails(); } @@ -79,45 +138,51 @@ void AssetBrowserPanel::SetProjectPath(const std::string& projectPath) { void AssetBrowserPanel::RenderFolderTree() { ImGui::BeginChild("FolderTree"); - - if (ImGui::TreeNodeEx("Assets", ImGuiTreeNodeFlags_DefaultOpen)) { - // Render folder hierarchy + + std::string rootLabel = std::string(ICON_FA_FOLDER) + " Assets"; + if (ImGui::TreeNodeEx(rootLabel.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) { try { if (!m_projectPath.empty() && std::filesystem::exists(m_projectPath)) { for (const auto& entry : std::filesystem::directory_iterator(m_projectPath)) { if (entry.is_directory()) { std::string folderName = entry.path().filename().string(); - if (ImGui::TreeNode(folderName.c_str())) { + bool isCurrentFolder = (entry.path().string() == m_currentFolder); + std::string nodeLabel = std::string(isCurrentFolder ? ICON_FA_FOLDER_OPEN : ICON_FA_FOLDER) + " " + folderName; + + ImGuiTreeNodeFlags nodeFlags = ImGuiTreeNodeFlags_OpenOnArrow; + if (isCurrentFolder) nodeFlags |= ImGuiTreeNodeFlags_Selected; + + if (ImGui::TreeNodeEx(nodeLabel.c_str(), nodeFlags)) { if (ImGui::IsItemClicked()) { m_currentFolder = entry.path().string(); RefreshAssets(); } ImGui::TreePop(); + } else if (ImGui::IsItemClicked()) { + m_currentFolder = entry.path().string(); + RefreshAssets(); } } } } } catch (const std::exception& e) { - ImGui::TextColored(ImVec4(1, 0.5f, 0.5f, 1), "Error reading folders"); + ImGui::TextColored(ImVec4(1, 0.5f, 0.5f, 1), ICON_FA_EXCLAMATION " Error reading folders"); } - + ImGui::TreePop(); } - + ImGui::EndChild(); } void AssetBrowserPanel::RenderAssetGrid() { ImGui::BeginChild("AssetGrid"); - - // Current path - ImGui::Text("Path: %s", m_currentFolder.c_str()); - ImGui::Separator(); - + // Asset grid float panelWidth = ImGui::GetContentRegionAvail().x; - int columns = std::max(1, (int)(panelWidth / (m_thumbnailSize + 10.0f))); - + float cellWidth = m_thumbnailSize + 14.0f; + int columns = std::max(1, (int)(panelWidth / cellWidth)); + if (ImGui::BeginTable("AssetGridTable", columns)) { int itemIndex = 0; for (const auto& asset : m_assets) { @@ -125,44 +190,73 @@ void AssetBrowserPanel::RenderAssetGrid() { ImGui::TableNextRow(); } ImGui::TableSetColumnIndex(itemIndex % columns); - - // Asset thumbnail (placeholder) + + std::filesystem::path assetPath(asset); + std::string ext = assetPath.extension().string(); + std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower); + std::string filename = assetPath.filename().string(); + const char* fileIcon = GetFileTypeIcon(ext); + ImU32 iconColor = GetFileTypeColor(ext); + + // Thumbnail ImDrawList* drawList = ImGui::GetWindowDrawList(); ImVec2 pos = ImGui::GetCursorScreenPos(); ImVec2 size(m_thumbnailSize, m_thumbnailSize); - + bool isSelected = (asset == m_selectedAsset); - ImU32 borderColor = isSelected ? IM_COL32(100, 150, 255, 255) : IM_COL32(100, 100, 100, 255); - - drawList->AddRectFilled(pos, ImVec2(pos.x + size.x, pos.y + size.y), IM_COL32(80, 80, 80, 255)); - drawList->AddRect(pos, ImVec2(pos.x + size.x, pos.y + size.y), borderColor); - - // Asset icon (simple) - ImVec2 iconPos = ImVec2(pos.x + size.x * 0.25f, pos.y + size.y * 0.25f); - ImVec2 iconSize = ImVec2(size.x * 0.5f, size.y * 0.5f); - drawList->AddRectFilled(iconPos, ImVec2(iconPos.x + iconSize.x, iconPos.y + iconSize.y), - IM_COL32(150, 150, 150, 255)); - - // Handle click + ImU32 bgColor = isSelected ? IM_COL32(45, 140, 240, 60) : IM_COL32(40, 43, 50, 255); + ImU32 borderColor = isSelected ? IM_COL32(45, 140, 240, 255) : IM_COL32(55, 58, 66, 255); + + drawList->AddRectFilled(pos, ImVec2(pos.x + size.x, pos.y + size.y), bgColor, 4.0f); + drawList->AddRect(pos, ImVec2(pos.x + size.x, pos.y + size.y), borderColor, 4.0f); + + // Large icon centered in thumbnail + ImVec2 iconTextSize = ImGui::CalcTextSize(fileIcon); + ImVec2 iconRenderPos( + pos.x + (size.x - iconTextSize.x) * 0.5f, + pos.y + (size.y - iconTextSize.y) * 0.5f - 4.0f + ); + drawList->AddText(iconRenderPos, iconColor, fileIcon); + + // Click handling ImGui::SetCursorScreenPos(pos); ImGui::InvisibleButton(asset.c_str(), size); if (ImGui::IsItemClicked()) { m_selectedAsset = asset; } - - // Asset name + + // Hover tooltip + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::Text("%s %s", fileIcon, filename.c_str()); + ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "Type: %s", ext.empty() ? "Unknown" : ext.c_str()); + try { + if (std::filesystem::exists(asset)) { + auto fsize = std::filesystem::file_size(asset); + if (fsize > 1024 * 1024) + ImGui::Text("Size: %.1f MB", fsize / (1024.0f * 1024.0f)); + else if (fsize > 1024) + ImGui::Text("Size: %.1f KB", fsize / 1024.0f); + else + ImGui::Text("Size: %lld bytes", (long long)fsize); + } + } catch (...) {} + ImGui::EndTooltip(); + } + + // Filename below thumbnail (truncated) ImGui::SetCursorScreenPos(ImVec2(pos.x, pos.y + size.y + 2.0f)); - std::string filename = std::filesystem::path(asset).filename().string(); - if (filename.length() > 12) { - filename = filename.substr(0, 9) + "..."; + int maxChars = (int)(m_thumbnailSize / 7.0f); + if ((int)filename.length() > maxChars) { + filename = filename.substr(0, maxChars - 3) + "..."; } - ImGui::Text("%s", filename.c_str()); - + ImGui::TextWrapped("%s", filename.c_str()); + itemIndex++; } ImGui::EndTable(); } - + ImGui::EndChild(); } diff --git a/SparkEditor/Source/Panels/GameViewPanel.cpp b/SparkEditor/Source/Panels/GameViewPanel.cpp new file mode 100644 index 000000000..b06dbd2f2 --- /dev/null +++ b/SparkEditor/Source/Panels/GameViewPanel.cpp @@ -0,0 +1,196 @@ +/** + * @file GameViewPanel.cpp + * @brief Game viewport panel — shows player camera perspective + * @author Spark Engine Team + * @date 2025 + */ + +#include "GameViewPanel.h" +#include "../Core/EditorIcons.h" +#include "../Core/EditorFonts.h" +#include +#include + +namespace SparkEditor { + +GameViewPanel::GameViewPanel() + : EditorPanel("Game View", "game_view_panel") { +} + +bool GameViewPanel::Initialize() { + std::cout << "Initializing Game View panel\n"; + return true; +} + +void GameViewPanel::Update(float deltaTime) { + // Simulate health regeneration + if (m_health < 100.0f) { + m_health = std::min(100.0f, m_health + 2.0f * deltaTime); + } +} + +void GameViewPanel::Render() { + if (!IsVisible()) return; + + if (BeginPanel()) { + RenderToolbar(); + RenderGameContent(); + } + EndPanel(); +} + +void GameViewPanel::Shutdown() { + std::cout << "Shutting down Game View panel\n"; +} + +bool GameViewPanel::HandleEvent(const std::string& eventType, void* eventData) { + return false; +} + +void GameViewPanel::RenderToolbar() { + // Resolution selector + const char* resLabels[] = { "Free", "1920x1080", "1280x720", "2560x1440", "3840x2160" }; + ImGui::SetNextItemWidth(100); + int resIdx = (int)m_resolution; + if (ImGui::Combo("##Resolution", &resIdx, resLabels, IM_ARRAYSIZE(resLabels))) { + m_resolution = (Resolution)resIdx; + } + if (ImGui::IsItemHovered()) ImGui::SetTooltip("Output Resolution"); + + ImGui::SameLine(); + ImGui::Checkbox("Lock AR", &m_lockAspectRatio); + if (ImGui::IsItemHovered()) ImGui::SetTooltip("Lock Aspect Ratio (16:9)"); + + ImGui::SameLine(); + ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); + ImGui::SameLine(); + + ImGui::Checkbox(ICON_FA_CROSSHAIRS " HUD", &m_showHUD); + ImGui::SameLine(); + ImGui::Checkbox(ICON_FA_CHART_BAR " Stats", &m_showStats); + + ImGui::SameLine(); + ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); + ImGui::SameLine(); + + if (ImGui::Button(m_maximized ? ICON_FA_COMPRESS : ICON_FA_EXPAND)) { + m_maximized = !m_maximized; + } + if (ImGui::IsItemHovered()) ImGui::SetTooltip(m_maximized ? "Restore" : "Maximize"); + + ImGui::Separator(); +} + +void GameViewPanel::RenderGameContent() { + ImVec2 viewportSize = ImGui::GetContentRegionAvail(); + if (viewportSize.x <= 0 || viewportSize.y <= 0) return; + + ImDrawList* drawList = ImGui::GetWindowDrawList(); + ImVec2 pos = ImGui::GetCursorScreenPos(); + + // Dark game viewport background + drawList->AddRectFilled(pos, ImVec2(pos.x + viewportSize.x, pos.y + viewportSize.y), + IM_COL32(20, 22, 28, 255)); + + // Simulated sky gradient + for (int y = 0; y < (int)(viewportSize.y * 0.6f); y++) { + float t = (float)y / (viewportSize.y * 0.6f); + int r = (int)(20 + t * 30); + int g = (int)(30 + t * 40); + int b = (int)(60 + t * 50); + drawList->AddLine(ImVec2(pos.x, pos.y + y), + ImVec2(pos.x + viewportSize.x, pos.y + y), + IM_COL32(r, g, b, 255)); + } + + // Ground line + float groundY = pos.y + viewportSize.y * 0.6f; + drawList->AddRectFilled(ImVec2(pos.x, groundY), + ImVec2(pos.x + viewportSize.x, pos.y + viewportSize.y), + IM_COL32(35, 40, 30, 255)); + + // Grid on ground + for (int i = 0; i < 30; i++) { + float x = pos.x + (i * viewportSize.x / 30.0f); + drawList->AddLine(ImVec2(x, groundY), ImVec2(x, pos.y + viewportSize.y), + IM_COL32(50, 55, 40, 200)); + } + for (int i = 0; i < 10; i++) { + float y = groundY + (i * (viewportSize.y * 0.4f) / 10.0f); + drawList->AddLine(ImVec2(pos.x, y), ImVec2(pos.x + viewportSize.x, y), + IM_COL32(50, 55, 40, 200)); + } + + // "Game View" center text + const char* label = "Game View - Player Camera"; + ImVec2 textSize = ImGui::CalcTextSize(label); + ImVec2 textPos(pos.x + (viewportSize.x - textSize.x) * 0.5f, + pos.y + (viewportSize.y - textSize.y) * 0.5f); + drawList->AddText(textPos, IM_COL32(100, 120, 140, 180), label); + + // FPS HUD overlay + if (m_showHUD) { + RenderFPSHUD(); + } + + // Advance cursor past the viewport area + ImGui::SetCursorScreenPos(ImVec2(pos.x, pos.y + viewportSize.y)); + ImGui::Dummy(ImVec2(viewportSize.x, 0)); +} + +void GameViewPanel::RenderFPSHUD() { + ImDrawList* drawList = ImGui::GetWindowDrawList(); + ImVec2 winPos = ImGui::GetWindowPos(); + ImVec2 winSize = ImGui::GetWindowSize(); + float padding = 12.0f; + float toolbarH = 38.0f; // approximate toolbar height + + // === Crosshair === + if (m_showCrosshair) { + float cx = winPos.x + winSize.x * 0.5f; + float cy = winPos.y + toolbarH + (winSize.y - toolbarH) * 0.5f; + ImU32 crossColor = IM_COL32(255, 255, 255, 200); + float size = 12.0f; + float gap = 3.0f; + float thickness = 2.0f; + drawList->AddLine(ImVec2(cx - size, cy), ImVec2(cx - gap, cy), crossColor, thickness); + drawList->AddLine(ImVec2(cx + gap, cy), ImVec2(cx + size, cy), crossColor, thickness); + drawList->AddLine(ImVec2(cx, cy - size), ImVec2(cx, cy - gap), crossColor, thickness); + drawList->AddLine(ImVec2(cx, cy + gap), ImVec2(cx, cy + size), crossColor, thickness); + drawList->AddCircle(ImVec2(cx, cy), 2.0f, crossColor, 12, 1.5f); + } + + // === Health bar (bottom-left) === + float barW = 200.0f, barH = 16.0f; + float bx = winPos.x + padding; + float by = winPos.y + winSize.y - padding - barH; + float healthPct = m_health / 100.0f; + + // Background + drawList->AddRectFilled(ImVec2(bx, by), ImVec2(bx + barW, by + barH), + IM_COL32(0, 0, 0, 150), 3.0f); + // Health fill + ImU32 healthColor = healthPct > 0.5f ? IM_COL32(76, 175, 80, 220) : + healthPct > 0.25f ? IM_COL32(245, 166, 35, 220) : + IM_COL32(229, 57, 53, 220); + drawList->AddRectFilled(ImVec2(bx + 2, by + 2), + ImVec2(bx + 2 + (barW - 4) * healthPct, by + barH - 2), + healthColor, 2.0f); + // Health text + char healthText[32]; + snprintf(healthText, sizeof(healthText), ICON_FA_HEART " %.0f", m_health); + drawList->AddText(ImVec2(bx + 4, by), IM_COL32(255, 255, 255, 220), healthText); + + // === Ammo (bottom-right) === + char ammoText[32]; + snprintf(ammoText, sizeof(ammoText), "%d / %d", m_ammo, m_maxAmmo); + ImVec2 ammoSize = ImGui::CalcTextSize(ammoText); + float ax = winPos.x + winSize.x - padding - ammoSize.x - 30; + float ay = by; + drawList->AddRectFilled(ImVec2(ax - 4, ay), ImVec2(ax + ammoSize.x + 30, ay + barH), + IM_COL32(0, 0, 0, 150), 3.0f); + drawList->AddText(ImVec2(ax, ay), IM_COL32(255, 255, 255, 220), ICON_FA_CROSSHAIRS); + drawList->AddText(ImVec2(ax + 20, ay), IM_COL32(255, 255, 255, 220), ammoText); +} + +} // namespace SparkEditor diff --git a/SparkEditor/Source/Panels/GameViewPanel.h b/SparkEditor/Source/Panels/GameViewPanel.h new file mode 100644 index 000000000..9bdfbcd0b --- /dev/null +++ b/SparkEditor/Source/Panels/GameViewPanel.h @@ -0,0 +1,49 @@ +/** + * @file GameViewPanel.h + * @brief Game viewport panel showing player-perspective view + * @author Spark Engine Team + * @date 2025 + */ + +#pragma once + +#include "../Core/EditorPanel.h" +#include + +namespace SparkEditor { + +class GameViewPanel : public EditorPanel { +public: + GameViewPanel(); + ~GameViewPanel() override = default; + + bool Initialize() override; + void Update(float deltaTime) override; + void Render() override; + void Shutdown() override; + bool HandleEvent(const std::string& eventType, void* eventData) override; + +private: + void RenderToolbar(); + void RenderGameContent(); + void RenderFPSHUD(); + + // Resolution presets + enum class Resolution { Free, R1920x1080, R1280x720, R2560x1440, R3840x2160 }; + Resolution m_resolution = Resolution::Free; + bool m_lockAspectRatio = true; + float m_aspectRatio = 16.0f / 9.0f; + + // HUD + bool m_showHUD = true; + bool m_showCrosshair = true; + bool m_showStats = false; + bool m_maximized = false; + + // Simulation + float m_health = 100.0f; + int m_ammo = 30; + int m_maxAmmo = 30; +}; + +} // namespace SparkEditor diff --git a/SparkEditor/Source/Panels/InspectorPanel.cpp b/SparkEditor/Source/Panels/InspectorPanel.cpp index def694432..7826d0d21 100644 --- a/SparkEditor/Source/Panels/InspectorPanel.cpp +++ b/SparkEditor/Source/Panels/InspectorPanel.cpp @@ -6,6 +6,8 @@ */ #include "InspectorPanel.h" +#include "../Core/EditorIcons.h" +#include "../Core/EditorFonts.h" #include #include @@ -29,19 +31,26 @@ void InspectorPanel::Render() { if (BeginPanel()) { if (m_inspectedObject.empty()) { - ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "No object selected"); + float avail = ImGui::GetContentRegionAvail().y; + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + avail * 0.4f); + float textWidth = ImGui::CalcTextSize(ICON_FA_MOUSE_POINTER " Select an object to inspect").x; + ImGui::SetCursorPosX((ImGui::GetWindowWidth() - textWidth) * 0.5f); + ImGui::TextColored(ImVec4(0.55f, 0.58f, 0.62f, 1.0f), ICON_FA_MOUSE_POINTER " Select an object to inspect"); } else { - ImGui::Text("Inspecting: %s", m_inspectedObject.c_str()); - ImGui::Separator(); - RenderObjectProperties(); + ImGui::Spacing(); RenderComponentList(); - + + ImGui::Spacing(); ImGui::Separator(); - if (ImGui::Button("Add Component")) { + ImGui::Spacing(); + + // Full-width "Add Component" button + float btnWidth = ImGui::GetContentRegionAvail().x; + if (ImGui::Button(ICON_FA_PLUS " Add Component", ImVec2(btnWidth, 30))) { m_showAddComponentMenu = true; } - + if (m_showAddComponentMenu) { RenderAddComponentMenu(); } @@ -67,70 +76,189 @@ void InspectorPanel::SetInspectedObject(const std::string& objectId) { } void InspectorPanel::RenderObjectProperties() { - if (ImGui::CollapsingHeader("Object Properties", ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::CollapsingHeader(ICON_FA_CUBE " Object Properties", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::Indent(4); + char nameBuffer[256]; strncpy_s(nameBuffer, m_inspectedObject.c_str(), sizeof(nameBuffer) - 1); - if (ImGui::InputText("Name", nameBuffer, sizeof(nameBuffer))) { + ImGui::SetNextItemWidth(-1); + if (ImGui::InputText("##Name", nameBuffer, sizeof(nameBuffer))) { m_inspectedObject = nameBuffer; } - + bool active = true; ImGui::Checkbox("Active", &active); - - // Tag and Layer - ImGui::Text("Tag: Default"); - ImGui::Text("Layer: Default"); + ImGui::SameLine(); + bool isStatic = false; + ImGui::Checkbox("Static", &isStatic); + + // Tag and Layer dropdowns + const char* tags[] = { "Default", "Player", "Enemy", "Weapon", "Terrain", "Trigger" }; + static int currentTag = 0; + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x * 0.5f); + ImGui::Combo("Tag", ¤tTag, tags, IM_ARRAYSIZE(tags)); + + const char* layers[] = { "Default", "Ignore Raycast", "Water", "UI", "Ground", "Player" }; + static int currentLayer = 0; + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x * 0.5f); + ImGui::Combo("Layer", ¤tLayer, layers, IM_ARRAYSIZE(layers)); + + ImGui::Unindent(4); } } void InspectorPanel::RenderComponentList() { RenderTransformComponent(); - - // Render other components - if (ImGui::CollapsingHeader("Mesh Renderer")) { - ImGui::Text("Material: Default"); - ImGui::Text("Mesh: Cube"); + + // Mesh Renderer + if (ImGui::CollapsingHeader(ICON_FA_SHAPES " Mesh Renderer")) { + ImGui::Indent(4); + const char* meshes[] = { "Cube", "Sphere", "Cylinder", "Plane", "Custom" }; + static int currentMesh = 0; + ImGui::Combo("Mesh", ¤tMesh, meshes, IM_ARRAYSIZE(meshes)); + + const char* materials[] = { "Default", "PBR Standard", "Unlit", "Terrain", "Water" }; + static int currentMaterial = 0; + ImGui::Combo("Material", ¤tMaterial, materials, IM_ARRAYSIZE(materials)); + + static bool castShadows = true; + ImGui::Checkbox("Cast Shadows", &castShadows); + ImGui::SameLine(); + static bool receiveShadows = true; + ImGui::Checkbox("Receive Shadows", &receiveShadows); + ImGui::Unindent(4); } - - if (ImGui::CollapsingHeader("Collider")) { - ImGui::Text("Type: Box Collider"); - bool isTrigger = false; + + // Collider + if (ImGui::CollapsingHeader(ICON_FA_VECTOR_SQUARE " Box Collider")) { + ImGui::Indent(4); + static bool isTrigger = false; ImGui::Checkbox("Is Trigger", &isTrigger); + static float center[3] = {0.0f, 0.0f, 0.0f}; + ImGui::DragFloat3("Center", center, 0.1f); + static float size[3] = {1.0f, 1.0f, 1.0f}; + ImGui::DragFloat3("Size", size, 0.1f); + ImGui::Unindent(4); } + + // Rigidbody + if (ImGui::CollapsingHeader(ICON_FA_GLOBE " Rigidbody")) { + ImGui::Indent(4); + static float mass = 1.0f; + ImGui::DragFloat("Mass", &mass, 0.1f, 0.01f, 1000.0f); + static float drag = 0.0f; + ImGui::DragFloat("Drag", &drag, 0.01f, 0.0f, 100.0f); + static float angularDrag = 0.05f; + ImGui::DragFloat("Angular Drag", &angularDrag, 0.01f, 0.0f, 100.0f); + static bool useGravity = true; + ImGui::Checkbox("Use Gravity", &useGravity); + static bool isKinematic = false; + ImGui::Checkbox("Is Kinematic", &isKinematic); + ImGui::Unindent(4); + } +} + +static void DrawVec3Control(const char* label, float* values, float resetValue, float speed) { + ImVec4 xColor(0.9f, 0.2f, 0.2f, 1.0f); + ImVec4 yColor(0.2f, 0.8f, 0.2f, 1.0f); + ImVec4 zColor(0.2f, 0.4f, 0.9f, 1.0f); + + ImGui::PushID(label); + ImGui::Text("%s", label); + ImGui::SameLine(90); + + float width = (ImGui::GetContentRegionAvail().x - 60) / 3.0f; + + // X + ImGui::PushStyleColor(ImGuiCol_Button, xColor); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 0.3f, 0.3f, 1.0f)); + if (ImGui::Button("X", ImVec2(20, 20))) values[0] = resetValue; + ImGui::PopStyleColor(2); + ImGui::SameLine(); + ImGui::SetNextItemWidth(width); + ImGui::DragFloat("##X", &values[0], speed); + ImGui::SameLine(); + + // Y + ImGui::PushStyleColor(ImGuiCol_Button, yColor); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.3f, 0.9f, 0.3f, 1.0f)); + if (ImGui::Button("Y", ImVec2(20, 20))) values[1] = resetValue; + ImGui::PopStyleColor(2); + ImGui::SameLine(); + ImGui::SetNextItemWidth(width); + ImGui::DragFloat("##Y", &values[1], speed); + ImGui::SameLine(); + + // Z + ImGui::PushStyleColor(ImGuiCol_Button, zColor); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.3f, 0.5f, 1.0f, 1.0f)); + if (ImGui::Button("Z", ImVec2(20, 20))) values[2] = resetValue; + ImGui::PopStyleColor(2); + ImGui::SameLine(); + ImGui::SetNextItemWidth(width); + ImGui::DragFloat("##Z", &values[2], speed); + + ImGui::PopID(); } void InspectorPanel::RenderTransformComponent() { - if (ImGui::CollapsingHeader("Transform", ImGuiTreeNodeFlags_DefaultOpen)) { - 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}; - - ImGui::DragFloat3("Position", position, 0.1f); - ImGui::DragFloat3("Rotation", rotation, 1.0f); - ImGui::DragFloat3("Scale", scale, 0.1f); + if (ImGui::CollapsingHeader(ICON_FA_ARROWS_ALT " Transform", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::Indent(4); + + static float position[3] = {0.0f, 0.0f, 0.0f}; + static float rotation[3] = {0.0f, 0.0f, 0.0f}; + static float scale[3] = {1.0f, 1.0f, 1.0f}; + + DrawVec3Control("Position", position, 0.0f, 0.1f); + DrawVec3Control("Rotation", rotation, 0.0f, 1.0f); + DrawVec3Control("Scale", scale, 1.0f, 0.1f); + + ImGui::Unindent(4); } } void InspectorPanel::RenderAddComponentMenu() { + if (m_showAddComponentMenu) { + ImGui::OpenPopup("AddComponentMenu"); + } + if (ImGui::BeginPopup("AddComponentMenu")) { - if (ImGui::MenuItem("Mesh Renderer")) { - // Add mesh renderer component - m_showAddComponentMenu = false; + ImGui::Text(ICON_FA_SEARCH " Search Components"); + ImGui::Separator(); + + if (ImGui::BeginMenu(ICON_FA_SHAPES " Rendering")) { + if (ImGui::MenuItem("Mesh Renderer")) m_showAddComponentMenu = false; + if (ImGui::MenuItem("Skinned Mesh Renderer")) m_showAddComponentMenu = false; + if (ImGui::MenuItem("Particle System")) m_showAddComponentMenu = false; + if (ImGui::MenuItem("Trail Renderer")) m_showAddComponentMenu = false; + ImGui::EndMenu(); + } + if (ImGui::BeginMenu(ICON_FA_VECTOR_SQUARE " Physics")) { + if (ImGui::MenuItem("Box Collider")) m_showAddComponentMenu = false; + if (ImGui::MenuItem("Sphere Collider")) m_showAddComponentMenu = false; + if (ImGui::MenuItem("Capsule Collider")) m_showAddComponentMenu = false; + if (ImGui::MenuItem("Rigidbody")) m_showAddComponentMenu = false; + ImGui::EndMenu(); } - if (ImGui::MenuItem("Box Collider")) { - // Add box collider component - m_showAddComponentMenu = false; + if (ImGui::BeginMenu(ICON_FA_CODE " Scripting")) { + if (ImGui::MenuItem("C++ Script")) m_showAddComponentMenu = false; + if (ImGui::MenuItem("Lua Script")) m_showAddComponentMenu = false; + ImGui::EndMenu(); } - if (ImGui::MenuItem("Rigidbody")) { - // Add rigidbody component - m_showAddComponentMenu = false; + if (ImGui::BeginMenu(ICON_FA_VOLUME_UP " Audio")) { + if (ImGui::MenuItem("Audio Source")) m_showAddComponentMenu = false; + if (ImGui::MenuItem("Audio Listener")) m_showAddComponentMenu = false; + ImGui::EndMenu(); + } + if (ImGui::BeginMenu(ICON_FA_CROSSHAIRS " FPS")) { + if (ImGui::MenuItem("Weapon Component")) m_showAddComponentMenu = false; + if (ImGui::MenuItem("Health System")) m_showAddComponentMenu = false; + if (ImGui::MenuItem("Spawn Point")) m_showAddComponentMenu = false; + if (ImGui::MenuItem("Damage Volume")) m_showAddComponentMenu = false; + ImGui::EndMenu(); } ImGui::EndPopup(); } - - if (m_showAddComponentMenu) { - ImGui::OpenPopup("AddComponentMenu"); - } } } // namespace SparkEditor \ No newline at end of file diff --git a/SparkEditor/Source/Panels/SceneViewPanel.cpp b/SparkEditor/Source/Panels/SceneViewPanel.cpp index f02202805..ba87969c6 100644 --- a/SparkEditor/Source/Panels/SceneViewPanel.cpp +++ b/SparkEditor/Source/Panels/SceneViewPanel.cpp @@ -6,6 +6,8 @@ */ #include "SceneViewPanel.h" +#include "../Core/EditorIcons.h" +#include "../Core/EditorFonts.h" #include #include @@ -103,26 +105,59 @@ void SceneViewPanel::SetDevice(ID3D11Device* device, ID3D11DeviceContext* contex } void SceneViewPanel::RenderToolbar() { - if (ImGui::Button("Move")) { - // Set move gizmo mode - } + ImVec4 accentBlue(0.176f, 0.549f, 0.941f, 1.0f); + float btnSize = 24.0f; + ImVec2 btnDim(btnSize, btnSize); + + // Transform tool buttons with icons + auto GizmoButton = [&](const char* icon, GizmoMode mode, const char* tooltip) { + bool active = (m_gizmoMode == mode); + if (active) ImGui::PushStyleColor(ImGuiCol_Button, accentBlue); + if (ImGui::Button(icon, btnDim)) m_gizmoMode = mode; + if (active) ImGui::PopStyleColor(); + if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", tooltip); + ImGui::SameLine(); + }; + + GizmoButton(ICON_FA_ARROWS_ALT, GizmoMode::Move, "Move (W)"); + GizmoButton(ICON_FA_SYNC_ALT, GizmoMode::Rotate, "Rotate (E)"); + GizmoButton(ICON_FA_EXPAND, GizmoMode::Scale, "Scale (R)"); + + ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); ImGui::SameLine(); - if (ImGui::Button("Rotate")) { - // Set rotation gizmo mode + + // Render mode dropdown + const char* renderModeNames[] = { "Shaded", "Wireframe", "Unlit", "Normals", "Depth" }; + ImGui::SetNextItemWidth(90); + if (ImGui::BeginCombo("##RenderMode", renderModeNames[(int)m_renderMode])) { + for (int i = 0; i < 5; i++) { + bool selected = ((int)m_renderMode == i); + if (ImGui::Selectable(renderModeNames[i], selected)) + m_renderMode = (RenderMode)i; + if (selected) ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); } + if (ImGui::IsItemHovered()) ImGui::SetTooltip("Render Mode"); + ImGui::SameLine(); - if (ImGui::Button("Scale")) { - // Set scale gizmo mode - } - + ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); ImGui::SameLine(); - ImGui::Separator(); + + // Toggles + ImGui::Checkbox(ICON_FA_GRID " Grid", &m_showGrid); ImGui::SameLine(); - - ImGui::Checkbox("Grid", &m_showGrid); + ImGui::Checkbox(ICON_FA_CUBE " Gizmos", &m_showGizmos); + ImGui::SameLine(); - ImGui::Checkbox("Gizmos", &m_showGizmos); - + ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); + ImGui::SameLine(); + + // Camera speed + ImGui::SetNextItemWidth(80); + ImGui::DragFloat("##CamSpeed", &m_cameraSpeed, 0.1f, 0.1f, 50.0f, ICON_FA_CAMERA " %.1f"); + if (ImGui::IsItemHovered()) ImGui::SetTooltip("Camera Speed"); + ImGui::Separator(); } diff --git a/SparkEditor/Source/Panels/SceneViewPanel.h b/SparkEditor/Source/Panels/SceneViewPanel.h index 191bea315..f5696a438 100644 --- a/SparkEditor/Source/Panels/SceneViewPanel.h +++ b/SparkEditor/Source/Panels/SceneViewPanel.h @@ -96,6 +96,14 @@ class SceneViewPanel : public EditorPanel { bool m_showGizmos = true; int m_renderTextureWidth = 512; int m_renderTextureHeight = 512; + + // Render mode + enum class RenderMode { Shaded, Wireframe, Unlit, Normals, Depth }; + RenderMode m_renderMode = RenderMode::Shaded; + + // Gizmo mode + enum class GizmoMode { Move, Rotate, Scale }; + GizmoMode m_gizmoMode = GizmoMode::Move; }; } // namespace SparkEditor \ No newline at end of file diff --git a/SparkEditor/Source/Panels/SimpleConsolePanel.cpp b/SparkEditor/Source/Panels/SimpleConsolePanel.cpp index 104257c00..f3054d40f 100644 --- a/SparkEditor/Source/Panels/SimpleConsolePanel.cpp +++ b/SparkEditor/Source/Panels/SimpleConsolePanel.cpp @@ -6,7 +6,9 @@ */ #include "SimpleConsolePanel.h" -#include "../Utils/SparkConsole.h" // Include SparkConsole for engine-style logging +#include "../Core/EditorIcons.h" +#include "../Core/EditorFonts.h" +#include "../Utils/SparkConsole.h" #include #include #include @@ -105,31 +107,71 @@ void SimpleConsolePanel::Render() { if (BeginPanel()) { // Console toolbar - if (ImGui::Button("Clear")) { + if (ImGui::Button(ICON_FA_TRASH " Clear")) { Clear(); } ImGui::SameLine(); ImGui::Checkbox("Auto-scroll", &m_scrollToBottom); - + ImGui::SameLine(); - ImGui::Separator(); + ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); ImGui::SameLine(); - + + // Log level filter toggle buttons with counts + m_infoCount = m_warningCount = m_errorCount = 0; + for (const auto& msg : m_messages) { + if (msg.level == "INFO" || msg.level == "RESULT" || msg.level == "COMMAND" || msg.level == "TRACE") m_infoCount++; + else if (msg.level == "WARNING") m_warningCount++; + else if (msg.level == "ERROR" || msg.level == "CRITICAL") m_errorCount++; + } + + // Info filter + ImVec4 infoBtnColor = m_showInfo ? ImVec4(0.176f, 0.549f, 0.941f, 0.8f) : ImVec4(0.3f, 0.3f, 0.3f, 1.0f); + ImGui::PushStyleColor(ImGuiCol_Button, infoBtnColor); + char infoLabel[64]; snprintf(infoLabel, sizeof(infoLabel), ICON_FA_INFO_CIRCLE " %d", m_infoCount); + if (ImGui::Button(infoLabel)) m_showInfo = !m_showInfo; + ImGui::PopStyleColor(); + if (ImGui::IsItemHovered()) ImGui::SetTooltip("Toggle Info messages"); + + ImGui::SameLine(); + + // Warning filter + ImVec4 warnBtnColor = m_showWarning ? ImVec4(0.961f, 0.651f, 0.137f, 0.8f) : ImVec4(0.3f, 0.3f, 0.3f, 1.0f); + ImGui::PushStyleColor(ImGuiCol_Button, warnBtnColor); + char warnLabel[64]; snprintf(warnLabel, sizeof(warnLabel), ICON_FA_EXCLAMATION " %d", m_warningCount); + if (ImGui::Button(warnLabel)) m_showWarning = !m_showWarning; + ImGui::PopStyleColor(); + if (ImGui::IsItemHovered()) ImGui::SetTooltip("Toggle Warning messages"); + + ImGui::SameLine(); + + // Error filter + ImVec4 errBtnColor = m_showError ? ImVec4(0.898f, 0.224f, 0.208f, 0.8f) : ImVec4(0.3f, 0.3f, 0.3f, 1.0f); + ImGui::PushStyleColor(ImGuiCol_Button, errBtnColor); + char errLabel[64]; snprintf(errLabel, sizeof(errLabel), ICON_FA_TIMES " %d", m_errorCount); + if (ImGui::Button(errLabel)) m_showError = !m_showError; + ImGui::PopStyleColor(); + if (ImGui::IsItemHovered()) ImGui::SetTooltip("Toggle Error messages"); + + ImGui::SameLine(); + ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); + ImGui::SameLine(); + // External console controls RenderExternalConsoleControls(); - + ImGui::Separator(); - + // Auto-connect settings RenderAutoConnectSettings(); - + ImGui::Separator(); - + // Messages area RenderMessages(); - + ImGui::Separator(); - + // Command input RenderCommandInput(); } @@ -456,27 +498,58 @@ void SimpleConsolePanel::Clear() { void SimpleConsolePanel::RenderMessages() { const float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footer_height_to_reserve), false, ImGuiWindowFlags_HorizontalScrollbar); - + + // Use monospace font for log messages + ImFont* monoFont = EditorFonts::GetMonospace(); + if (monoFont) ImGui::PushFont(monoFont); + for (const auto& msg : m_messages) { - ImVec4 color = ImVec4(1, 1, 1, 1); // White - if (msg.level == "WARNING") color = ImVec4(1, 1, 0, 1); // Yellow - else if (msg.level == "ERROR") color = ImVec4(1, 0, 0, 1); // Red - else if (msg.level == "SUCCESS") color = ImVec4(0, 1, 0, 1); // Green - else if (msg.level == "CRITICAL") color = ImVec4(1, 0.5f, 0, 1); // Orange - else if (msg.level == "TRACE") color = ImVec4(0.7f, 0.7f, 1, 1); // Light Blue - - ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "[%s]", msg.timestamp.c_str()); + // Apply filter + bool isInfo = (msg.level == "INFO" || msg.level == "RESULT" || msg.level == "COMMAND" || msg.level == "TRACE" || msg.level == "SUCCESS"); + bool isWarning = (msg.level == "WARNING"); + bool isError = (msg.level == "ERROR" || msg.level == "CRITICAL"); + + if (isInfo && !m_showInfo) continue; + if (isWarning && !m_showWarning) continue; + if (isError && !m_showError) continue; + + // Color by level + ImVec4 color(0.88f, 0.89f, 0.92f, 1.0f); // Default: cool white + const char* icon = ICON_FA_INFO_CIRCLE; + if (msg.level == "WARNING") { + color = ImVec4(0.96f, 0.80f, 0.28f, 1.0f); + icon = ICON_FA_EXCLAMATION; + } else if (msg.level == "ERROR") { + color = ImVec4(0.90f, 0.30f, 0.28f, 1.0f); + icon = ICON_FA_TIMES; + } else if (msg.level == "CRITICAL") { + color = ImVec4(1.0f, 0.45f, 0.0f, 1.0f); + icon = ICON_FA_BOMB; + } else if (msg.level == "SUCCESS") { + color = ImVec4(0.30f, 0.80f, 0.32f, 1.0f); + icon = ICON_FA_CHECK; + } else if (msg.level == "COMMAND") { + color = ImVec4(0.55f, 0.75f, 1.0f, 1.0f); + icon = ICON_FA_CHEVRON_RIGHT; + } else if (msg.level == "TRACE") { + color = ImVec4(0.60f, 0.60f, 0.85f, 1.0f); + icon = ICON_FA_BUG; + } + + ImGui::TextColored(ImVec4(0.45f, 0.48f, 0.52f, 1.0f), "[%s]", msg.timestamp.c_str()); ImGui::SameLine(); - ImGui::TextColored(color, "[%s]", msg.level.c_str()); + ImGui::TextColored(color, "%s", icon); ImGui::SameLine(); - ImGui::TextWrapped("%s", msg.message.c_str()); + ImGui::TextColored(color, "%s", msg.message.c_str()); } - + + if (monoFont) ImGui::PopFont(); + if (m_scrollToBottom && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) { ImGui::SetScrollHereY(1.0f); m_scrollToBottom = false; } - + ImGui::EndChild(); } diff --git a/SparkEditor/Source/Panels/SimpleConsolePanel.h b/SparkEditor/Source/Panels/SimpleConsolePanel.h index 48d41b40e..fd521a0d3 100644 --- a/SparkEditor/Source/Panels/SimpleConsolePanel.h +++ b/SparkEditor/Source/Panels/SimpleConsolePanel.h @@ -94,6 +94,13 @@ class SimpleConsolePanel : public EditorPanel { std::vector m_commandHistory; char m_commandBuffer[512]; bool m_scrollToBottom = true; + bool m_showInfo = true; + bool m_showWarning = true; + bool m_showError = true; + bool m_showSuccess = true; + int m_infoCount = 0; + int m_warningCount = 0; + int m_errorCount = 0; bool m_externalConsoleConnected = false; bool m_autoConnect = false; // Default to false for debugger compatibility bool m_autoConnectAttempted = false; diff --git a/SparkEditor/Source/Panels/SimpleHierarchyPanel.cpp b/SparkEditor/Source/Panels/SimpleHierarchyPanel.cpp index dc3a0bd87..9115062a2 100644 --- a/SparkEditor/Source/Panels/SimpleHierarchyPanel.cpp +++ b/SparkEditor/Source/Panels/SimpleHierarchyPanel.cpp @@ -6,8 +6,12 @@ */ #include "SimpleHierarchyPanel.h" +#include "../Core/EditorIcons.h" +#include "../Core/EditorFonts.h" #include #include +#include +#include namespace SparkEditor { @@ -38,55 +42,107 @@ void SimpleHierarchyPanel::Render() { if (!IsVisible()) return; if (BeginPanel()) { - // Toolbar - if (ImGui::Button("Create")) { + // Toolbar with icon buttons + if (ImGui::Button(ICON_FA_PLUS " Create")) { ImGui::OpenPopup("CreateObject"); } ImGui::SameLine(); - if (ImGui::Button("Delete") && !m_selectedObject.empty()) { + if (ImGui::Button(ICON_FA_TRASH " Delete") && !m_selectedObject.empty()) { DeleteObject(m_selectedObject); } - + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_COPY " Duplicate") && !m_selectedObject.empty()) { + CreateObject(m_selectedObject); + } + + // Search filter bar + ImGui::SetNextItemWidth(-1); + ImGui::InputTextWithHint("##HierarchySearch", ICON_FA_SEARCH " Search objects...", m_searchFilter, sizeof(m_searchFilter)); + ImGui::Separator(); - + // Object list - for (const auto& object : m_sceneObjects) { - ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; - + ImGui::BeginChild("##ObjectList"); + for (size_t i = 0; i < m_sceneObjects.size(); ++i) { + const auto& object = m_sceneObjects[i]; + + // Apply search filter + if (m_searchFilter[0] != '\0') { + std::string lower = object; + std::string filter = m_searchFilter; + std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); + std::transform(filter.begin(), filter.end(), filter.begin(), ::tolower); + if (lower.find(filter) == std::string::npos) continue; + } + + ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen | + ImGuiTreeNodeFlags_SpanAvailWidth; + if (object == m_selectedObject) { flags |= ImGuiTreeNodeFlags_Selected; } - - // Object icon - const char* icon = "??"; - if (object.find("Camera") != std::string::npos) icon = "??"; - else if (object.find("Light") != std::string::npos) icon = "??"; - else if (object.find("Player") != std::string::npos) icon = "??"; - - ImGui::TreeNodeEx(object.c_str(), flags, "%s %s", icon, object.c_str()); - + + // Object icon based on type (FontAwesome) + const char* icon = ICON_FA_CUBE; + if (object.find("Camera") != std::string::npos) icon = ICON_FA_CAMERA; + else if (object.find("Light") != std::string::npos) icon = ICON_FA_LIGHTBULB; + else if (object.find("Player") != std::string::npos) icon = ICON_FA_CROSSHAIRS; + else if (object.find("Plane") != std::string::npos || object.find("Ground") != std::string::npos) icon = ICON_FA_VECTOR_SQUARE; + else if (object.find("Sphere") != std::string::npos) icon = ICON_FA_CIRCLE; + + ImGui::PushID(static_cast(i)); + ImGui::TreeNodeEx("##obj", flags, "%s %s", icon, object.c_str()); + if (ImGui::IsItemClicked()) { m_selectedObject = object; } + + // Right-click context menu + if (ImGui::BeginPopupContextItem("##ObjContext")) { + m_selectedObject = object; + if (ImGui::MenuItem(ICON_FA_COPY " Duplicate")) { + CreateObject(object); + } + if (ImGui::MenuItem(ICON_FA_TRASH " Delete")) { + DeleteObject(object); + ImGui::EndPopup(); + ImGui::PopID(); + break; + } + ImGui::Separator(); + if (ImGui::MenuItem(ICON_FA_EYE " Toggle Visibility")) { + // Toggle visibility placeholder + } + ImGui::EndPopup(); + } + ImGui::PopID(); } - + ImGui::EndChild(); + // Create object popup if (ImGui::BeginPopup("CreateObject")) { - if (ImGui::MenuItem("Empty GameObject")) { + if (ImGui::MenuItem(ICON_FA_CUBE " Empty GameObject")) { CreateObject("Empty GameObject"); } - if (ImGui::BeginMenu("3D Object")) { - if (ImGui::MenuItem("Cube")) CreateObject("Cube"); - if (ImGui::MenuItem("Sphere")) CreateObject("Sphere"); - if (ImGui::MenuItem("Plane")) CreateObject("Plane"); + if (ImGui::BeginMenu(ICON_FA_SHAPES " 3D Object")) { + if (ImGui::MenuItem(ICON_FA_CUBE " Cube")) CreateObject("Cube"); + if (ImGui::MenuItem(ICON_FA_CIRCLE " Sphere")) CreateObject("Sphere"); + if (ImGui::MenuItem(ICON_FA_VECTOR_SQUARE " Plane")) CreateObject("Plane"); ImGui::EndMenu(); } - if (ImGui::BeginMenu("Light")) { + if (ImGui::BeginMenu(ICON_FA_LIGHTBULB " Light")) { if (ImGui::MenuItem("Directional Light")) CreateObject("Directional Light"); if (ImGui::MenuItem("Point Light")) CreateObject("Point Light"); + if (ImGui::MenuItem("Spot Light")) CreateObject("Spot Light"); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu(ICON_FA_CROSSHAIRS " FPS")) { + if (ImGui::MenuItem(ICON_FA_CROSSHAIRS " Player Spawn")) CreateObject("Player Spawn"); + if (ImGui::MenuItem(ICON_FA_FLAG " Capture Point")) CreateObject("Capture Point"); + if (ImGui::MenuItem(ICON_FA_SHIELD " Cover Point")) CreateObject("Cover Point"); ImGui::EndMenu(); } - if (ImGui::MenuItem("Camera")) CreateObject("Camera"); + if (ImGui::MenuItem(ICON_FA_CAMERA " Camera")) CreateObject("Camera"); ImGui::EndPopup(); } } diff --git a/SparkEditor/Source/Panels/SimpleHierarchyPanel.h b/SparkEditor/Source/Panels/SimpleHierarchyPanel.h index 1e2b6e661..c067e0f87 100644 --- a/SparkEditor/Source/Panels/SimpleHierarchyPanel.h +++ b/SparkEditor/Source/Panels/SimpleHierarchyPanel.h @@ -52,6 +52,7 @@ class SimpleHierarchyPanel : public EditorPanel { private: std::vector m_sceneObjects; std::string m_selectedObject; + char m_searchFilter[256] = {0}; }; } // namespace SparkEditor \ No newline at end of file diff --git a/SparkEditor/Source/Profiler/PerformanceProfiler.cpp b/SparkEditor/Source/Profiler/PerformanceProfiler.cpp new file mode 100644 index 000000000..148f0d5de --- /dev/null +++ b/SparkEditor/Source/Profiler/PerformanceProfiler.cpp @@ -0,0 +1,566 @@ +/** + * @file PerformanceProfiler.cpp + * @brief Performance profiling panel with real-time graphs + * @author Spark Engine Team + * @date 2025 + */ + +#include "PerformanceProfiler.h" +#include "../Core/EditorIcons.h" +#include "../Core/EditorFonts.h" +#include +#include +#include +#include +#include + +namespace SparkEditor { + +// Global profiler pointer +PerformanceProfiler* g_profiler = nullptr; + +// ---- PerformanceCounter ---- +void PerformanceCounter::AddSample(float value) { + currentValue = value; + if (value < minValue) minValue = value; + if (value > maxValue) maxValue = value; + history.push_back(value); + if ((int)history.size() > historySize) + history.erase(history.begin()); + + // Running average + if (!history.empty()) { + float sum = 0.0f; + for (float v : history) sum += v; + averageValue = sum / (float)history.size(); + } + lastUpdate = std::chrono::steady_clock::now(); +} + +void PerformanceCounter::Clear() { + currentValue = 0.0f; + minValue = FLT_MAX; + maxValue = -FLT_MAX; + averageValue = 0.0f; + history.clear(); +} + +float PerformanceCounter::GetSmoothedValue(float smoothingFactor) const { + if (history.size() < 2) return currentValue; + float prev = history[history.size() - 2]; + return prev + smoothingFactor * (currentValue - prev); +} + +// ---- CPUProfileSample ---- +float CPUProfileSample::GetSelfTime() const { + float childTime = 0.0f; + for (const auto& child : children) { + childTime += child->GetTotalTime(); + } + return duration - childTime; +} + +float CPUProfileSample::GetTotalTime() const { + return duration; +} + +// ---- ProfileScope ---- +ProfileScope::ProfileScope(const std::string& name, const std::string& category) + : m_name(name) + , m_startTime(std::chrono::high_resolution_clock::now()) +{ + if (g_profiler && g_profiler->IsProfiling()) { + g_profiler->BeginCPUSample(name, category); + } +} + +ProfileScope::~ProfileScope() { + End(); +} + +void ProfileScope::End() { + if (!m_ended) { + m_ended = true; + // Simple timing — actual integration would end the CPU sample + } +} + +// ---- PerformanceProfiler ---- + +PerformanceProfiler::PerformanceProfiler() + : EditorPanel("Profiler", "performance_profiler_panel") +{ + g_profiler = this; +} + +PerformanceProfiler::~PerformanceProfiler() { + if (g_profiler == this) g_profiler = nullptr; +} + +bool PerformanceProfiler::Initialize() { + std::cout << "Initializing Performance Profiler\n"; + + m_currentFrame = std::make_unique(); + + // Create default counters + AddPerformanceCounter("Frame Time", ProfilerSampleType::CPU_SAMPLE, "ms"); + AddPerformanceCounter("CPU Time", ProfilerSampleType::CPU_SAMPLE, "ms"); + AddPerformanceCounter("GPU Time", ProfilerSampleType::GPU_SAMPLE, "ms"); + AddPerformanceCounter("Draw Calls", ProfilerSampleType::RENDERING_SAMPLE, ""); + AddPerformanceCounter("Triangles", ProfilerSampleType::RENDERING_SAMPLE, "K"); + AddPerformanceCounter("Memory", ProfilerSampleType::MEMORY_SAMPLE, "MB"); + + m_isProfiling = true; + return true; +} + +void PerformanceProfiler::Update(float deltaTime) { + if (!m_isProfiling) return; + + m_currentFrameNumber++; + + // Simulate profiling data + float frameMs = deltaTime * 1000.0f; + if (m_currentFrame) { + m_currentFrame->frameNumber = m_currentFrameNumber; + m_currentFrame->frameTime = frameMs; + m_currentFrame->cpuTime = frameMs * 0.6f + ((float)(rand() % 100) / 100.0f) * 2.0f; + m_currentFrame->gpuTime = frameMs * 0.4f + ((float)(rand() % 100) / 100.0f) * 1.5f; + m_currentFrame->renderTime = m_currentFrame->gpuTime * 0.8f; + m_currentFrame->updateTime = m_currentFrame->cpuTime * 0.3f; + m_currentFrame->physicsTime = m_currentFrame->cpuTime * 0.15f; + m_currentFrame->drawCalls = 150 + rand() % 50; + m_currentFrame->triangles = 45000 + rand() % 15000; + m_currentFrame->fps = frameMs > 0.001f ? 1000.0f / frameMs : 0.0f; + m_currentFrame->systemMemoryUsage = 256 * 1024 * 1024 + rand() % (64 * 1024 * 1024); + m_currentFrame->videoMemoryUsage = 128 * 1024 * 1024 + rand() % (32 * 1024 * 1024); + m_currentFrame->activeObjects = 120 + rand() % 30; + m_currentFrame->visibleObjects = 80 + rand() % 20; + } + + // Update counters + if (m_performanceCounters.size() >= 6) { + m_performanceCounters[0].AddSample(frameMs); + m_performanceCounters[1].AddSample(m_currentFrame->cpuTime); + m_performanceCounters[2].AddSample(m_currentFrame->gpuTime); + m_performanceCounters[3].AddSample((float)m_currentFrame->drawCalls); + m_performanceCounters[4].AddSample((float)m_currentFrame->triangles / 1000.0f); + m_performanceCounters[5].AddSample((float)m_currentFrame->systemMemoryUsage / (1024.0f * 1024.0f)); + } +} + +void PerformanceProfiler::Render() { + if (!IsVisible()) return; + + if (BeginPanel()) { + // Toolbar + if (m_isProfiling) { + if (ImGui::Button(ICON_FA_PAUSE " Pause")) StopProfiling(); + } else { + if (ImGui::Button(ICON_FA_PLAY " Record")) StartProfiling(); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_TRASH " Clear")) ClearProfilingData(); + ImGui::SameLine(); + + ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); + ImGui::SameLine(); + + // Quick stats + if (m_currentFrame) { + float fps = m_currentFrame->fps; + ImVec4 fpsColor = fps >= 60 ? ImVec4(0.3f, 0.8f, 0.3f, 1.0f) + : fps >= 30 ? ImVec4(0.9f, 0.9f, 0.3f, 1.0f) + : ImVec4(0.9f, 0.3f, 0.3f, 1.0f); + ImGui::TextColored(fpsColor, "%.0f FPS", fps); + ImGui::SameLine(); + ImGui::Text("| %.1fms | CPU: %.1fms | GPU: %.1fms | Draw: %d", + m_currentFrame->frameTime, m_currentFrame->cpuTime, + m_currentFrame->gpuTime, m_currentFrame->drawCalls); + } + + ImGui::Separator(); + + // Tab bar for different profiler views + if (ImGui::BeginTabBar("ProfilerTabs")) { + if (ImGui::BeginTabItem(ICON_FA_CHART_BAR " Overview")) { + RenderOverviewPanel(); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem(ICON_FA_MICROCHIP " CPU")) { + RenderCPUProfilerPanel(); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem(ICON_FA_TV " GPU")) { + RenderGPUProfilerPanel(); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem(ICON_FA_MEMORY " Memory")) { + RenderMemoryProfilerPanel(); + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); + } + } + EndPanel(); +} + +void PerformanceProfiler::Shutdown() { + std::cout << "Shutting down Performance Profiler\n"; + m_isProfiling = false; + if (g_profiler == this) g_profiler = nullptr; +} + +bool PerformanceProfiler::HandleEvent(const std::string& eventType, void* eventData) { + return false; +} + +void PerformanceProfiler::StartProfiling() { m_isProfiling = true; } +void PerformanceProfiler::StopProfiling() { m_isProfiling = false; } + +uint32_t PerformanceProfiler::BeginCPUSample(const std::string& name, const std::string& category) { + std::lock_guard lock(m_cpuSampleMutex); + uint32_t id = m_nextCPUSampleID++; + auto sample = std::make_unique(); + sample->name = name; + sample->category = category; + sample->startTime = std::chrono::high_resolution_clock::now(); + m_cpuSampleMap[id] = sample.get(); + m_activeCPUSamples.push_back(std::move(sample)); + return id; +} + +void PerformanceProfiler::EndCPUSample(uint32_t sampleID) { + std::lock_guard lock(m_cpuSampleMutex); + auto it = m_cpuSampleMap.find(sampleID); + if (it != m_cpuSampleMap.end()) { + it->second->endTime = std::chrono::high_resolution_clock::now(); + it->second->duration = std::chrono::duration( + it->second->endTime - it->second->startTime).count(); + } +} + +void PerformanceProfiler::BeginGPUSample(const std::string& name, const std::string& shaderName) { + GPUProfileSample sample; + sample.name = name; + sample.shaderName = shaderName; + m_activeGPUSamples[name] = sample; +} + +void PerformanceProfiler::EndGPUSample(const std::string& name) { + m_activeGPUSamples.erase(name); +} + +void PerformanceProfiler::RecordMemoryAllocation(const std::string& category, size_t bytes, void* pointer) { + std::lock_guard lock(m_memoryMutex); + if (pointer) m_memoryAllocations[pointer] = {category, bytes}; + m_memoryCategories[category].allocatedBytes += bytes; + m_memoryCategories[category].allocationCount++; + m_memoryCategories[category].totalAllocatedBytes += bytes; + if (m_memoryCategories[category].allocatedBytes > m_memoryCategories[category].peakBytes) + m_memoryCategories[category].peakBytes = m_memoryCategories[category].allocatedBytes; +} + +void PerformanceProfiler::RecordMemoryDeallocation(void* pointer) { + std::lock_guard lock(m_memoryMutex); + auto it = m_memoryAllocations.find(pointer); + if (it != m_memoryAllocations.end()) { + m_memoryCategories[it->second.first].allocatedBytes -= it->second.second; + m_memoryCategories[it->second.first].deallocationCount++; + m_memoryAllocations.erase(it); + } +} + +uint32_t PerformanceProfiler::AddPerformanceCounter(const std::string& name, ProfilerSampleType type, const std::string& unit) { + PerformanceCounter counter; + counter.name = name; + counter.type = type; + counter.unit = unit; + m_performanceCounters.push_back(counter); + return m_nextCounterID++; +} + +void PerformanceProfiler::UpdatePerformanceCounter(uint32_t counterID, float value) { + if (counterID > 0 && counterID <= m_performanceCounters.size()) { + m_performanceCounters[counterID - 1].AddSample(value); + } +} + +const FrameProfileData* PerformanceProfiler::GetCurrentFrame() const { + return m_currentFrame.get(); +} + +const FrameProfileData* PerformanceProfiler::GetFrame(int frameIndex) const { + if (frameIndex == 0) return m_currentFrame.get(); + if (frameIndex > 0 && frameIndex <= (int)m_frameHistory.size()) + return m_frameHistory[m_frameHistory.size() - frameIndex].get(); + return nullptr; +} + +bool PerformanceProfiler::ApplyOptimization(int suggestionIndex) { return false; } + +bool PerformanceProfiler::ExportProfilingData(const std::string& filePath, const std::string& format) { + return false; // Stub +} + +bool PerformanceProfiler::ImportProfilingData(const std::string& filePath) { + return false; // Stub +} + +void PerformanceProfiler::ClearProfilingData() { + m_frameHistory.clear(); + for (auto& counter : m_performanceCounters) counter.Clear(); + m_detectedBottlenecks.clear(); + m_optimizationSuggestions.clear(); + m_currentFrameNumber = 0; +} + +void PerformanceProfiler::SetConfiguration(const ProfilerConfig& config) { m_config = config; } + +uint32_t PerformanceProfiler::TakeSnapshot(const std::string& name) { + return m_nextSnapshotID++; +} + +std::string PerformanceProfiler::CompareSnapshots(uint32_t snapshot1, uint32_t snapshot2) { + return "Snapshot comparison not yet implemented"; +} + +std::string PerformanceProfiler::GetTrendAnalysis(const std::string& metric, float timespan) { + return "Trend analysis not yet implemented"; +} + +// ---- Rendering ---- + +void PerformanceProfiler::RenderOverviewPanel() { + if (!m_currentFrame) return; + + // Frame time graph + ImGui::Text(ICON_FA_CHART_LINE " Frame Time History"); + if (!m_performanceCounters.empty() && !m_performanceCounters[0].history.empty()) { + const auto& history = m_performanceCounters[0].history; + float maxVal = *std::max_element(history.begin(), history.end()); + maxVal = std::max(maxVal, 33.3f); // At least show up to 30fps line + + ImGui::PlotLines("##FrameTime", history.data(), (int)history.size(), + 0, nullptr, 0.0f, maxVal, ImVec2(-1, 80)); + + // Budget line info + ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), + "Target: 16.7ms (60fps) | Avg: %.1fms | Min: %.1fms | Max: %.1fms", + m_performanceCounters[0].averageValue, + m_performanceCounters[0].minValue, + m_performanceCounters[0].maxValue); + } + + ImGui::Spacing(); + + // Summary table + if (ImGui::BeginTable("OverviewTable", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + ImGui::TableSetupColumn("Metric"); + ImGui::TableSetupColumn("Current"); + ImGui::TableSetupColumn("Average"); + ImGui::TableSetupColumn("Budget"); + ImGui::TableHeadersRow(); + + auto Row = [](const char* name, float current, float avg, float budget, const char* unit) { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text("%s", name); + ImGui::TableSetColumnIndex(1); + ImVec4 color = current <= budget ? ImVec4(0.3f, 0.8f, 0.3f, 1.0f) : ImVec4(0.9f, 0.3f, 0.3f, 1.0f); + ImGui::TextColored(color, "%.1f%s", current, unit); + ImGui::TableSetColumnIndex(2); + ImGui::Text("%.1f%s", avg, unit); + ImGui::TableSetColumnIndex(3); + ImGui::Text("%.1f%s", budget, unit); + }; + + Row("Frame Time", m_currentFrame->frameTime, + m_performanceCounters.size() > 0 ? m_performanceCounters[0].averageValue : 0, + 16.67f, "ms"); + Row("CPU Time", m_currentFrame->cpuTime, + m_performanceCounters.size() > 1 ? m_performanceCounters[1].averageValue : 0, + m_config.cpuBudget, "ms"); + Row("GPU Time", m_currentFrame->gpuTime, + m_performanceCounters.size() > 2 ? m_performanceCounters[2].averageValue : 0, + m_config.gpuBudget, "ms"); + + ImGui::EndTable(); + } + + ImGui::Spacing(); + + // Rendering stats + ImGui::Text(ICON_FA_PAINT_BRUSH " Rendering"); + ImGui::Text("Draw Calls: %d | Triangles: %dK | Objects: %d/%d visible", + m_currentFrame->drawCalls, m_currentFrame->triangles / 1000, + m_currentFrame->visibleObjects, m_currentFrame->activeObjects); + + // Memory stats + ImGui::Spacing(); + ImGui::Text(ICON_FA_MEMORY " Memory"); + ImGui::Text("System: %.1f MB | Video: %.1f MB", + m_currentFrame->systemMemoryUsage / (1024.0f * 1024.0f), + m_currentFrame->videoMemoryUsage / (1024.0f * 1024.0f)); +} + +void PerformanceProfiler::RenderCPUProfilerPanel() { + if (!m_currentFrame) return; + + ImGui::Text(ICON_FA_MICROCHIP " CPU Breakdown"); + ImGui::Separator(); + + // Simulated CPU breakdown bars + struct TimingEntry { const char* name; float ms; ImVec4 color; }; + TimingEntry entries[] = { + { "Update", m_currentFrame->updateTime, ImVec4(0.3f, 0.6f, 0.9f, 1.0f) }, + { "Physics", m_currentFrame->physicsTime, ImVec4(0.2f, 0.8f, 0.4f, 1.0f) }, + { "Render Prep", m_currentFrame->cpuTime * 0.25f, ImVec4(0.9f, 0.6f, 0.2f, 1.0f) }, + { "Audio", m_currentFrame->audioTime, ImVec4(0.7f, 0.3f, 0.7f, 1.0f) }, + { "Scripts", m_currentFrame->cpuTime * 0.15f, ImVec4(0.9f, 0.9f, 0.3f, 1.0f) }, + }; + + float totalCpu = m_currentFrame->cpuTime; + for (const auto& entry : entries) { + float pct = totalCpu > 0 ? (entry.ms / totalCpu) : 0; + ImGui::TextColored(entry.color, ICON_FA_SQUARE); + ImGui::SameLine(); + ImGui::Text("%-12s", entry.name); + ImGui::SameLine(160); + ImGui::ProgressBar(pct, ImVec2(200, 16), ""); + ImGui::SameLine(); + ImGui::Text("%.2fms (%.0f%%)", entry.ms, pct * 100); + } + + // CPU time graph + ImGui::Spacing(); + if (m_performanceCounters.size() > 1 && !m_performanceCounters[1].history.empty()) { + ImGui::Text("CPU Time History"); + ImGui::PlotLines("##CPUTime", m_performanceCounters[1].history.data(), + (int)m_performanceCounters[1].history.size(), 0, nullptr, 0, 20, ImVec2(-1, 60)); + } +} + +void PerformanceProfiler::RenderGPUProfilerPanel() { + if (!m_currentFrame) return; + + ImGui::Text(ICON_FA_TV " GPU Breakdown"); + ImGui::Separator(); + + struct GPUEntry { const char* name; float ms; ImVec4 color; }; + GPUEntry entries[] = { + { "Shadow Pass", m_currentFrame->gpuTime * 0.25f, ImVec4(0.4f, 0.4f, 0.7f, 1.0f) }, + { "G-Buffer", m_currentFrame->gpuTime * 0.20f, ImVec4(0.3f, 0.7f, 0.5f, 1.0f) }, + { "Lighting", m_currentFrame->gpuTime * 0.30f, ImVec4(0.9f, 0.7f, 0.2f, 1.0f) }, + { "Post Process", m_currentFrame->gpuTime * 0.15f, ImVec4(0.7f, 0.3f, 0.5f, 1.0f) }, + { "UI", m_currentFrame->gpuTime * 0.10f, ImVec4(0.5f, 0.5f, 0.8f, 1.0f) }, + }; + + float totalGpu = m_currentFrame->gpuTime; + for (const auto& entry : entries) { + float pct = totalGpu > 0 ? (entry.ms / totalGpu) : 0; + ImGui::TextColored(entry.color, ICON_FA_SQUARE); + ImGui::SameLine(); + ImGui::Text("%-14s", entry.name); + ImGui::SameLine(170); + ImGui::ProgressBar(pct, ImVec2(200, 16), ""); + ImGui::SameLine(); + ImGui::Text("%.2fms (%.0f%%)", entry.ms, pct * 100); + } + + ImGui::Spacing(); + if (m_performanceCounters.size() > 2 && !m_performanceCounters[2].history.empty()) { + ImGui::Text("GPU Time History"); + ImGui::PlotLines("##GPUTime", m_performanceCounters[2].history.data(), + (int)m_performanceCounters[2].history.size(), 0, nullptr, 0, 20, ImVec2(-1, 60)); + } +} + +void PerformanceProfiler::RenderMemoryProfilerPanel() { + if (!m_currentFrame) return; + + ImGui::Text(ICON_FA_MEMORY " Memory Overview"); + ImGui::Separator(); + + float sysMB = m_currentFrame->systemMemoryUsage / (1024.0f * 1024.0f); + float vidMB = m_currentFrame->videoMemoryUsage / (1024.0f * 1024.0f); + float budgetMB = m_config.memoryBudget / (1024.0f * 1024.0f); + + // Memory bars + ImGui::Text("System RAM:"); + ImGui::SameLine(120); + ImGui::ProgressBar(sysMB / budgetMB, ImVec2(300, 18), ""); + ImGui::SameLine(); + ImGui::Text("%.0f / %.0f MB", sysMB, budgetMB); + + ImGui::Text("Video RAM:"); + ImGui::SameLine(120); + ImGui::ProgressBar(vidMB / 1024.0f, ImVec2(300, 18), ""); + ImGui::SameLine(); + ImGui::Text("%.0f / 1024 MB", vidMB); + + // Memory history + ImGui::Spacing(); + if (m_performanceCounters.size() > 5 && !m_performanceCounters[5].history.empty()) { + ImGui::Text("Memory Usage History (MB)"); + ImGui::PlotLines("##MemHistory", m_performanceCounters[5].history.data(), + (int)m_performanceCounters[5].history.size(), 0, nullptr, 0, + budgetMB * 0.5f, ImVec2(-1, 60)); + } + + // Category breakdown + ImGui::Spacing(); + ImGui::Text("Category Breakdown (simulated):"); + if (ImGui::BeginTable("MemTable", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + ImGui::TableSetupColumn("Category"); + ImGui::TableSetupColumn("Allocated"); + ImGui::TableSetupColumn("Peak"); + ImGui::TableHeadersRow(); + + auto MemRow = [](const char* cat, float allocMB, float peakMB) { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); ImGui::Text("%s", cat); + ImGui::TableSetColumnIndex(1); ImGui::Text("%.1f MB", allocMB); + ImGui::TableSetColumnIndex(2); ImGui::Text("%.1f MB", peakMB); + }; + + MemRow("Textures", sysMB * 0.35f, sysMB * 0.40f); + MemRow("Meshes", sysMB * 0.20f, sysMB * 0.22f); + MemRow("Audio", sysMB * 0.08f, sysMB * 0.10f); + MemRow("Scripts", sysMB * 0.05f, sysMB * 0.06f); + MemRow("Physics", sysMB * 0.12f, sysMB * 0.15f); + MemRow("Other", sysMB * 0.20f, sysMB * 0.22f); + + ImGui::EndTable(); + } +} + +void PerformanceProfiler::RenderPerformanceCountersPanel() {} +void PerformanceProfiler::RenderOptimizationPanel() {} +void PerformanceProfiler::RenderConfigurationPanel() {} +void PerformanceProfiler::UpdateFrameData() {} +void PerformanceProfiler::AnalyzePerformance() {} +void PerformanceProfiler::GenerateOptimizationSuggestions() {} +void PerformanceProfiler::DetectCPUBottlenecks() {} +void PerformanceProfiler::DetectGPUBottlenecks() {} +void PerformanceProfiler::DetectMemoryBottlenecks() {} +void PerformanceProfiler::ProcessGPUQueries() {} +void PerformanceProfiler::UpdateMemoryTracking() {} +void PerformanceProfiler::CalculateStatistics() {} + +void PerformanceProfiler::RenderCPUSampleHierarchy(const CPUProfileSample* sample, int depth) { + if (!sample) return; + ImGui::Text("%*s%s: %.2fms", depth * 2, "", sample->name.c_str(), sample->duration); + for (const auto& child : sample->children) { + RenderCPUSampleHierarchy(child.get(), depth + 1); + } +} + +void PerformanceProfiler::RenderPerformanceGraph(const PerformanceCounter& counter, const XMFLOAT2& size) { + if (counter.history.empty()) return; + ImGui::PlotLines(("##" + counter.name).c_str(), counter.history.data(), + (int)counter.history.size(), 0, counter.name.c_str(), 0, + counter.maxValue * 1.2f, ImVec2(size.x, size.y)); +} + +} // namespace SparkEditor diff --git a/SparkEditor/Source/Profiler/PerformanceProfiler.h b/SparkEditor/Source/Profiler/PerformanceProfiler.h index efe03087b..e1e4353a7 100644 --- a/SparkEditor/Source/Profiler/PerformanceProfiler.h +++ b/SparkEditor/Source/Profiler/PerformanceProfiler.h @@ -12,6 +12,8 @@ #pragma once #include "../Core/EditorPanel.h" +#include +#include #include #include #include @@ -23,6 +25,7 @@ #include #include #include +#include using namespace DirectX; diff --git a/SparkEditor/Source/SceneSystem/SceneFile.h b/SparkEditor/Source/SceneSystem/SceneFile.h index 808d99dd3..bb4cd04e9 100644 --- a/SparkEditor/Source/SceneSystem/SceneFile.h +++ b/SparkEditor/Source/SceneSystem/SceneFile.h @@ -16,6 +16,7 @@ #include #include #include +#include "../Enums/SceneSystemEnums.h" using namespace DirectX; @@ -199,20 +200,7 @@ struct AudioSource { int priority = 128; ///< Audio priority (0-255) }; -/** - * @brief Component type enumeration - */ -enum class ComponentType : uint32_t { - TRANSFORM = 0, - MESH_RENDERER = 1, - LIGHT = 2, - CAMERA = 3, - RIGID_BODY = 4, - COLLIDER = 5, - AUDIO_SOURCE = 6, - SCRIPT = 7, - CUSTOM = 1000 // Custom components start at 1000 -}; +// ComponentType is defined in ../Enums/SceneSystemEnums.h to avoid ODR violations /** * @brief Generic component wrapper diff --git a/SparkEditor/Source/VersionControl/VersionControlSystem.h b/SparkEditor/Source/VersionControl/VersionControlSystem.h index b9bac6ea6..59e5212d9 100644 --- a/SparkEditor/Source/VersionControl/VersionControlSystem.h +++ b/SparkEditor/Source/VersionControl/VersionControlSystem.h @@ -23,36 +23,10 @@ #include #include #include +#include "../Enums/VersionControlEnums.h" namespace SparkEditor { -/** - * @brief Version control system types - */ -enum class VCSType { - NONE = 0, ///< No version control - GIT = 1, ///< Git version control - PERFORCE = 2, ///< Perforce version control - SVN = 3, ///< Subversion version control - CUSTOM = 4 ///< Custom version control system -}; - -/** - * @brief File status in version control - */ -enum class FileStatus { - UNTRACKED = 0, ///< File not tracked by VCS - ADDED = 1, ///< File added to VCS - MODIFIED = 2, ///< File modified since last commit - DELETED = 3, ///< File deleted - RENAMED = 4, ///< File renamed - COPIED = 5, ///< File copied - IGNORED = 6, ///< File ignored by VCS - CONFLICTED = 7, ///< File has merge conflicts - LOCKED = 8, ///< File locked by another user - UP_TO_DATE = 9 ///< File up to date -}; - /** * @brief Branch information */ diff --git a/SparkEditor/Source/VisualScripting/VisualScriptingSystem.h b/SparkEditor/Source/VisualScripting/VisualScriptingSystem.h index 06ad084da..5dda2b441 100644 --- a/SparkEditor/Source/VisualScripting/VisualScriptingSystem.h +++ b/SparkEditor/Source/VisualScripting/VisualScriptingSystem.h @@ -272,6 +272,8 @@ struct ScriptNode { * @param context Execution context * @return true if execution succeeded */ + virtual ~ScriptNode() = default; + virtual bool Execute(const std::vector& inputs, std::vector& outputs, class ScriptExecutionContext* context) = 0; diff --git a/SparkEditor/Source/main.cpp b/SparkEditor/Source/main.cpp index c02966b36..bc28f9c09 100644 --- a/SparkEditor/Source/main.cpp +++ b/SparkEditor/Source/main.cpp @@ -80,8 +80,8 @@ int main(int argc, char* argv[]) { config.projectPath = "."; config.enableLogging = true; config.startMaximized = false; // Don't start maximized in debug mode - config.windowWidth = 1200; - config.windowHeight = 800; + config.windowWidth = 1600; + config.windowHeight = 900; console.LogInfo("Editor configuration prepared"); diff --git a/ThirdParty/UI/imgui b/ThirdParty/UI/imgui index 87d7f7744..934c6a5f5 160000 --- a/ThirdParty/UI/imgui +++ b/ThirdParty/UI/imgui @@ -1 +1 @@ -Subproject commit 87d7f7744efe63e77f4d0e00ccb5f6affd12aca7 +Subproject commit 934c6a5f5ef2355d6df25395d555cb71f790c4e9