From 2bb666a08814b4dc74d20e55dbc33fec0c353f0e Mon Sep 17 00:00:00 2001 From: Carlo Pezzotti Date: Wed, 9 Jul 2025 09:07:32 +0200 Subject: [PATCH] feat(gui): Extract and modularize GUI system from main.cpp - Create modular GUI architecture with base Panel class - Implement GUIManager to handle window lifecycle and panel management - Add Window wrapper class for SDL3/OpenGL context management - Create specialized panels: - ConsolePanel: Colored log output with timestamps - CPUPanel: CPU debugging with tabs for registers, memory, and disassembly - PerformancePanel: Real-time FPS and frame time monitoring - Apply modern dark theme with purple accents - Add comprehensive menu bar (File, Emulation, View, Tools, Help) - Update CMakeLists.txt to include new gui/ directory structure - Refactor main.cpp to use the new GUI system - Cutom theme on switch colors --- CMakeLists.txt | 2 + core/main.cpp | 169 ++++++-------------- gui/CMakeLists.txt | 39 +++++ gui/Colors.h | 73 +++++++++ gui/GUIManager.cpp | 275 ++++++++++++++++++++++++++++++++ gui/GUIManager.h | 44 +++++ gui/Panel.h | 27 ++++ gui/Window.cpp | 97 +++++++++++ gui/Window.h | 36 +++++ gui/panels/CPUPanel.cpp | 143 +++++++++++++++++ gui/panels/CPUPanel.h | 32 ++++ gui/panels/ConsolePanel.cpp | 110 +++++++++++++ gui/panels/ConsolePanel.h | 36 +++++ gui/panels/PerformancePanel.cpp | 100 ++++++++++++ gui/panels/PerformancePanel.h | 37 +++++ 15 files changed, 1099 insertions(+), 121 deletions(-) create mode 100644 gui/CMakeLists.txt create mode 100644 gui/Colors.h create mode 100644 gui/GUIManager.cpp create mode 100644 gui/GUIManager.h create mode 100644 gui/Panel.h create mode 100644 gui/Window.cpp create mode 100644 gui/Window.h create mode 100644 gui/panels/CPUPanel.cpp create mode 100644 gui/panels/CPUPanel.h create mode 100644 gui/panels/ConsolePanel.cpp create mode 100644 gui/panels/ConsolePanel.h create mode 100644 gui/panels/PerformancePanel.cpp create mode 100644 gui/panels/PerformancePanel.h diff --git a/CMakeLists.txt b/CMakeLists.txt index e54ccec..e8990b7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,3 +71,5 @@ target_include_directories(Pound PRIVATE find_package(OpenGL REQUIRED) target_link_libraries(Pound PRIVATE OpenGL::GL) +# add ./gui directory +add_subdirectory(gui) \ No newline at end of file diff --git a/core/main.cpp b/core/main.cpp index a27e49a..335c331 100644 --- a/core/main.cpp +++ b/core/main.cpp @@ -1,68 +1,20 @@ // Copyright 2025 Pound Emulator Project. All rights reserved. #include -#include -#include -#include +#include +#include #include "Base/Logging/Backend.h" - -#include -#include - -#include "ARM/cpu.h" #include "Base/Config.h" +#include "ARM/cpu.h" #include "JIT/jit.h" -SDL_Window* Window{}; -SDL_GLContext gl_context{}; -SDL_Event windowEvent; - -void initSDL3() { - - if (!SDL_Init(SDL_INIT_VIDEO)) { - LOG_ERROR(Render, "Error while creating SDL3 Context!"); - } - - SDL_PropertiesID props = SDL_CreateProperties(); - SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, "Pound Emulator"); - SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_CENTERED); - SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, SDL_WINDOWPOS_CENTERED); - SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, Config::windowWidth()); - SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, Config::windowHeight()); - // For a new Vulkan support, don't forget to change 'SDL_WINDOW_OPENGL' by 'SDL_WINDOW_VULKAN'. - SDL_SetNumberProperty(props, "flags", SDL_WINDOW_OPENGL); - SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_RESIZABLE_BOOLEAN, true); - SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN, true); - Window = SDL_CreateWindowWithProperties(props); - SDL_DestroyProperties(props); - - SDL_SetWindowMinimumSize(Window, 640, 480); - - gl_context = SDL_GL_CreateContext(Window); - if (!gl_context) { - LOG_ERROR(Render, "Failed to create OpenGL context: {}", SDL_GetError()); - } - - SDL_GL_MakeCurrent(Window, gl_context); - SDL_GL_SetSwapInterval(1); - - ImGui::CreateContext(); - ImGui::StyleColorsDark(); - ImGui_ImplSDL3_InitForOpenGL(Window, gl_context); - ImGui_ImplOpenGL3_Init("#version 330"); - -} - -void deinitializeSDL3() { - ImGui_ImplOpenGL3_Shutdown(); - ImGui_ImplSDL3_Shutdown(); - ImGui::DestroyContext(); - - SDL_DestroyWindow(Window); - SDL_Quit(); -} +#include "gui/GUIManager.h" +#include "gui/panels/ConsolePanel.h" +#include "gui/panels/CPUPanel.h" +#include "gui/panels/PerformancePanel.h" +// CPU test function void cpuTest() { CPU cpu; cpu.pc = 0; @@ -90,73 +42,48 @@ int main() { const auto config_dir = Base::FS::GetUserPath(Base::FS::PathType::BinaryDir); Config::Load(config_dir / "config.toml"); - initSDL3(); - - bool rendering = true; - bool show_popup = false; // Flag to control popup visibility - - while (rendering) { - // Process events. - while (SDL_PollEvent(&windowEvent)) { - ImGui_ImplSDL3_ProcessEvent(&windowEvent); - - switch (windowEvent.type) { - case SDL_EVENT_QUIT: - rendering = false; - break; - default: - break; - } - } - - ImGui_ImplOpenGL3_NewFrame(); - ImGui_ImplSDL3_NewFrame(); - ImGui::SetNextWindowSizeConstraints(ImVec2(50.0f, 50.0f), ImVec2(FLT_MAX, FLT_MAX)); - ImGui::NewFrame(); - - ImGui::Begin("Pound Emulator"); - - if (ImGui::Button("Run CPU Test")) { - cpuTest(); - show_popup = true; // Trigger the popup after running the test - } - - ImGui::End(); - - // Popup logic - if (show_popup) { - ImGui::OpenPopup("CPU Test Info"); - } - - // Define the modal popup - if (ImGui::BeginPopupModal("CPU Test Info", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::Text("The CPU test has ran in the terminal.\nKeep in mind that Pound is still in pre-alpha state."); - ImGui::Separator(); - - if (ImGui::Button("OK", ImVec2(120, 0))) { - show_popup = false; // Close the popup - ImGui::CloseCurrentPopup(); - } - - ImGui::EndPopup(); - } - - ImGui::Render(); - ImGuiIO& io = ImGui::GetIO(); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f); - ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); - - glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y); - glClearColor(0.1f, 0.1f, 0.1f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - - SDL_GL_SwapWindow(Window); + // Create GUI manager + auto gui_manager = std::make_unique(); + + // Initialize GUI + if (!gui_manager->Initialize("Pound Emulator", Config::windowWidth(), Config::windowHeight())) { + LOG_ERROR(Render, "Failed to initialize GUI"); + return -1; + } + // Create and add panels + auto console_panel = std::make_shared(); + auto cpu_panel = std::make_shared(); + auto performance_panel = std::make_shared(); + + gui_manager->AddPanel(console_panel); + gui_manager->AddPanel(cpu_panel); + gui_manager->AddPanel(performance_panel); + + // Set up callbacks + auto cpu_test_callback = [console_panel]() { + console_panel->AddLog("[INFO] Running CPU test..."); + cpuTest(); + console_panel->AddLog("[INFO] CPU test completed. Check terminal for details."); + }; + + gui_manager->SetCPUTestCallback(cpu_test_callback); + cpu_panel->SetCPUTestCallback(cpu_test_callback); + + // Add initial console message + console_panel->AddLog("[INFO] Pound Emulator started"); + console_panel->AddLog("[INFO] Version: Pre-Alpha"); + + // Main loop + while (gui_manager->IsRunning()) { + gui_manager->RunFrame(); + + // Small delay to prevent excessive CPU usage std::this_thread::sleep_for(std::chrono::milliseconds(5)); } - deinitializeSDL3(); + // Cleanup + gui_manager->Shutdown(); + return 0; -} +} \ No newline at end of file diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt new file mode 100644 index 0000000..ffbae12 --- /dev/null +++ b/gui/CMakeLists.txt @@ -0,0 +1,39 @@ +# Copyright 2025 Pound Emulator Project. All rights reserved. + +# GUI sources +set(GUI_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/Window.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/GUIManager.cpp +) + +set(GUI_HEADERS + ${CMAKE_CURRENT_SOURCE_DIR}/Window.h + ${CMAKE_CURRENT_SOURCE_DIR}/GUIManager.h + ${CMAKE_CURRENT_SOURCE_DIR}/Panel.h +) + +# Panel sources +set(PANEL_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/panels/ConsolePanel.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/panels/CPUPanel.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/panels/PerformancePanel.cpp +) + +set(PANEL_HEADERS + ${CMAKE_CURRENT_SOURCE_DIR}/panels/ConsolePanel.h + ${CMAKE_CURRENT_SOURCE_DIR}/panels/CPUPanel.h + ${CMAKE_CURRENT_SOURCE_DIR}/panels/PerformancePanel.h +) + +# Add all GUI sources to the main target +target_sources(Pound PRIVATE + ${GUI_SOURCES} + ${GUI_HEADERS} + ${PANEL_SOURCES} + ${PANEL_HEADERS} +) + +# Include directories for GUI +target_include_directories(Pound PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/.. +) \ No newline at end of file diff --git a/gui/Colors.h b/gui/Colors.h new file mode 100644 index 0000000..c34abac --- /dev/null +++ b/gui/Colors.h @@ -0,0 +1,73 @@ +// Copyright 2025 Pound Emulator Project. All rights reserved. +#pragma once + +#include + +namespace Pound::GUI { + +class Colors { +public: + // Primary colors + static constexpr ImVec4 Primary = ImVec4(0.0f, 0.765f, 0.890f, 1.0f); // #00c3e3 + static constexpr ImVec4 PrimaryHover = ImVec4(0.0f, 0.865f, 0.990f, 1.0f); // Lighter + static constexpr ImVec4 PrimaryActive = ImVec4(0.0f, 0.665f, 0.790f, 1.0f); // Darker + + // Secondary colors + static constexpr ImVec4 Secondary = ImVec4(1.0f, 0.271f, 0.329f, 1.0f); // #ff4554 + static constexpr ImVec4 SecondaryHover = ImVec4(1.0f, 0.371f, 0.429f, 1.0f); // Lighter + static constexpr ImVec4 SecondaryActive = ImVec4(0.9f, 0.171f, 0.229f, 1.0f); // Darker + + // Background colors + static constexpr ImVec4 Background = ImVec4(0.255f, 0.271f, 0.282f, 1.0f); // #414548 + static constexpr ImVec4 BackgroundDark = ImVec4(0.155f, 0.171f, 0.182f, 1.0f); + static constexpr ImVec4 BackgroundLight = ImVec4(0.355f, 0.371f, 0.382f, 1.0f); + + // Text colors + static constexpr ImVec4 Text = ImVec4(0.95f, 0.96f, 0.98f, 1.0f); + static constexpr ImVec4 TextDisabled = ImVec4(0.60f, 0.60f, 0.60f, 1.0f); + + // UI element colors + static constexpr ImVec4 Border = ImVec4(0.43f, 0.43f, 0.50f, 0.50f); + static constexpr ImVec4 Frame = ImVec4(0.16f, 0.29f, 0.48f, 0.54f); + static constexpr ImVec4 FrameHover = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); + static constexpr ImVec4 FrameActive = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); + + // Special colors + static constexpr ImVec4 Success = ImVec4(0.0f, 0.8f, 0.0f, 1.0f); + static constexpr ImVec4 Warning = ImVec4(1.0f, 0.8f, 0.0f, 1.0f); + static constexpr ImVec4 Error = ImVec4(1.0f, 0.0f, 0.0f, 1.0f); + static constexpr ImVec4 Info = ImVec4(0.0f, 0.765f, 0.890f, 1.0f); + + // Utility functions + static ImVec4 WithAlpha(const ImVec4& color, float alpha) { + return ImVec4(color.x, color.y, color.z, alpha); + } + + static ImVec4 Lighten(const ImVec4& color, float amount = 0.1f) { + return ImVec4( + std::min(1.0f, color.x + amount), + std::min(1.0f, color.y + amount), + std::min(1.0f, color.z + amount), + color.w + ); + } + + static ImVec4 Darken(const ImVec4& color, float amount = 0.1f) { + return ImVec4( + std::max(0.0f, color.x - amount), + std::max(0.0f, color.y - amount), + std::max(0.0f, color.z - amount), + color.w + ); + } + + // Convert hex to ImVec4 (utility for future use) + static ImVec4 FromHex(uint32_t hex, float alpha = 1.0f) { + float r = ((hex >> 16) & 0xFF) / 255.0f; + float g = ((hex >> 8) & 0xFF) / 255.0f; + float b = (hex & 0xFF) / 255.0f; + return ImVec4(r, g, b, alpha); + } +}; + +} // namespace Pound::GUI \ No newline at end of file diff --git a/gui/GUIManager.cpp b/gui/GUIManager.cpp new file mode 100644 index 0000000..39f50f7 --- /dev/null +++ b/gui/GUIManager.cpp @@ -0,0 +1,275 @@ +// Copyright 2025 Pound Emulator Project. All rights reserved. + +#include "GUIManager.h" +#include "Colors.h" +#include "Base/Logging/Log.h" +#include +#include +#include +#include +#include + +namespace Pound::GUI +{ + + GUIManager::GUIManager() = default; + + GUIManager::~GUIManager() + { + Shutdown(); + } + + bool GUIManager::Initialize(const std::string &title, int width, int height) + { + window = std::make_unique(); + + if (!window->Initialize(title, width, height)) + { + LOG_ERROR(Render, "Failed to initialize window"); + return false; + } + + // Initialize ImGui + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO &io = ImGui::GetIO(); + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + + // Setup style + ApplyTheme(); + + // Setup platform/renderer backends + ImGui_ImplSDL3_InitForOpenGL(window->GetSDLWindow(), window->GetGLContext()); + ImGui_ImplOpenGL3_Init("#version 330"); + + running = true; + return true; + } + + void GUIManager::Shutdown() + { + if (!running) + return; + + panels.clear(); + + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplSDL3_Shutdown(); + ImGui::DestroyContext(); + + window.reset(); + running = false; + } + + void GUIManager::RunFrame() + { + if (!running) + return; + + window->ProcessEvents(); + + BeginFrame(); + + // Render menu bar + RenderMainMenuBar(); + + // Render all panels + for (auto &panel : panels) + { + if (panel->IsVisible()) + { + panel->Render(); + } + } + + // Demo window for debugging + if (show_demo_window) + { + ImGui::ShowDemoWindow(&show_demo_window); + } + + EndFrame(); + } + + void GUIManager::BeginFrame() + { + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplSDL3_NewFrame(); + ImGui::NewFrame(); + } + + void GUIManager::EndFrame() + { + ImGui::Render(); + + ImGuiIO &io = ImGui::GetIO(); + glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y); + glClearColor(0.08f, 0.08f, 0.10f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + + window->SwapBuffers(); + } + + void GUIManager::RenderMainMenuBar() + { + if (ImGui::BeginMainMenuBar()) + { + if (ImGui::BeginMenu("File")) + { + if (ImGui::MenuItem("Load ROM...", "Ctrl+O")) + { + // TODO: Implement ROM loading + } + ImGui::Separator(); + if (ImGui::MenuItem("Exit", "Alt+F4")) + { + window->SetShouldClose(true); + } + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu("Emulation")) + { + if (ImGui::MenuItem("Run CPU Test")) + { + if (cpu_test_callback) + { + cpu_test_callback(); + } + } + ImGui::Separator(); + if (ImGui::MenuItem("Pause", "F5")) + { + // TODO: Implement pause + } + if (ImGui::MenuItem("Reset", "F6")) + { + // TODO: Implement reset + } + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu("View")) + { + for (auto &panel : panels) + { + bool visible = panel->IsVisible(); + if (ImGui::MenuItem(panel->GetName().c_str(), nullptr, &visible)) + { + panel->SetVisible(visible); + } + } + ImGui::Separator(); + if (ImGui::MenuItem("ImGui Demo", nullptr, &show_demo_window)) + { + // Toggle handled by flag + } + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu("Tools")) + { + if (ImGui::MenuItem("Settings", "Ctrl+,")) + { + // TODO: Open settings panel + } + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu("Help")) + { + if (ImGui::MenuItem("About")) + { + // TODO: Show about dialog + } + ImGui::EndMenu(); + } + + ImGui::EndMainMenuBar(); + } + } + + void GUIManager::ApplyTheme() + { + ImGuiStyle &style = ImGui::GetStyle(); + + // Modern theme with custom colors + style.WindowRounding = 8.0f; + style.FrameRounding = 4.0f; + style.PopupRounding = 4.0f; + style.ScrollbarRounding = 6.0f; + style.GrabRounding = 4.0f; + style.TabRounding = 4.0f; + + style.WindowTitleAlign = ImVec2(0.5f, 0.5f); + style.WindowMenuButtonPosition = ImGuiDir_Right; + + // Apply custom color scheme + style.Colors[ImGuiCol_Text] = Colors::Text; + style.Colors[ImGuiCol_TextDisabled] = Colors::TextDisabled; + style.Colors[ImGuiCol_WindowBg] = Colors::WithAlpha(Colors::Background, 0.95f); + style.Colors[ImGuiCol_ChildBg] = Colors::BackgroundDark; + style.Colors[ImGuiCol_PopupBg] = Colors::WithAlpha(Colors::Background, 0.94f); + style.Colors[ImGuiCol_Border] = Colors::Border; + style.Colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + style.Colors[ImGuiCol_FrameBg] = Colors::BackgroundLight; + style.Colors[ImGuiCol_FrameBgHovered] = Colors::Lighten(Colors::BackgroundLight, 0.1f); + style.Colors[ImGuiCol_FrameBgActive] = Colors::Lighten(Colors::BackgroundLight, 0.2f); + style.Colors[ImGuiCol_TitleBg] = Colors::BackgroundDark; + style.Colors[ImGuiCol_TitleBgActive] = Colors::Background; + style.Colors[ImGuiCol_TitleBgCollapsed] = Colors::WithAlpha(Colors::BackgroundDark, 0.51f); + style.Colors[ImGuiCol_MenuBarBg] = Colors::BackgroundDark; + style.Colors[ImGuiCol_ScrollbarBg] = Colors::WithAlpha(Colors::BackgroundDark, 0.53f); + style.Colors[ImGuiCol_ScrollbarGrab] = Colors::BackgroundLight; + style.Colors[ImGuiCol_ScrollbarGrabHovered] = Colors::Lighten(Colors::BackgroundLight, 0.1f); + style.Colors[ImGuiCol_ScrollbarGrabActive] = Colors::Lighten(Colors::BackgroundLight, 0.2f); + style.Colors[ImGuiCol_CheckMark] = Colors::Primary; + style.Colors[ImGuiCol_SliderGrab] = Colors::Primary; + style.Colors[ImGuiCol_SliderGrabActive] = Colors::PrimaryActive; + style.Colors[ImGuiCol_Button] = Colors::WithAlpha(Colors::Primary, 0.40f); + style.Colors[ImGuiCol_ButtonHovered] = Colors::PrimaryHover; + style.Colors[ImGuiCol_ButtonActive] = Colors::PrimaryActive; + style.Colors[ImGuiCol_Header] = Colors::WithAlpha(Colors::Primary, 0.31f); + style.Colors[ImGuiCol_HeaderHovered] = Colors::WithAlpha(Colors::Primary, 0.80f); + style.Colors[ImGuiCol_HeaderActive] = Colors::Primary; + style.Colors[ImGuiCol_Separator] = Colors::Border; + style.Colors[ImGuiCol_SeparatorHovered] = Colors::WithAlpha(Colors::Primary, 0.78f); + style.Colors[ImGuiCol_SeparatorActive] = Colors::Primary; + style.Colors[ImGuiCol_ResizeGrip] = Colors::WithAlpha(Colors::Primary, 0.25f); + style.Colors[ImGuiCol_ResizeGripHovered] = Colors::WithAlpha(Colors::Primary, 0.67f); + style.Colors[ImGuiCol_ResizeGripActive] = Colors::WithAlpha(Colors::Primary, 0.95f); + style.Colors[ImGuiCol_Tab] = Colors::BackgroundLight; + style.Colors[ImGuiCol_TabHovered] = Colors::WithAlpha(Colors::Primary, 0.80f); + style.Colors[ImGuiCol_TabActive] = Colors::Primary; + style.Colors[ImGuiCol_TabUnfocused] = Colors::Background; + style.Colors[ImGuiCol_TabUnfocusedActive] = Colors::Lighten(Colors::Background, 0.1f); + style.Colors[ImGuiCol_PlotLines] = Colors::Primary; + style.Colors[ImGuiCol_PlotLinesHovered] = Colors::PrimaryHover; + style.Colors[ImGuiCol_PlotHistogram] = Colors::Secondary; + style.Colors[ImGuiCol_PlotHistogramHovered] = Colors::SecondaryHover; + style.Colors[ImGuiCol_TextSelectedBg] = Colors::WithAlpha(Colors::Primary, 0.35f); + style.Colors[ImGuiCol_DragDropTarget] = Colors::WithAlpha(Colors::Secondary, 0.90f); + style.Colors[ImGuiCol_NavHighlight] = Colors::Primary; + style.Colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); + style.Colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f); + style.Colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f); + } + + void GUIManager::AddPanel(std::shared_ptr panel) + { + panels.push_back(panel); + } + + void GUIManager::RemovePanel(const std::string &name) + { + panels.erase( + std::remove_if(panels.begin(), panels.end(), + [&name](const std::shared_ptr &panel) + { + return panel->GetName() == name; + }), + panels.end()); + } +} \ No newline at end of file diff --git a/gui/GUIManager.h b/gui/GUIManager.h new file mode 100644 index 0000000..5a8ec67 --- /dev/null +++ b/gui/GUIManager.h @@ -0,0 +1,44 @@ +// Copyright 2025 Pound Emulator Project. All rights reserved. +#pragma once + +#include +#include +#include +#include "Window.h" +#include "Panel.h" + +namespace Pound::GUI { + +class GUIManager { +public: + GUIManager(); + ~GUIManager(); + + bool Initialize(const std::string& title, int width, int height); + void Shutdown(); + + void RunFrame(); + bool IsRunning() const { return running && window && !window->ShouldClose(); } + + void AddPanel(std::shared_ptr panel); + void RemovePanel(const std::string& name); + + // Callback for external systems + void SetCPUTestCallback(std::function callback) { cpu_test_callback = callback; } + +private: + void BeginFrame(); + void EndFrame(); + void RenderMainMenuBar(); + void ApplyTheme(); + + std::unique_ptr window; + std::vector> panels; + bool running = false; + bool show_demo_window = false; + + // Callbacks + std::function cpu_test_callback; +}; + +} // namespace Pound::GUI \ No newline at end of file diff --git a/gui/Panel.h b/gui/Panel.h new file mode 100644 index 0000000..87c6269 --- /dev/null +++ b/gui/Panel.h @@ -0,0 +1,27 @@ +// Copyright 2025 Pound Emulator Project. All rights reserved. +#pragma once + +#include +#include + +namespace Pound::GUI +{ + + class Panel + { + public: + Panel(const std::string &name) : name(name) {} + virtual ~Panel() = default; + + virtual void Render() = 0; + + const std::string &GetName() const { return name; } + bool IsVisible() const { return visible; } + void SetVisible(bool vis) { visible = vis; } + + protected: + std::string name; + bool visible = true; + }; + +} // namespace Pound::GUI \ No newline at end of file diff --git a/gui/Window.cpp b/gui/Window.cpp new file mode 100644 index 0000000..7c1a232 --- /dev/null +++ b/gui/Window.cpp @@ -0,0 +1,97 @@ +// Copyright 2025 Pound Emulator Project. All rights reserved. + +#include "Window.h" +#include "Base/Logging/Log.h" +#include +#include +#include + +namespace Pound::GUI +{ + + Window::Window() = default; + + Window::~Window() + { + Shutdown(); + } + + bool Window::Initialize(const std::string &title, int width, int height) + { + if (!SDL_Init(SDL_INIT_VIDEO)) + { + LOG_ERROR(Render, "Error while creating SDL3 Context!"); + return false; + } + + SDL_PropertiesID props = SDL_CreateProperties(); + SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, title.c_str()); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_CENTERED); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, SDL_WINDOWPOS_CENTERED); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, width); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, height); + SDL_SetNumberProperty(props, "flags", SDL_WINDOW_OPENGL); + SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_RESIZABLE_BOOLEAN, true); + SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN, true); + + window = SDL_CreateWindowWithProperties(props); + SDL_DestroyProperties(props); + + if (!window) + { + LOG_ERROR(Render, "Failed to create SDL window: {}", SDL_GetError()); + return false; + } + + SDL_SetWindowMinimumSize(window, 640, 480); + + gl_context = SDL_GL_CreateContext(window); + if (!gl_context) + { + LOG_ERROR(Render, "Failed to create OpenGL context: {}", SDL_GetError()); + return false; + } + + SDL_GL_MakeCurrent(window, gl_context); + SDL_GL_SetSwapInterval(1); + + return true; + } + + void Window::Shutdown() + { + if (gl_context) + { + SDL_GL_DestroyContext(gl_context); + gl_context = nullptr; + } + + if (window) + { + SDL_DestroyWindow(window); + window = nullptr; + } + + SDL_Quit(); + } + + void Window::ProcessEvents() + { + SDL_Event event; + while (SDL_PollEvent(&event)) + { + ImGui_ImplSDL3_ProcessEvent(&event); + + if (event.type == SDL_EVENT_QUIT) + { + should_close = true; + } + } + } + + void Window::SwapBuffers() + { + SDL_GL_SwapWindow(window); + } + +} // namespace Pound::GUI \ No newline at end of file diff --git a/gui/Window.h b/gui/Window.h new file mode 100644 index 0000000..7752d7f --- /dev/null +++ b/gui/Window.h @@ -0,0 +1,36 @@ +// Copyright 2025 Pound Emulator Project. All rights reserved. +#pragma once + +#include +#include +#include +#include + +namespace Pound::GUI +{ + + class Window + { + public: + Window(); + ~Window(); + + bool Initialize(const std::string &title, int width, int height); + void Shutdown(); + + SDL_Window *GetSDLWindow() const { return window; } + SDL_GLContext GetGLContext() const { return gl_context; } + + bool ShouldClose() const { return should_close; } + void SetShouldClose(bool close) { should_close = close; } + + void ProcessEvents(); + void SwapBuffers(); + + private: + SDL_Window *window = nullptr; + SDL_GLContext gl_context = nullptr; + bool should_close = false; + }; + +} // namespace Pound::GUI \ No newline at end of file diff --git a/gui/panels/CPUPanel.cpp b/gui/panels/CPUPanel.cpp new file mode 100644 index 0000000..3a8f057 --- /dev/null +++ b/gui/panels/CPUPanel.cpp @@ -0,0 +1,143 @@ +// Copyright 2025 Pound Emulator Project. All rights reserved. + +#include "CPUPanel.h" +#include "../Colors.h" +#include +#include + +namespace Pound::GUI { + +CPUPanel::CPUPanel() : Panel("CPU Debug") {} + +void CPUPanel::Render() { + if (!ImGui::Begin(name.c_str(), &visible, ImGuiWindowFlags_NoCollapse)) { + ImGui::End(); + return; + } + + // Control buttons with custom colors + ImGui::PushStyleColor(ImGuiCol_Button, Colors::WithAlpha(Colors::Primary, 0.40f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, Colors::PrimaryHover); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, Colors::PrimaryActive); + + if (ImGui::Button("Run CPU Test", ImVec2(120, 0))) { + if (cpu_test_callback) { + cpu_test_callback(); + show_test_result = true; + } + } + + ImGui::SameLine(); + if (ImGui::Button("Step", ImVec2(60, 0))) { + // TODO: Implement step functionality + } + + ImGui::SameLine(); + + // Reset button with secondary color + ImGui::PushStyleColor(ImGuiCol_Button, Colors::WithAlpha(Colors::Secondary, 0.40f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, Colors::SecondaryHover); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, Colors::SecondaryActive); + + if (ImGui::Button("Reset", ImVec2(60, 0))) { + // TODO: Implement reset functionality + } + + ImGui::PopStyleColor(6); // Pop all 6 color changes + + ImGui::Separator(); + + // Tabs for different views + if (ImGui::BeginTabBar("CPUTabBar")) { + if (ImGui::BeginTabItem("Registers")) { + // General purpose registers + ImGui::Text("General Purpose Registers:"); + ImGui::Separator(); + + if (ImGui::BeginTable("RegisterTable", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + ImGui::TableSetupColumn("Register", ImGuiTableColumnFlags_WidthFixed, 60.0f); + ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed, 120.0f); + ImGui::TableSetupColumn("Register", ImGuiTableColumnFlags_WidthFixed, 60.0f); + ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed, 120.0f); + ImGui::TableHeadersRow(); + + for (int i = 0; i < 16; i++) { + ImGui::TableNextRow(); + + // Left column + ImGui::TableSetColumnIndex(0); + ImGui::Text("X%d", i); + ImGui::TableSetColumnIndex(1); + ImGui::Text("0x%016llX", cpu_state.registers[i]); + + // Right column + if (i + 16 < 32) { + ImGui::TableSetColumnIndex(2); + ImGui::Text("X%d", i + 16); + ImGui::TableSetColumnIndex(3); + ImGui::Text("0x%016llX", cpu_state.registers[i + 16]); + } + } + + ImGui::EndTable(); + } + + ImGui::Spacing(); + ImGui::Text("Program Counter: 0x%016llX", cpu_state.pc); + ImGui::Text("Flags: 0x%08X", cpu_state.flags); + + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem("Memory")) { + static char addr_input[17] = "0000000000000000"; + ImGui::Text("Memory Viewer"); + ImGui::Separator(); + + ImGui::InputText("Address", addr_input, sizeof(addr_input), + ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase); + + // TODO: Implement memory viewer + ImGui::Text("Memory viewer will be implemented here"); + + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem("Disassembly")) { + ImGui::Text("Disassembly View"); + ImGui::Separator(); + + // TODO: Implement disassembly view + ImGui::Text("Disassembly will be shown here"); + + ImGui::EndTabItem(); + } + + ImGui::EndTabBar(); + } + + // Test result popup + if (show_test_result) { + ImGui::OpenPopup("CPU Test Result"); + } + + if (ImGui::BeginPopupModal("CPU Test Result", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::Text("The CPU test has been executed successfully!"); + ImGui::Text("Check the console for detailed output."); + ImGui::Separator(); + ImGui::Text("Note: Pound is still in pre-alpha state."); + + ImGui::Spacing(); + + if (ImGui::Button("OK", ImVec2(120, 0))) { + show_test_result = false; + ImGui::CloseCurrentPopup(); + } + + ImGui::EndPopup(); + } + + ImGui::End(); +} + +} // namespace Pound::GUI \ No newline at end of file diff --git a/gui/panels/CPUPanel.h b/gui/panels/CPUPanel.h new file mode 100644 index 0000000..2e92f03 --- /dev/null +++ b/gui/panels/CPUPanel.h @@ -0,0 +1,32 @@ +// Copyright 2025 Pound Emulator Project. All rights reserved. +#pragma once + +#include "../Panel.h" +#include + +namespace Pound::GUI +{ + + class CPUPanel : public Panel + { + public: + CPUPanel(); + + void Render() override; + void SetCPUTestCallback(std::function callback) { cpu_test_callback = callback; } + void ShowTestResult(bool show = true) { show_test_result = show; } + + private: + std::function cpu_test_callback; + bool show_test_result = false; + + // CPU state display (placeholder for future integration) + struct CPUState + { + uint64_t registers[32] = {0}; + uint64_t pc = 0; + uint32_t flags = 0; + } cpu_state; + }; + +} // namespace Pound::GUI \ No newline at end of file diff --git a/gui/panels/ConsolePanel.cpp b/gui/panels/ConsolePanel.cpp new file mode 100644 index 0000000..31b9e28 --- /dev/null +++ b/gui/panels/ConsolePanel.cpp @@ -0,0 +1,110 @@ +// Copyright 2025 Pound Emulator Project. All rights reserved. + +#include "ConsolePanel.h" +#include "../Colors.h" +#include +#include +#include +#include + +namespace Pound::GUI +{ + + ConsolePanel::ConsolePanel() : Panel("Console") {} + + void ConsolePanel::Render() + { + if (!ImGui::Begin(name.c_str(), &visible)) + { + ImGui::End(); + return; + } + + // Options + if (ImGui::BeginPopup("Options")) + { + ImGui::Checkbox("Auto-scroll", &auto_scroll); + ImGui::Checkbox("Show timestamps", &show_timestamps); + ImGui::EndPopup(); + } + + // Buttons + if (ImGui::Button("Options")) + ImGui::OpenPopup("Options"); + ImGui::SameLine(); + if (ImGui::Button("Clear")) + Clear(); + ImGui::SameLine(); + ImGui::Text("Log entries: %zu", log_buffer.size()); + + ImGui::Separator(); + + // Log display + ImGui::BeginChild("ScrollingRegion", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar); + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + + for (const auto &entry : log_buffer) + { + ImGui::PushStyleColor(ImGuiCol_Text, entry.color); + ImGui::TextUnformatted(entry.text.c_str()); + ImGui::PopStyleColor(); + } + + if (auto_scroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) + ImGui::SetScrollHereY(1.0f); + + ImGui::PopStyleVar(); + ImGui::EndChild(); + + ImGui::End(); + } + + void ConsolePanel::AddLog(const std::string &text) + { + std::string final_text = text; + + if (show_timestamps) + { + auto now = std::chrono::system_clock::now(); + auto time_t = std::chrono::system_clock::to_time_t(now); + std::stringstream ss; + ss << "[" << std::put_time(std::localtime(&time_t), "%H:%M:%S") << "] "; + final_text = ss.str() + text; + } + + log_buffer.push_back({final_text, GetLogColor(text)}); + + // Keep buffer size limited + while (log_buffer.size() > MAX_LOG_ENTRIES) + { + log_buffer.pop_front(); + } + } + + void ConsolePanel::Clear() + { + log_buffer.clear(); + } + + ImVec4 ConsolePanel::GetLogColor(const std::string &text) const + { + if (text.find("[ERROR]") != std::string::npos) + { + return Colors::Error; + } + else if (text.find("[WARN]") != std::string::npos) + { + return Colors::Warning; + } + else if (text.find("[INFO]") != std::string::npos) + { + return Colors::Info; + } + else if (text.find("[DEBUG]") != std::string::npos) + { + return Colors::TextDisabled; + } + return Colors::Text; + } +} \ No newline at end of file diff --git a/gui/panels/ConsolePanel.h b/gui/panels/ConsolePanel.h new file mode 100644 index 0000000..e3aeda5 --- /dev/null +++ b/gui/panels/ConsolePanel.h @@ -0,0 +1,36 @@ +// Copyright 2025 Pound Emulator Project. All rights reserved. +#pragma once + +#include "../Panel.h" +#include +#include +#include + +namespace Pound::GUI +{ + + class ConsolePanel : public Panel + { + public: + ConsolePanel(); + + void Render() override; + void AddLog(const std::string &text); + void Clear(); + + private: + struct LogEntry + { + std::string text; + ImVec4 color; + }; + + std::deque log_buffer; + bool auto_scroll = true; + bool show_timestamps = true; + static constexpr size_t MAX_LOG_ENTRIES = 1000; + + ImVec4 GetLogColor(const std::string &text) const; + }; + +} // namespace Pound::GUI \ No newline at end of file diff --git a/gui/panels/PerformancePanel.cpp b/gui/panels/PerformancePanel.cpp new file mode 100644 index 0000000..80cb0e8 --- /dev/null +++ b/gui/panels/PerformancePanel.cpp @@ -0,0 +1,100 @@ +// Copyright 2025 Pound Emulator Project. All rights reserved. + +#include "PerformancePanel.h" +#include + +namespace Pound::GUI +{ + + PerformancePanel::PerformancePanel() : Panel("Performance") + { + last_update = std::chrono::steady_clock::now(); + } + + void PerformancePanel::Render() + { + if (!ImGui::Begin(name.c_str(), &visible)) + { + ImGui::End(); + return; + } + + Update(); + + // Performance metrics + ImGui::Text("FPS: %.1f", current_data.fps); + ImGui::Text("Frame Time: %.2f ms", current_data.frame_time); + ImGui::Separator(); + + // FPS Graph + if (!fps_history.empty()) + { + float fps_array[HISTORY_SIZE]; + std::copy(fps_history.begin(), fps_history.end(), fps_array); + + ImGui::Text("FPS History:"); + ImGui::PlotLines("##FPS", fps_array, (int)fps_history.size(), 0, nullptr, + 0.0f, 144.0f, ImVec2(0, 80)); + } + + // Frame Time Graph + if (!frame_time_history.empty()) + { + float frame_time_array[HISTORY_SIZE]; + std::copy(frame_time_history.begin(), frame_time_history.end(), frame_time_array); + + ImGui::Text("Frame Time History (ms):"); + ImGui::PlotLines("##FrameTime", frame_time_array, (int)frame_time_history.size(), 0, nullptr, + 0.0f, 33.33f, ImVec2(0, 80)); + } + + ImGui::Separator(); + + // System info (placeholder) + ImGui::Text("CPU Usage: %.1f%%", current_data.cpu_usage); + ImGui::Text("Memory Usage: %.1f MB", current_data.memory_usage); + + // Emulation stats + ImGui::Separator(); + ImGui::Text("Emulation Statistics:"); + ImGui::Text("Instructions/sec: N/A"); + ImGui::Text("JIT Cache Usage: N/A"); + + ImGui::End(); + } + + void PerformancePanel::Update() + { + frame_count++; + + auto now = std::chrono::steady_clock::now(); + auto duration = std::chrono::duration_cast(now - last_update); + + if (duration.count() >= 100) + { // Update every 100ms + current_data.fps = frame_count * 1000.0f / duration.count(); + current_data.frame_time = duration.count() / (float)frame_count; + + fps_history.push_back(current_data.fps); + frame_time_history.push_back(current_data.frame_time); + + // Keep history size limited + while (fps_history.size() > HISTORY_SIZE) + { + fps_history.pop_front(); + } + while (frame_time_history.size() > HISTORY_SIZE) + { + frame_time_history.pop_front(); + } + + frame_count = 0; + last_update = now; + + // TODO: Get actual CPU and memory usage + current_data.cpu_usage = 0.0f; + current_data.memory_usage = 0.0f; + } + } + +} // namespace Pound::GUI \ No newline at end of file diff --git a/gui/panels/PerformancePanel.h b/gui/panels/PerformancePanel.h new file mode 100644 index 0000000..2e2dfe7 --- /dev/null +++ b/gui/panels/PerformancePanel.h @@ -0,0 +1,37 @@ +// Copyright 2025 Pound Emulator Project. All rights reserved. +#pragma once + +#include "../Panel.h" +#include +#include + +namespace Pound::GUI +{ + + class PerformancePanel : public Panel + { + public: + PerformancePanel(); + + void Render() override; + void Update(); + + private: + struct PerformanceData + { + float fps = 0.0f; + float frame_time = 0.0f; + float cpu_usage = 0.0f; + float memory_usage = 0.0f; + }; + + PerformanceData current_data; + std::deque fps_history; + std::deque frame_time_history; + static constexpr size_t HISTORY_SIZE = 120; + + std::chrono::steady_clock::time_point last_update; + int frame_count = 0; + }; + +} // namespace Pound::GUI \ No newline at end of file