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
This commit is contained in:
Carlo Pezzotti 2025-07-09 09:07:32 +02:00
parent 22418185f8
commit 2bb666a088
15 changed files with 1099 additions and 121 deletions

View file

@ -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)

View file

@ -1,68 +1,20 @@
// Copyright 2025 Pound Emulator Project. All rights reserved.
#include <thread>
#include <imgui.h>
#include <imgui_impl_sdl3.h>
#include <imgui_impl_opengl3.h>
#include <memory>
#include <chrono>
#include "Base/Logging/Backend.h"
#include <SDL3/SDL.h>
#include <SDL3/SDL_opengl.h>
#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<Pound::GUI::GUIManager>();
// 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<Pound::GUI::ConsolePanel>();
auto cpu_panel = std::make_shared<Pound::GUI::CPUPanel>();
auto performance_panel = std::make_shared<Pound::GUI::PerformancePanel>();
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;
}
}

39
gui/CMakeLists.txt Normal file
View file

@ -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}/..
)

73
gui/Colors.h Normal file
View file

@ -0,0 +1,73 @@
// Copyright 2025 Pound Emulator Project. All rights reserved.
#pragma once
#include <imgui.h>
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

275
gui/GUIManager.cpp Normal file
View file

@ -0,0 +1,275 @@
// Copyright 2025 Pound Emulator Project. All rights reserved.
#include "GUIManager.h"
#include "Colors.h"
#include "Base/Logging/Log.h"
#include <imgui.h>
#include <imgui_impl_sdl3.h>
#include <imgui_impl_opengl3.h>
#include <SDL3/SDL_opengl.h>
#include <algorithm>
namespace Pound::GUI
{
GUIManager::GUIManager() = default;
GUIManager::~GUIManager()
{
Shutdown();
}
bool GUIManager::Initialize(const std::string &title, int width, int height)
{
window = std::make_unique<Window>();
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> 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> &panel)
{
return panel->GetName() == name;
}),
panels.end());
}
}

44
gui/GUIManager.h Normal file
View file

@ -0,0 +1,44 @@
// Copyright 2025 Pound Emulator Project. All rights reserved.
#pragma once
#include <memory>
#include <vector>
#include <functional>
#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> panel);
void RemovePanel(const std::string& name);
// Callback for external systems
void SetCPUTestCallback(std::function<void()> callback) { cpu_test_callback = callback; }
private:
void BeginFrame();
void EndFrame();
void RenderMainMenuBar();
void ApplyTheme();
std::unique_ptr<Window> window;
std::vector<std::shared_ptr<Panel>> panels;
bool running = false;
bool show_demo_window = false;
// Callbacks
std::function<void()> cpu_test_callback;
};
} // namespace Pound::GUI

27
gui/Panel.h Normal file
View file

@ -0,0 +1,27 @@
// Copyright 2025 Pound Emulator Project. All rights reserved.
#pragma once
#include <string>
#include <imgui.h>
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

97
gui/Window.cpp Normal file
View file

@ -0,0 +1,97 @@
// Copyright 2025 Pound Emulator Project. All rights reserved.
#include "Window.h"
#include "Base/Logging/Log.h"
#include <imgui.h>
#include <imgui_impl_sdl3.h>
#include <imgui_impl_opengl3.h>
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

36
gui/Window.h Normal file
View file

@ -0,0 +1,36 @@
// Copyright 2025 Pound Emulator Project. All rights reserved.
#pragma once
#include <SDL3/SDL.h>
#include <SDL3/SDL_opengl.h>
#include <memory>
#include <string>
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

143
gui/panels/CPUPanel.cpp Normal file
View file

@ -0,0 +1,143 @@
// Copyright 2025 Pound Emulator Project. All rights reserved.
#include "CPUPanel.h"
#include "../Colors.h"
#include <iomanip>
#include <sstream>
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

32
gui/panels/CPUPanel.h Normal file
View file

@ -0,0 +1,32 @@
// Copyright 2025 Pound Emulator Project. All rights reserved.
#pragma once
#include "../Panel.h"
#include <functional>
namespace Pound::GUI
{
class CPUPanel : public Panel
{
public:
CPUPanel();
void Render() override;
void SetCPUTestCallback(std::function<void()> callback) { cpu_test_callback = callback; }
void ShowTestResult(bool show = true) { show_test_result = show; }
private:
std::function<void()> 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

110
gui/panels/ConsolePanel.cpp Normal file
View file

@ -0,0 +1,110 @@
// Copyright 2025 Pound Emulator Project. All rights reserved.
#include "ConsolePanel.h"
#include "../Colors.h"
#include <chrono>
#include <ctime>
#include <iomanip>
#include <sstream>
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;
}
}

36
gui/panels/ConsolePanel.h Normal file
View file

@ -0,0 +1,36 @@
// Copyright 2025 Pound Emulator Project. All rights reserved.
#pragma once
#include "../Panel.h"
#include <vector>
#include <string>
#include <deque>
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<LogEntry> 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

View file

@ -0,0 +1,100 @@
// Copyright 2025 Pound Emulator Project. All rights reserved.
#include "PerformancePanel.h"
#include <algorithm>
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<std::chrono::milliseconds>(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

View file

@ -0,0 +1,37 @@
// Copyright 2025 Pound Emulator Project. All rights reserved.
#pragma once
#include "../Panel.h"
#include <deque>
#include <chrono>
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<float> fps_history;
std::deque<float> 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