mirror of
https://github.com/pound-emu/pound.git
synced 2025-12-12 01:36:57 +00:00
Rewrite the entirety of Pound
feat!: rewrote program in a data oriented style.
This commit is contained in:
commit
33091fe066
26 changed files with 1179 additions and 1162 deletions
91
CONTRIBUTING.md
Normal file
91
CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
# Data-Oriented Design Fundamentals
|
||||||
|
|
||||||
|
This document outlines the core principles and guidelines for contributing to our project, which has been rewritten
|
||||||
|
using a data-oriented design (DoD) approach.
|
||||||
|
|
||||||
|
## Why Data-Oriented Design?
|
||||||
|
|
||||||
|
The source code is now written in a data-oriented style instead of an object-oriented one. This change was made because
|
||||||
|
traditional OOP can be CPU cache-unfriendly, leading to slower performance compared to DoD principles.
|
||||||
|
|
||||||
|
While Yuzu's entire codebase is object-oriented, we believe that applying DoD from the very beginning could
|
||||||
|
significantly improve its speed and efficiency.
|
||||||
|
|
||||||
|
## Key Resources
|
||||||
|
|
||||||
|
To learn more about data-oriented design fundamentals, please refer to this invaluable resource:
|
||||||
|
|
||||||
|
https://github.com/dbartolini/data-oriented-design
|
||||||
|
|
||||||
|
This guide should be treated as a fundamental reference when working on our codebase.
|
||||||
|
|
||||||
|
## Core Principles
|
||||||
|
|
||||||
|
### 1. Performance First
|
||||||
|
|
||||||
|
Performance is the top priority in all aspects of development. Always ask yourself: "Is the CPU wasting cycles running
|
||||||
|
this code? If so, how do I fix it?" The data-oriented design resource above contains answers to these questions.
|
||||||
|
|
||||||
|
### 2. Memory Management
|
||||||
|
|
||||||
|
- **Heap Allocation Ban**: Using memory allocation functions like malloc(), free(), new, and delete is prohibited.
|
||||||
|
- **Stack Preference**: Keep everything on the stack whenever possible.
|
||||||
|
- **Last Resort**: If you must allocate memory, use our custom memory allocator in `core/memory/arena.h`. This should be
|
||||||
|
your last resort only.
|
||||||
|
|
||||||
|
The reason for these strict rules is that heap allocations can introduce undefined behavior issues into our codebase.
|
||||||
|
|
||||||
|
### 3. Safety First
|
||||||
|
|
||||||
|
1. **Error Handling**: Every return code from a function must be checked and handled appropriately.
|
||||||
|
2. **Assertions**: Use assertions to guarantee behavior whenever possible. Watch this video for an explanation:
|
||||||
|
https://youtube.com/shorts/M-VU0fLjIUU
|
||||||
|
3. **Static Analysis**: Use the SonarQube static analyzer to catch potential bugs early in development.
|
||||||
|
|
||||||
|
### 4. Documentation
|
||||||
|
|
||||||
|
Document every struct and function throughout our codebase. This is a tedious task, but it will be greatly appreciated
|
||||||
|
by current and future programmers working on this project.
|
||||||
|
|
||||||
|
## Style Conventions
|
||||||
|
|
||||||
|
Refer to `main.cpp` and the GUI folder for examples of proper code styling. Here are some specific rules:
|
||||||
|
|
||||||
|
1. **Constant First in Equality Tests**:
|
||||||
|
```c
|
||||||
|
// Non-compliant
|
||||||
|
if (var == constant)
|
||||||
|
if (pointer == NULL)
|
||||||
|
|
||||||
|
// Compliant
|
||||||
|
if (constant == var)
|
||||||
|
if (NULL == pointer)
|
||||||
|
```
|
||||||
|
2. **Todo Comments**: Use the following format:
|
||||||
|
```c
|
||||||
|
// Todo(<Username>:<section>): ...
|
||||||
|
|
||||||
|
Where <section> is one of:
|
||||||
|
|
||||||
|
- cpu
|
||||||
|
- gpu
|
||||||
|
- gui
|
||||||
|
- memory
|
||||||
|
- filesystem
|
||||||
|
|
||||||
|
For example:
|
||||||
|
// Todo(GloriousTaco:memory): Create a custom allocator.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing Suggestions
|
||||||
|
|
||||||
|
For those who want to contribute now, we suggest rewriting:
|
||||||
|
|
||||||
|
- Base::Log::Initialize()
|
||||||
|
- Base::Log::Start()
|
||||||
|
- Config::load()
|
||||||
|
|
||||||
|
in main() using the principles outlined in this document.
|
||||||
|
|
||||||
|
If you're familiar with memory allocators, please consider implementing std::allocator for our custom memory allocator
|
||||||
|
in core/memory/arena.h. This would allow us to manage the memory of C++ standard types like std::vector.
|
||||||
14
README.md
14
README.md
|
|
@ -20,12 +20,13 @@
|
||||||
|
|
||||||
Join the [**Pound Discord Server**](https://discord.gg/aMmTmKsVC7)!
|
Join the [**Pound Discord Server**](https://discord.gg/aMmTmKsVC7)!
|
||||||
|
|
||||||
**Pound** is an early-stage emulator for the **Nintendo Switch 2**, targeting **Windows**, **Linux** and **macOS** (Intel).
|
**Pound** is an early-stage emulator for the **Nintendo Switch 2**, targeting **Windows**, **Linux** and **macOS** (
|
||||||
|
Intel).
|
||||||
|
|
||||||
Future Supports: **Android**, **macOS ARM**
|
Future Supports: **Android**, **macOS ARM**
|
||||||
|
|
||||||
|
Initial focus is on implementing the architectural similarities to the original Nintendo Switch. Later stages of
|
||||||
Initial focus is on implementing the architectural similarities to the original Nintendo Switch. Later stages of development will address differences in hardware between the two console generations.
|
development will address differences in hardware between the two console generations.
|
||||||
|
|
||||||
## Disclaimer
|
## Disclaimer
|
||||||
|
|
||||||
|
|
@ -36,28 +37,23 @@ legally purchased devices and games and information made public on the internet
|
||||||
(you'd be surprised what's indexed on Google...). We are not any way affiliated
|
(you'd be surprised what's indexed on Google...). We are not any way affiliated
|
||||||
with Nintendo or NVidia.
|
with Nintendo or NVidia.
|
||||||
|
|
||||||
|
|
||||||
## How to Compile Pound
|
## How to Compile Pound
|
||||||
|
|
||||||
See the [**compilation guide**](/resources/docs/compguide.md) for detailed instructions on how to compile Pound.
|
See the [**compilation guide**](/resources/docs/compguide.md) for detailed instructions on how to compile Pound.
|
||||||
|
|
||||||
|
|
||||||
## Codebase
|
## Codebase
|
||||||
|
|
||||||
Pound reuses selected components from existing Nintendo Switch 1 emulators, primarily **Yuzu**.
|
Pound reuses selected components from existing Nintendo Switch 1 emulators, primarily **Yuzu**.
|
||||||
All third-party code is clearly documented and properly attributed in the relevant parts of the repository.
|
All third-party code is clearly documented and properly attributed in the relevant parts of the repository.
|
||||||
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
(coming soon)
|
See [**here**](/CONTRIBUTING.md) before submitting a pull request.
|
||||||
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
- Distributed under the [**GNU GPL-2.0 license**](https://github.com/pound-emu/pound/blob/main/LICENSE)
|
- Distributed under the [**GNU GPL-2.0 license**](https://github.com/pound-emu/pound/blob/main/LICENSE)
|
||||||
|
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
- Parts of the emulator are based on code from the Yuzu project.
|
- Parts of the emulator are based on code from the Yuzu project.
|
||||||
|
|
|
||||||
20
core/ARM/cpu.cpp
Executable file
20
core/ARM/cpu.cpp
Executable file
|
|
@ -0,0 +1,20 @@
|
||||||
|
#include "cpu.h"
|
||||||
|
#include "JIT/jit.h"
|
||||||
|
void cpuTest()
|
||||||
|
{
|
||||||
|
CPU cpu;
|
||||||
|
cpu.pc = 0;
|
||||||
|
|
||||||
|
// Simple ARMv8 program in memory (MOVZ X0, #5; ADD X0, X0, #3; RET)
|
||||||
|
// These are placeholders; real encoding will be parsed later
|
||||||
|
cpu.write_byte(0, 0x05); // MOVZ placeholder
|
||||||
|
cpu.write_byte(4, 0x03); // ADD placeholder
|
||||||
|
cpu.write_byte(8, 0xFF); // RET placeholder
|
||||||
|
|
||||||
|
LOG_INFO(ARM, "{}", cpu.read_byte(0));
|
||||||
|
|
||||||
|
JIT jit;
|
||||||
|
//jit.translate_and_run(cpu);
|
||||||
|
|
||||||
|
cpu.print_debug_information();
|
||||||
|
}
|
||||||
|
|
@ -6,38 +6,44 @@
|
||||||
|
|
||||||
#include "Base/Logging/Log.h"
|
#include "Base/Logging/Log.h"
|
||||||
|
|
||||||
struct CPU {
|
struct CPU
|
||||||
u64 regs[31] = {0}; // X0–X30
|
{
|
||||||
|
u64 regs[31] = {0}; // X0–X30
|
||||||
u64 pc = 0;
|
u64 pc = 0;
|
||||||
static constexpr size_t MEM_SIZE = 64 * 1024;
|
static constexpr size_t MEM_SIZE = 64 * 1024;
|
||||||
u8 memory[MEM_SIZE];
|
u8 memory[MEM_SIZE];
|
||||||
|
|
||||||
CPU() {
|
CPU() { std::memset(memory, 0, MEM_SIZE); }
|
||||||
std::memset(memory, 0, MEM_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
u64& x(int i) {
|
u64& x(int i) { return regs[i]; }
|
||||||
return regs[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
u8 read_byte(u64 addr) {
|
u8 read_byte(u64 addr)
|
||||||
if (addr >= MEM_SIZE) {
|
{
|
||||||
|
if (addr >= MEM_SIZE)
|
||||||
|
{
|
||||||
LOG_INFO(ARM, "{} out of bounds", addr);
|
LOG_INFO(ARM, "{} out of bounds", addr);
|
||||||
}
|
}
|
||||||
return memory[addr];
|
return memory[addr];
|
||||||
}
|
}
|
||||||
|
|
||||||
void write_byte(u64 addr, u8 byte) {
|
void write_byte(u64 addr, u8 byte)
|
||||||
if (addr >= MEM_SIZE) {
|
{
|
||||||
|
if (addr >= MEM_SIZE)
|
||||||
|
{
|
||||||
LOG_INFO(ARM, "{} out of bounds", addr);
|
LOG_INFO(ARM, "{} out of bounds", addr);
|
||||||
}
|
}
|
||||||
memory[addr] = byte;
|
memory[addr] = byte;
|
||||||
}
|
}
|
||||||
|
|
||||||
void print_debug_information() {
|
void print_debug_information()
|
||||||
|
{
|
||||||
LOG_INFO(ARM, "PC = {}", pc);
|
LOG_INFO(ARM, "PC = {}", pc);
|
||||||
for (int reg = 0; reg < 32; reg++) {
|
for (int reg = 0; reg < 31; reg++)
|
||||||
LOG_INFO(ARM, "X{} = {}", reg, x(reg)); // X0 = 0...
|
{
|
||||||
|
uint64_t regis = x(reg);
|
||||||
|
LOG_INFO(ARM, "X{} = {}", reg, regis); // X0 = 0..
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void cpuTest();
|
||||||
|
|
|
||||||
157
core/main.cpp
157
core/main.cpp
|
|
@ -1,89 +1,128 @@
|
||||||
// Copyright 2025 Pound Emulator Project. All rights reserved.
|
// Copyright 2025 Pound Emulator Project. All rights reserved.
|
||||||
|
|
||||||
#include <thread>
|
|
||||||
#include <memory>
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <memory>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
#include "Base/Logging/Backend.h"
|
|
||||||
#include "Base/Config.h"
|
|
||||||
#include "ARM/cpu.h"
|
#include "ARM/cpu.h"
|
||||||
|
#include "Base/Config.h"
|
||||||
|
#include "Base/Logging/Backend.h"
|
||||||
#include "JIT/jit.h"
|
#include "JIT/jit.h"
|
||||||
|
#include "gui/gui.h"
|
||||||
|
#include "memory/arena.h"
|
||||||
|
|
||||||
#include "gui/GUIManager.h"
|
#include <SDL3/SDL_opengl.h>
|
||||||
#include "gui/panels/ConsolePanel.h"
|
#include "gui/color.h"
|
||||||
#include "gui/panels/CPUPanel.h"
|
#include "gui/panels.h"
|
||||||
#include "gui/panels/PerformancePanel.h"
|
#include "imgui_impl_opengl3.h"
|
||||||
|
#include "imgui_impl_sdl3.h"
|
||||||
|
|
||||||
// CPU test function
|
int main()
|
||||||
void cpuTest() {
|
{
|
||||||
CPU cpu;
|
// This is meant to replace malloc() and its related functions.
|
||||||
cpu.pc = 0;
|
// TODO(GloriousTaco:memory): Implement std::allocator for this custom allocator which allows it to manage the memory of C++ standard types like std::vector.
|
||||||
|
memory::arena_t arena = memory::arena_init(1024);
|
||||||
|
|
||||||
// Simple ARMv8 program in memory (MOVZ X0, #5; ADD X0, X0, #3; RET)
|
|
||||||
// These are placeholders; real encoding will be parsed later
|
|
||||||
cpu.write_byte(0, 0x05); // MOVZ placeholder
|
|
||||||
cpu.write_byte(4, 0x03); // ADD placeholder
|
|
||||||
cpu.write_byte(8, 0xFF); // RET placeholder
|
|
||||||
|
|
||||||
LOG_INFO(ARM, "{}", cpu.read_byte(0));
|
|
||||||
|
|
||||||
JIT jit;
|
|
||||||
jit.translate_and_run(cpu);
|
|
||||||
|
|
||||||
cpu.print_debug_information();
|
|
||||||
|
|
||||||
LOG_INFO(ARM, "X0 = {}", cpu.x(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
Base::Log::Initialize();
|
Base::Log::Initialize();
|
||||||
Base::Log::Start();
|
Base::Log::Start();
|
||||||
|
|
||||||
const auto config_dir = Base::FS::GetUserPath(Base::FS::PathType::BinaryDir);
|
const auto& config_dir = Base::FS::GetUserPath(Base::FS::PathType::BinaryDir);
|
||||||
Config::Load(config_dir / "config.toml");
|
Config::Load(config_dir / "config.toml");
|
||||||
|
|
||||||
// Create GUI manager
|
// Create GUI manager
|
||||||
auto gui_manager = std::make_unique<Pound::GUI::GUIManager>();
|
gui::window_t window = {.data = nullptr, .gl_context = nullptr};
|
||||||
|
(void)gui::window_init(&window, "Pound Emulator", Config::windowWidth(), Config::windowHeight());
|
||||||
|
|
||||||
// Initialize GUI
|
if (bool return_code = gui::init_imgui(&window); false == return_code)
|
||||||
if (!gui_manager->Initialize("Pound Emulator", Config::windowWidth(), Config::windowHeight())) {
|
{
|
||||||
LOG_ERROR(Render, "Failed to initialize GUI");
|
LOG_ERROR(Render, "Failed to initialize GUI");
|
||||||
return -1;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and add panels
|
const size_t panels_capacity = 2;
|
||||||
auto console_panel = std::make_shared<Pound::GUI::ConsolePanel>();
|
const char* panel_names[panels_capacity] = {PANEL_NAME_CPU, PANEL_NAME_PERFORMANCE};
|
||||||
auto cpu_panel = std::make_shared<Pound::GUI::CPUPanel>();
|
bool panels_visibility[panels_capacity] = {false};
|
||||||
auto performance_panel = std::make_shared<Pound::GUI::PerformancePanel>();
|
bool imgui_demo_visible = false;
|
||||||
|
|
||||||
gui_manager->AddPanel(console_panel);
|
gui::gui_t gui = {
|
||||||
gui_manager->AddPanel(cpu_panel);
|
.window = window,
|
||||||
gui_manager->AddPanel(performance_panel);
|
.custom_panels = panel_names,
|
||||||
|
.custom_panels_visibility = panels_visibility,
|
||||||
// Set up callbacks
|
.custom_panels_capacity = panels_capacity,
|
||||||
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);
|
gui::panel::performance_panel_t performance_panel = {};
|
||||||
cpu_panel->SetCPUTestCallback(cpu_test_callback);
|
gui::panel::performance_data_t performance_data = {.frame_count = 1};
|
||||||
|
std::chrono::steady_clock::time_point performance_panel_last_render = std::chrono::steady_clock::now();
|
||||||
// Add initial console message
|
|
||||||
console_panel->AddLog("[INFO] Pound Emulator started");
|
|
||||||
console_panel->AddLog("[INFO] Version: Pre-Alpha");
|
|
||||||
|
|
||||||
// Main loop
|
// Main loop
|
||||||
while (gui_manager->IsRunning()) {
|
bool is_running = true;
|
||||||
gui_manager->RunFrame();
|
bool show_cpu_result_popup = false;
|
||||||
|
while (true == is_running)
|
||||||
|
{
|
||||||
|
SDL_Event event;
|
||||||
|
while (::SDL_PollEvent(&event))
|
||||||
|
{
|
||||||
|
(void)::ImGui_ImplSDL3_ProcessEvent(&event);
|
||||||
|
if (event.type == SDL_EVENT_QUIT)
|
||||||
|
{
|
||||||
|
is_running = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::ImGui_ImplOpenGL3_NewFrame();
|
||||||
|
::ImGui_ImplSDL3_NewFrame();
|
||||||
|
::ImGui::NewFrame();
|
||||||
|
if (int8_t return_code = gui::render_memu_bar(gui.custom_panels, gui.custom_panels_capacity,
|
||||||
|
gui.custom_panels_visibility, &imgui_demo_visible);
|
||||||
|
WINDOW_SHOULD_CLOSE == return_code)
|
||||||
|
{
|
||||||
|
is_running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < panels_capacity; ++i)
|
||||||
|
{
|
||||||
|
if (false == gui.custom_panels_visibility[i])
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 == ::strcmp(gui.custom_panels[i], PANEL_NAME_PERFORMANCE))
|
||||||
|
{
|
||||||
|
int8_t return_code = gui::panel::render_performance_panel(&performance_panel, &performance_data,
|
||||||
|
&performance_panel_last_render);
|
||||||
|
if (ERROR_PANEL_IS_CLOSED == return_code)
|
||||||
|
{
|
||||||
|
gui.custom_panels_visibility[i] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (0 == ::strcmp(gui.custom_panels[i], PANEL_NAME_CPU))
|
||||||
|
{
|
||||||
|
int8_t return_code = gui::panel::render_cpu_panel(&show_cpu_result_popup);
|
||||||
|
if (ERROR_PANEL_IS_CLOSED == return_code)
|
||||||
|
{
|
||||||
|
gui.custom_panels_visibility[i] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// End Frame.
|
||||||
|
ImGui::Render();
|
||||||
|
const ImGuiIO& io = ImGui::GetIO();
|
||||||
|
::glViewport(0, 0, static_cast<GLint>(io.DisplaySize.x), static_cast<GLint>(io.DisplaySize.y));
|
||||||
|
::glClearColor(0.08f, 0.08f, 0.10f, 1.0f);
|
||||||
|
::glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
|
::ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||||
|
if (bool sdl_ret_code = ::SDL_GL_SwapWindow(gui.window.data); false == sdl_ret_code)
|
||||||
|
{
|
||||||
|
LOG_ERROR(Render, "Failed to update window with OpenGL rendering: {}", SDL_GetError());
|
||||||
|
is_running = false;
|
||||||
|
}
|
||||||
|
|
||||||
// Small delay to prevent excessive CPU usage
|
// Small delay to prevent excessive CPU usage
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup
|
gui::destroy();
|
||||||
gui_manager->Shutdown();
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
@ -4,43 +4,50 @@
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Memory::Arena Memory::arena_init() {
|
memory::arena_t memory::arena_init(size_t capacity)
|
||||||
// TODO(GloriousEggroll): Replace malloc with a windows memory mapping API.
|
{
|
||||||
|
|
||||||
|
// TODO(GloriousTaco:memory): Replace malloc with a windows memory mapping API.
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
auto data =
|
auto data = static_cast<uint8_t*>(malloc(sizeof(uint8_t) * MEMORY_CAPACITY));
|
||||||
static_cast<uint8_t*>(malloc(sizeof(uint8_t) * MEMORY_CAPACITY));
|
|
||||||
#else
|
#else
|
||||||
void* data = mmap(nullptr, MEMORY_CAPACITY, PROT_READ | PROT_WRITE,
|
void* data = ::mmap(nullptr, capacity, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||||
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
if (data == MAP_FAILED)
|
||||||
if (data == MAP_FAILED) {
|
{
|
||||||
return {0, 0, nullptr}; // Return invalid arena on failure
|
return {0, 0, nullptr}; // Return invalid arena on failure
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
Memory::Arena arena = {
|
|
||||||
.capacity = MEMORY_CAPACITY,
|
(void)std::memset(data, POISON_PATTERN, capacity);
|
||||||
|
memory::arena_t arena = {
|
||||||
|
.capacity = capacity,
|
||||||
.size = 0,
|
.size = 0,
|
||||||
.data = static_cast<uint8_t*>(data),
|
.data = data,
|
||||||
};
|
};
|
||||||
return arena;
|
return arena;
|
||||||
}
|
}
|
||||||
// new more memsafe code (ownedbywuigi) (i give up on windows compatibility for now, will stick to the old unsafe code)
|
// new more memsafe code (ownedbywuigi) (i give up on windows compatibility for now, will stick to the old unsafe code)
|
||||||
|
|
||||||
const uint8_t* Memory::arena_allocate(Memory::Arena* arena,
|
const void* memory::arena_allocate(memory::arena_t* arena, const std::size_t size)
|
||||||
const std::size_t size) {
|
{
|
||||||
ASSERT(arena != nullptr);
|
ASSERT(arena != nullptr);
|
||||||
ASSERT(arena->size + size < arena->capacity);
|
ASSERT(arena->size + size < arena->capacity);
|
||||||
const uint8_t* const data = &(arena->data[arena->size]);
|
const void* const data = &arena->data + arena->size;
|
||||||
arena->size += size;
|
arena->size += size;
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
void Memory::arena_reset(Memory::Arena* arena) {
|
void memory::arena_reset(memory::arena_t* arena)
|
||||||
ASSERT(arena != nullptr);
|
{
|
||||||
|
ASSERT(nullptr != arena);
|
||||||
|
ASSERT(nullptr != arena->data);
|
||||||
arena->size = 0;
|
arena->size = 0;
|
||||||
|
std::memset(arena->data, POISON_PATTERN, arena->capacity);
|
||||||
}
|
}
|
||||||
void Memory::arena_free(Memory::Arena* arena) {
|
void memory::arena_free(memory::arena_t* arena)
|
||||||
|
{
|
||||||
ASSERT(arena != nullptr);
|
ASSERT(arena != nullptr);
|
||||||
arena->capacity = 0;
|
arena->capacity = 0;
|
||||||
arena->size = 0;
|
arena->size = 0;
|
||||||
// TODO(GloriousTaco): Replace free with a memory safe alternative.
|
// TODO(GloriousTaco:memory): Replace free with a memory safe alternative.
|
||||||
free(arena->data);
|
free(arena->data);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,21 +5,24 @@
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
|
||||||
namespace Memory {
|
namespace memory
|
||||||
|
{
|
||||||
|
|
||||||
/* Defines the default size (in bytes) for memory arenas created via arena_init() */
|
/* Defines the default size (in bytes) for memory arenas created via arena_init() */
|
||||||
#define MEMORY_CAPACITY 20480 // 20 KB
|
#define MEMORY_CAPACITY 20480 // 20 KB
|
||||||
|
|
||||||
|
#define POISON_PATTERN 0xAA
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* NAME
|
* NAME
|
||||||
* Arena - Memory management structure for efficient allocation and de-allocation.
|
* arena_t - memory management structure for efficient allocation and de-allocation.
|
||||||
*
|
*
|
||||||
* SYNOPSIS
|
* SYNOPSIS
|
||||||
* typedef struct {
|
* typedef struct {
|
||||||
* std::size_t capacity; Total number of bytes allocated.
|
* std::size_t capacity; Total number of bytes allocated.
|
||||||
* std::size_t size; The current number of bytes consumed.
|
* std::size_t size; The current number of bytes consumed.
|
||||||
* uint8_t* data; A pointer to the base address of the allocated memory buffer.
|
* void* data; A pointer to the base address of the allocated memory buffer.
|
||||||
* } Arena;
|
* } arena_t;
|
||||||
*
|
*
|
||||||
* DESCRIPTION
|
* DESCRIPTION
|
||||||
* The arena struct handles allocating and managing contiguous memory blocks.
|
* The arena struct handles allocating and managing contiguous memory blocks.
|
||||||
|
|
@ -29,18 +32,19 @@ namespace Memory {
|
||||||
* maintaining a single contiguous block eliminates heap fragmentation
|
* maintaining a single contiguous block eliminates heap fragmentation
|
||||||
* that occurs with frequent small allocations.
|
* that occurs with frequent small allocations.
|
||||||
*/
|
*/
|
||||||
typedef struct {
|
typedef struct
|
||||||
|
{
|
||||||
std::size_t capacity;
|
std::size_t capacity;
|
||||||
std::size_t size;
|
std::size_t size;
|
||||||
uint8_t* data;
|
void* data;
|
||||||
} Arena;
|
} arena_t;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* NAME
|
* NAME
|
||||||
* arena_init - Initialize a memory arena with default capacity.
|
* arena_init - Initialize a memory arena with default capacity.
|
||||||
*
|
*
|
||||||
* SYNOPSIS
|
* SYNOPSIS
|
||||||
* Arena Memory::arena_init();
|
* arema_t memory::arena_init();
|
||||||
*
|
*
|
||||||
* DESCRIPTION
|
* DESCRIPTION
|
||||||
* The function creates and returns a new memory arena instance with a
|
* The function creates and returns a new memory arena instance with a
|
||||||
|
|
@ -48,16 +52,16 @@ typedef struct {
|
||||||
* default capacity.
|
* default capacity.
|
||||||
*
|
*
|
||||||
* RETURN VALUE
|
* RETURN VALUE
|
||||||
* Returns a valid Arena object on success.
|
* Returns a valid arema_t object on success.
|
||||||
*/
|
*/
|
||||||
extern Arena arena_init();
|
extern memory::arena_t arena_init(size_t capacity);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* NAME
|
* NAME
|
||||||
* arena_allocate - Allocate memory from a pre-initialized arena.
|
* arena_allocate - Allocate memory from a pre-initialized arena.
|
||||||
*
|
*
|
||||||
* SYNOPSIS
|
* SYNOPSIS
|
||||||
* const uint8_t Memory::arena_allocate(Memory::Arena* arena, std::size_t size);
|
* const void* memory::arena_allocate(memory::arena_t* arena, std::size_t size);
|
||||||
*
|
*
|
||||||
* DESCRIPTION
|
* DESCRIPTION
|
||||||
* The function allocates size bytes from the specified arena. It assumes
|
* The function allocates size bytes from the specified arena. It assumes
|
||||||
|
|
@ -69,19 +73,19 @@ extern Arena arena_init();
|
||||||
* pointer is valid until the arena is reset or destroyed.
|
* pointer is valid until the arena is reset or destroyed.
|
||||||
*
|
*
|
||||||
* NOTES
|
* NOTES
|
||||||
* Requires Arena to be initialized with arena_init() or similar.
|
* Requires arema_t to be initialized with arena_init() or similar.
|
||||||
*/
|
*/
|
||||||
const uint8_t* arena_allocate(Arena* arena, std::size_t size);
|
const void* arena_allocate(arena_t* arena, const std::size_t size);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* NAME
|
* NAME
|
||||||
* arena_reset - Reset a memory arena's allocation size to zero.
|
* arena_reset - Reset a memory arena's allocation size to zero.
|
||||||
*
|
*
|
||||||
* SYNOPSIS
|
* SYNOPSIS
|
||||||
* void Memory::arena_reset(Memory::Arena* arena);
|
* void memory::arena_reset(memory::arena_t* arena);
|
||||||
*
|
*
|
||||||
* DESCRIPTION
|
* DESCRIPTION
|
||||||
* The function resets the allocation size of a pre-initialized Arena to zero.
|
* The function resets the allocation size of a pre-initialized arema_t to zero.
|
||||||
* This effectively "frees" all memory allocated from the arena without
|
* This effectively "frees" all memory allocated from the arena without
|
||||||
* deallocating the underlying buffer, allowing reuse of the capacity for
|
* deallocating the underlying buffer, allowing reuse of the capacity for
|
||||||
* future allocations.
|
* future allocations.
|
||||||
|
|
@ -91,21 +95,21 @@ const uint8_t* arena_allocate(Arena* arena, std::size_t size);
|
||||||
* Does not free the underlying memory buffer.
|
* Does not free the underlying memory buffer.
|
||||||
* Useful for reusing arenas without reallocation.
|
* Useful for reusing arenas without reallocation.
|
||||||
*/
|
*/
|
||||||
void arena_reset(Arena* arena);
|
void arena_reset(arena_t* arena);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NAME
|
* NAME
|
||||||
* arena_free - Free the memory allocated by an arena
|
* arena_free - Free the memory allocated by an arena
|
||||||
*
|
*
|
||||||
* SYNOPSIS
|
* SYNOPSIS
|
||||||
* void Memory::arena_free(Memory::Arena* arena);
|
* void memory::arena_free(memory::arena_t* arena);
|
||||||
*
|
*
|
||||||
* DESCRIPTION
|
* DESCRIPTION
|
||||||
* The function releases the memory buffer associated with a Arena and
|
* The function releases the memory buffer associated with a arema_t and
|
||||||
* resets its capacity and size to zero. This marks the arena as invalid for
|
* resets its capacity and size to zero. This marks the arena as invalid for
|
||||||
* future allocation unless reinitialized.
|
* future allocation unless reinitialized.
|
||||||
*/
|
*/
|
||||||
void arena_free(Memory::Arena* arena);
|
void arena_free(memory::arena_t* arena);
|
||||||
|
|
||||||
} // namespace Memory
|
} // namespace memory
|
||||||
#endif //POUND_ARENA_H
|
#endif //POUND_ARENA_H
|
||||||
|
|
|
||||||
|
|
@ -2,38 +2,17 @@
|
||||||
|
|
||||||
# GUI sources
|
# GUI sources
|
||||||
set(GUI_SOURCES
|
set(GUI_SOURCES
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/Window.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/gui.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/GUIManager.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/color.cpp
|
||||||
)
|
${CMAKE_CURRENT_SOURCE_DIR}/panels.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
|
# Add all GUI sources to the main target
|
||||||
target_sources(Pound PRIVATE
|
target_sources(Pound PRIVATE
|
||||||
${GUI_SOURCES}
|
${GUI_SOURCES}
|
||||||
${GUI_HEADERS}
|
|
||||||
${PANEL_SOURCES}
|
|
||||||
${PANEL_HEADERS}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Include directories for GUI
|
# Include directories for GUI
|
||||||
target_include_directories(Pound PRIVATE
|
target_include_directories(Pound PRIVATE
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/..
|
${CMAKE_CURRENT_SOURCE_DIR}/..
|
||||||
)
|
)
|
||||||
73
gui/Colors.h
73
gui/Colors.h
|
|
@ -1,73 +0,0 @@
|
||||||
// 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
|
|
||||||
|
|
@ -1,275 +0,0 @@
|
||||||
// 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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
// 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
27
gui/Panel.h
|
|
@ -1,27 +0,0 @@
|
||||||
// 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
|
|
||||||
|
|
@ -1,97 +0,0 @@
|
||||||
// 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
36
gui/Window.h
|
|
@ -1,36 +0,0 @@
|
||||||
// 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
|
|
||||||
34
gui/color.cpp
Executable file
34
gui/color.cpp
Executable file
|
|
@ -0,0 +1,34 @@
|
||||||
|
#include "color.h"
|
||||||
|
|
||||||
|
ImVec4 gui::color::with_alpha(const ImVec4& color, float alpha)
|
||||||
|
{
|
||||||
|
auto vec = ImVec4(color.x, color.y, color.z, alpha);
|
||||||
|
return vec;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImVec4 gui::color::lighten(const ImVec4& color, float amount)
|
||||||
|
{
|
||||||
|
float x = std::min(1.0F, color.x + amount);
|
||||||
|
float y = std::min(1.0F, color.y + amount);
|
||||||
|
float z = std::min(1.0F, color.z + amount);
|
||||||
|
auto vec = ImVec4(x, y, z, color.w);
|
||||||
|
return vec;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImVec4 gui::color::darken(const ImVec4& color, float amount)
|
||||||
|
{
|
||||||
|
float x = std::max(0.0F, color.x - amount);
|
||||||
|
float y = std::max(0.0F, color.y - amount);
|
||||||
|
float z = std::max(0.0F, color.z - amount);
|
||||||
|
auto vec = ImVec4(x, y, z, color.w);
|
||||||
|
return vec;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImVec4 gui::color::from_hex(uint32_t hex, float alpha)
|
||||||
|
{
|
||||||
|
float r = static_cast<float>(((hex >> 16) & 0xFF)) / 255.0f;
|
||||||
|
float g = static_cast<float>(((hex >> 8) & 0xFF)) / 255.0f;
|
||||||
|
float b = static_cast<float>((hex & 0xFF)) / 255.0f;
|
||||||
|
auto vec = ImVec4(r, g, b, alpha);
|
||||||
|
return vec;
|
||||||
|
}
|
||||||
137
gui/color.h
Executable file
137
gui/color.h
Executable file
|
|
@ -0,0 +1,137 @@
|
||||||
|
#ifndef POUND_COLORS_H
|
||||||
|
#define POUND_COLORS_H
|
||||||
|
|
||||||
|
#include <imgui.h>
|
||||||
|
|
||||||
|
namespace gui::color
|
||||||
|
{
|
||||||
|
constexpr ImVec4 primary = ImVec4(0.0f, 0.765f, 0.890f, 1.0f); // #00c3e3
|
||||||
|
constexpr ImVec4 primary_hover = ImVec4(0.0f, 0.865f, 0.990f, 1.0f); // Lighter
|
||||||
|
constexpr ImVec4 primary_active = ImVec4(0.0f, 0.665f, 0.790f, 1.0f); // Darker
|
||||||
|
|
||||||
|
// Secondary colors
|
||||||
|
constexpr ImVec4 secondary = ImVec4(1.0f, 0.271f, 0.329f, 1.0f); // #ff4554
|
||||||
|
constexpr ImVec4 secondary_hover = ImVec4(1.0f, 0.371f, 0.429f, 1.0f); // Lighter
|
||||||
|
constexpr ImVec4 secondary_active = ImVec4(0.9f, 0.171f, 0.229f, 1.0f); // Darker
|
||||||
|
|
||||||
|
// Background colors
|
||||||
|
constexpr ImVec4 background = ImVec4(0.255f, 0.271f, 0.282f, 1.0f);
|
||||||
|
constexpr ImVec4 background_dark = ImVec4(0.155f, 0.171f, 0.182f, 1.0f);
|
||||||
|
constexpr ImVec4 background_light = ImVec4(0.355f, 0.371f, 0.382f, 1.0f);
|
||||||
|
|
||||||
|
// Text colors
|
||||||
|
constexpr ImVec4 text = ImVec4(0.95f, 0.96f, 0.98f, 1.0f);
|
||||||
|
constexpr ImVec4 text_disable = ImVec4(0.60f, 0.60f, 0.60f, 1.0f);
|
||||||
|
|
||||||
|
// UI element colors
|
||||||
|
constexpr ImVec4 border = ImVec4(0.43f, 0.43f, 0.50f, 0.50f);
|
||||||
|
constexpr ImVec4 frame = ImVec4(0.16f, 0.29f, 0.48f, 0.54f);
|
||||||
|
constexpr ImVec4 frame_hover = ImVec4(0.26f, 0.59f, 0.98f, 0.40f);
|
||||||
|
constexpr ImVec4 frame_active = ImVec4(0.26f, 0.59f, 0.98f, 0.67f);
|
||||||
|
|
||||||
|
// Special colors
|
||||||
|
constexpr ImVec4 sucess = ImVec4(0.0f, 0.8f, 0.0f, 1.0f);
|
||||||
|
constexpr ImVec4 warning = ImVec4(1.0f, 0.8f, 0.0f, 1.0f);
|
||||||
|
constexpr ImVec4 error = ImVec4(1.0f, 0.0f, 0.0f, 1.0f);
|
||||||
|
constexpr ImVec4 info = ImVec4(0.0f, 0.765f, 0.890f, 1.0f);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NAME
|
||||||
|
* with_alpha - Create a new color with adjusted alpha channel.
|
||||||
|
*
|
||||||
|
* SYNOPSIS
|
||||||
|
* ImVec4 gui::color::with_alpha(const ImVec4& color, float alpha channel)
|
||||||
|
*
|
||||||
|
* DESCRIPTION
|
||||||
|
* Returns a copy of the input color with the alpha component replaced by the specified value. The original color's
|
||||||
|
* RGB values remain unchanged.
|
||||||
|
*
|
||||||
|
* This should be used when the transparency level needs to be adjusted without modifying the original color value.
|
||||||
|
*
|
||||||
|
* RETURN VALUE
|
||||||
|
* A new ImVec4 instance with RGB values from `color` amd alpha value from `alpha`.
|
||||||
|
*
|
||||||
|
* EXAMPLES
|
||||||
|
* ImVec4 red = ImVec4(1.0F, 0.0F, 0.0F, 1.0F);
|
||||||
|
* ImVec4 semi_transparent_red = gui::color::with_alpha(red, 0.5F);
|
||||||
|
* // semi_transparent_red = (1.0, 0.0, 0.0, 0.5)
|
||||||
|
*/
|
||||||
|
ImVec4 with_alpha(const ImVec4& color, float alpha);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NAME
|
||||||
|
* lighten - Create a new color with increased brightness.
|
||||||
|
*
|
||||||
|
* SYNOPSIS
|
||||||
|
* ImVec4 gui::color::lighten(const ImVec& color, float amount);
|
||||||
|
*
|
||||||
|
* DESCRIPTION
|
||||||
|
* Returns a copy of the input color with each RGB channel brightened by the specified amount.
|
||||||
|
*
|
||||||
|
* Brightening works by adding the amount to each RGB component and clamping the result between 0.0F and 1.0F.
|
||||||
|
* This is useful for creating lighter color variations, highlights, or glow effects while maintaining the original
|
||||||
|
* transparency.
|
||||||
|
*
|
||||||
|
* RETURN VALUE
|
||||||
|
* A new ImVec4 instance with brightened RGB values and unchanged alpha.
|
||||||
|
*
|
||||||
|
* NOTES
|
||||||
|
* - Negative amounts will result in darkening instead of lightening.
|
||||||
|
*
|
||||||
|
* EXAMPLES
|
||||||
|
* // Create a brighter version of a base color.
|
||||||
|
* ImVec4 blue = ImVec4(0.0F, 0.0F, 1.0F, 1.0F);
|
||||||
|
* ImVec4 lighter_blue = gui::color::lighten(blue, 0.3F); // (0.3, 0.3 1.0, 1.0)
|
||||||
|
*/
|
||||||
|
ImVec4 lighten(const ImVec4& color, float amount);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NAME
|
||||||
|
* darken - Create a new color ith decreased brightness.
|
||||||
|
*
|
||||||
|
* SYNOPSIS
|
||||||
|
* ImVec4 - gui::color::darken(const ImVec4& color, float amount);
|
||||||
|
*
|
||||||
|
* DESCRIPTION
|
||||||
|
* Return a copy of the input color with each RGB channel darkened by the specified amount.
|
||||||
|
*
|
||||||
|
* Darkening works by subtracting the amount from each RGB component and ensuring the result does not go below 0.0F.
|
||||||
|
* This operation is useful for creating darker color variation, shadows, or dimmed effects while maintaining the
|
||||||
|
* original transparency.
|
||||||
|
*
|
||||||
|
* RETURNS
|
||||||
|
* A new ImVec4 instance with darkened RGB values and unchanged alpha.
|
||||||
|
*
|
||||||
|
* NOTES
|
||||||
|
* - Negative amounts will result in lightening instead of darkening.
|
||||||
|
*
|
||||||
|
* EXAMPLES
|
||||||
|
* // Create a darker version of a base color.
|
||||||
|
* ImVec4 yellow = ImVec4(1.0F, 1.0F, 0.0F, 1.0F);
|
||||||
|
* ImVec4 dark_yellow = gui::color::darken(yellow, 0.5F); // (0.5, 0.5, 0.0, 1.0)
|
||||||
|
*/
|
||||||
|
ImVec4 darken(const ImVec4& color, float amount);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NAME
|
||||||
|
* from_hex - Convert a hex color value to an ImVec4 color with specified alpha.
|
||||||
|
*
|
||||||
|
* SYNOPSIS
|
||||||
|
* ImVec4 gui::color::from_hex(uint32_t hex, float alpha);
|
||||||
|
*
|
||||||
|
* DESCRIPTION
|
||||||
|
* Converts a 24-bit hexadecimal color value (RRGGBB format) to an ImVec4 color structure. The input hex value is
|
||||||
|
* interpreted as having three bytes: red, green, and blue components in that order. Each component ranges from
|
||||||
|
* 0x00 to 0xFF and is normalized to the ranged [0.0F, 1.0F] for the output.
|
||||||
|
*
|
||||||
|
* RETURN VALUE
|
||||||
|
* A new ImVec4 instance with RGB values from hex specified alpha.
|
||||||
|
*
|
||||||
|
* EXAMPLES
|
||||||
|
* // Convert pure red with full opacity.
|
||||||
|
* ImVec4 red = gui::color::from_hex(0xFF0000, 1.0F); // (1.0, 0.0, 0.0, 1.0)
|
||||||
|
*/
|
||||||
|
ImVec4 from_hex(uint32_t hex, float alpha);
|
||||||
|
} // namespace gui::color
|
||||||
|
|
||||||
|
#endif //POUND_COLORS_H
|
||||||
264
gui/gui.cpp
Executable file
264
gui/gui.cpp
Executable file
|
|
@ -0,0 +1,264 @@
|
||||||
|
#include "gui.h"
|
||||||
|
#include "Base/Assert.h"
|
||||||
|
#include "Base/Logging/Log.h"
|
||||||
|
#include "color.h"
|
||||||
|
#include "imgui_impl_opengl3_loader.h"
|
||||||
|
|
||||||
|
#include <imgui.h>
|
||||||
|
#include <imgui_impl_opengl3.h>
|
||||||
|
#include <imgui_impl_sdl3.h>
|
||||||
|
|
||||||
|
static void apply_theme();
|
||||||
|
|
||||||
|
//=========================================================
|
||||||
|
// PUBLIC FUNCTIONS
|
||||||
|
//=========================================================
|
||||||
|
|
||||||
|
bool gui::window_init(window_t* window, const char* title, int64_t width, int64_t height)
|
||||||
|
{
|
||||||
|
ASSERT(nullptr != window);
|
||||||
|
ASSERT(nullptr != title);
|
||||||
|
|
||||||
|
bool ret = ::SDL_Init(SDL_INIT_VIDEO);
|
||||||
|
if (false == ret)
|
||||||
|
{
|
||||||
|
LOG_ERROR(Render, "Error creating SDL3 Context: {}", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_PropertiesID properties = ::SDL_CreateProperties();
|
||||||
|
if (0 == properties)
|
||||||
|
{
|
||||||
|
LOG_ERROR(Render, "Error creating SDL3 Properties: {}", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = ::SDL_SetStringProperty(properties, SDL_PROP_WINDOW_CREATE_TITLE_STRING, title);
|
||||||
|
if (false == ret)
|
||||||
|
{
|
||||||
|
LOG_ERROR(Render, "Error setting window title {}: {}", title, SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
(void)::SDL_SetNumberProperty(properties, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_CENTERED);
|
||||||
|
(void)::SDL_SetNumberProperty(properties, SDL_PROP_WINDOW_CREATE_Y_NUMBER, SDL_WINDOWPOS_CENTERED);
|
||||||
|
|
||||||
|
ret = ::SDL_SetNumberProperty(properties, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, width);
|
||||||
|
if (false == ret)
|
||||||
|
{
|
||||||
|
LOG_ERROR(Render, "Error setting window {} width {}: ", title, width, SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = ::SDL_SetNumberProperty(properties, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, height);
|
||||||
|
if (false == ret)
|
||||||
|
{
|
||||||
|
LOG_ERROR(Render, "Error setting window {} height {}: ", title, height);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
(void)::SDL_SetNumberProperty(properties, "flags", SDL_WINDOW_OPENGL);
|
||||||
|
(void)::SDL_SetBooleanProperty(properties, SDL_PROP_WINDOW_CREATE_RESIZABLE_BOOLEAN, true);
|
||||||
|
(void)::SDL_SetBooleanProperty(properties, SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN, true);
|
||||||
|
|
||||||
|
window->data = ::SDL_CreateWindowWithProperties(properties);
|
||||||
|
::SDL_DestroyProperties(properties);
|
||||||
|
if (nullptr == window->data)
|
||||||
|
{
|
||||||
|
LOG_ERROR(Render, "Failed to create window {}: {}", title, SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = ::SDL_SetWindowMinimumSize(window->data, WINDOW_MINIMUM_SIZE_WIDTH, WINDOW_MINIMUM_SIZE_HEIGHT);
|
||||||
|
if (false == ret)
|
||||||
|
{
|
||||||
|
LOG_ERROR(Render, "Failed to set window {} minimum width and height: {}", title, SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
window->gl_context = ::SDL_GL_CreateContext(window->data);
|
||||||
|
if (nullptr == window->gl_context)
|
||||||
|
{
|
||||||
|
LOG_ERROR(Render, "Failed to create OpenGL context: {}", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = ::SDL_GL_MakeCurrent(window->data, window->gl_context);
|
||||||
|
if (false == ret)
|
||||||
|
{
|
||||||
|
LOG_ERROR(Render, "Failed to make set OpenGL context to window {}: {}", title, SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = ::SDL_GL_SetSwapInterval(1);
|
||||||
|
if (false == ret)
|
||||||
|
{
|
||||||
|
LOG_ERROR(Render, "Failed to set swap interval for window {}: {}", title, SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void gui::window_destroy(gui::window_t* window)
|
||||||
|
{
|
||||||
|
bool ret = false;
|
||||||
|
if (window->gl_context != nullptr)
|
||||||
|
{
|
||||||
|
ret = ::SDL_GL_DestroyContext(window->gl_context);
|
||||||
|
if (false == ret)
|
||||||
|
{
|
||||||
|
LOG_ERROR(Render, "Failed to destroy OpenGL context");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (window->data != nullptr)
|
||||||
|
{
|
||||||
|
::SDL_DestroyWindow(window->data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool gui::init_imgui(gui::window_t* main_window)
|
||||||
|
{
|
||||||
|
ASSERT(nullptr != main_window->data);
|
||||||
|
ASSERT(nullptr != main_window->gl_context);
|
||||||
|
|
||||||
|
// Initialize ImGui
|
||||||
|
IMGUI_CHECKVERSION();
|
||||||
|
(void)::ImGui::CreateContext();
|
||||||
|
ImGuiIO& io = ::ImGui::GetIO();
|
||||||
|
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
||||||
|
|
||||||
|
::apply_theme();
|
||||||
|
|
||||||
|
bool ret = ::ImGui_ImplSDL3_InitForOpenGL(main_window->data, main_window->gl_context);
|
||||||
|
if (false == ret)
|
||||||
|
{
|
||||||
|
LOG_ERROR(Render, "Failed to init SDL3: {}", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = ::ImGui_ImplOpenGL3_Init("#version 330");
|
||||||
|
if (false == ret)
|
||||||
|
{
|
||||||
|
LOG_ERROR(Render, "Failed to init OpenGL3: {}", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int8_t gui::render_memu_bar(const char** panels, const size_t panels_count, bool* panels_visibility,
|
||||||
|
bool* imgui_demo_visible)
|
||||||
|
{
|
||||||
|
int8_t return_code = GUI_SUCCESS;
|
||||||
|
if (true == ::ImGui::BeginMainMenuBar())
|
||||||
|
{
|
||||||
|
if (true == ::ImGui::BeginMenu("File"))
|
||||||
|
{
|
||||||
|
::ImGui::Separator();
|
||||||
|
if (true == ::ImGui::MenuItem("Exit", "Alt+F4"))
|
||||||
|
{
|
||||||
|
return_code = WINDOW_SHOULD_CLOSE;
|
||||||
|
}
|
||||||
|
::ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
if (true == ::ImGui::BeginMenu("View"))
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < panels_count; ++i)
|
||||||
|
{
|
||||||
|
(void)::ImGui::MenuItem(panels[i], nullptr, &panels_visibility[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
::ImGui::Separator();
|
||||||
|
// The demo window will need to be rendered outside this nested if statement, or else it will close the next frame.
|
||||||
|
(void)::ImGui::MenuItem("ImGui Demo", nullptr, imgui_demo_visible);
|
||||||
|
::ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
::ImGui::EndMainMenuBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (true == *imgui_demo_visible)
|
||||||
|
{
|
||||||
|
::ImGui::ShowDemoWindow(imgui_demo_visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
return return_code;
|
||||||
|
}
|
||||||
|
|
||||||
|
void gui::destroy()
|
||||||
|
{
|
||||||
|
::ImGui_ImplOpenGL3_Shutdown();
|
||||||
|
::ImGui_ImplSDL3_Shutdown();
|
||||||
|
::ImGui::DestroyContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
//=========================================================
|
||||||
|
// Private FUNCTIONS
|
||||||
|
//=========================================================
|
||||||
|
|
||||||
|
void apply_theme()
|
||||||
|
{
|
||||||
|
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] = gui::color::text;
|
||||||
|
style.Colors[ImGuiCol_TextDisabled] = gui::color::text_disable;
|
||||||
|
style.Colors[ImGuiCol_WindowBg] = gui::color::with_alpha(gui::color::background, 0.95F);
|
||||||
|
style.Colors[ImGuiCol_ChildBg] = gui::color::background_dark;
|
||||||
|
style.Colors[ImGuiCol_PopupBg] = gui::color::with_alpha(gui::color::background, 0.94F);
|
||||||
|
style.Colors[ImGuiCol_Border] = gui::color::border;
|
||||||
|
style.Colors[ImGuiCol_BorderShadow] = ImVec4(0.0F, 0.0F, 0.0F, 0.0F);
|
||||||
|
style.Colors[ImGuiCol_FrameBg] = gui::color::background_light;
|
||||||
|
style.Colors[ImGuiCol_FrameBgHovered] = gui::color::lighten(gui::color::background_light, 0.1F);
|
||||||
|
style.Colors[ImGuiCol_FrameBgActive] = gui::color::lighten(gui::color::background_light, 0.2F);
|
||||||
|
style.Colors[ImGuiCol_TitleBg] = gui::color::background_dark;
|
||||||
|
style.Colors[ImGuiCol_TitleBgActive] = gui::color::background;
|
||||||
|
style.Colors[ImGuiCol_TitleBgCollapsed] = gui::color::with_alpha(gui::color::background_dark, 0.51F);
|
||||||
|
style.Colors[ImGuiCol_MenuBarBg] = gui::color::background_dark;
|
||||||
|
style.Colors[ImGuiCol_ScrollbarBg] = gui::color::with_alpha(gui::color::background_dark, 0.53F);
|
||||||
|
style.Colors[ImGuiCol_ScrollbarGrab] = gui::color::background_light;
|
||||||
|
style.Colors[ImGuiCol_ScrollbarGrabHovered] = gui::color::lighten(gui::color::background_light, 0.1F);
|
||||||
|
style.Colors[ImGuiCol_ScrollbarGrabActive] = gui::color::lighten(gui::color::background_light, 0.2F);
|
||||||
|
style.Colors[ImGuiCol_CheckMark] = gui::color::primary;
|
||||||
|
style.Colors[ImGuiCol_SliderGrab] = gui::color::primary;
|
||||||
|
style.Colors[ImGuiCol_SliderGrabActive] = gui::color::primary_active;
|
||||||
|
style.Colors[ImGuiCol_Button] = gui::color::with_alpha(gui::color::primary, 0.4F);
|
||||||
|
style.Colors[ImGuiCol_ButtonHovered] = gui::color::primary_hover;
|
||||||
|
style.Colors[ImGuiCol_ButtonActive] = gui::color::primary_active;
|
||||||
|
style.Colors[ImGuiCol_Header] = gui::color::with_alpha(gui::color::primary, 0.4F);
|
||||||
|
style.Colors[ImGuiCol_HeaderHovered] = gui::color::with_alpha(gui::color::primary, 0.8F);
|
||||||
|
style.Colors[ImGuiCol_HeaderActive] = gui::color::primary;
|
||||||
|
style.Colors[ImGuiCol_Separator] = gui::color::border;
|
||||||
|
style.Colors[ImGuiCol_SeparatorHovered] = gui::color::with_alpha(gui::color::primary, 0.78F);
|
||||||
|
style.Colors[ImGuiCol_SeparatorActive] = gui::color::primary;
|
||||||
|
style.Colors[ImGuiCol_ResizeGrip] = gui::color::with_alpha(gui::color::primary, 0.25F);
|
||||||
|
style.Colors[ImGuiCol_ResizeGripHovered] = gui::color::with_alpha(gui::color::primary, 0.67F);
|
||||||
|
style.Colors[ImGuiCol_ResizeGripActive] = gui::color::with_alpha(gui::color::primary, 0.95F);
|
||||||
|
style.Colors[ImGuiCol_Tab] = gui::color::background_light;
|
||||||
|
style.Colors[ImGuiCol_TabHovered] = gui::color::with_alpha(gui::color::primary, 0.8F);
|
||||||
|
style.Colors[ImGuiCol_TabActive] = gui::color::primary;
|
||||||
|
style.Colors[ImGuiCol_TabUnfocused] = gui::color::background;
|
||||||
|
style.Colors[ImGuiCol_TabUnfocusedActive] = gui::color::lighten(gui::color::background, 0.1F);
|
||||||
|
style.Colors[ImGuiCol_PlotLines] = gui::color::primary;
|
||||||
|
style.Colors[ImGuiCol_PlotLinesHovered] = gui::color::primary_hover;
|
||||||
|
style.Colors[ImGuiCol_PlotHistogram] = gui::color::secondary;
|
||||||
|
style.Colors[ImGuiCol_PlotHistogramHovered] = gui::color::secondary_hover;
|
||||||
|
style.Colors[ImGuiCol_TextSelectedBg] = gui::color::with_alpha(gui::color::primary, 0.35F);
|
||||||
|
style.Colors[ImGuiCol_DragDropTarget] = gui::color::with_alpha(gui::color::secondary, 0.9F);
|
||||||
|
style.Colors[ImGuiCol_NavHighlight] = gui::color::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);
|
||||||
|
}
|
||||||
219
gui/gui.h
Executable file
219
gui/gui.h
Executable file
|
|
@ -0,0 +1,219 @@
|
||||||
|
#ifndef POUND_GUI_H
|
||||||
|
#define POUND_GUI_H
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
#include <vector>
|
||||||
|
#include "memory/arena.h"
|
||||||
|
|
||||||
|
namespace gui
|
||||||
|
{
|
||||||
|
|
||||||
|
#define WINDOW_MINIMUM_SIZE_WIDTH 640
|
||||||
|
#define WINDOW_MINIMUM_SIZE_HEIGHT 480
|
||||||
|
|
||||||
|
#define GUI_SUCCESS 0
|
||||||
|
#define WINDOW_SHOULD_CLOSE 1
|
||||||
|
#define ERROR_OPENGL 2
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NAME
|
||||||
|
* window_t - Structure representing a window with OpenGL context.
|
||||||
|
*
|
||||||
|
* SYNOPSIS
|
||||||
|
* #include "gui/gui.h"
|
||||||
|
*
|
||||||
|
* typedef struct {
|
||||||
|
* SDL_Window* data; // Window created by the SDL library.
|
||||||
|
* SDL_GLContext gl_context; // OpenGL context associated with the window.
|
||||||
|
* } window_t;
|
||||||
|
*
|
||||||
|
* DESCRIPTION
|
||||||
|
* The window_t structure is used to represent a window along with its associated OpenGL context.
|
||||||
|
*
|
||||||
|
* EXAMPLE
|
||||||
|
*
|
||||||
|
* #include "gui/gui.h"
|
||||||
|
* #include <stdio.h>
|
||||||
|
*
|
||||||
|
* int main() {
|
||||||
|
* gui::window_t window;
|
||||||
|
*
|
||||||
|
* // Initialize the widow.
|
||||||
|
* if (!gui::window_init(&window, "Pound Emulator", 800, 600)) {
|
||||||
|
* fprintf(stderr, "Failed to initialize the window. \n");
|
||||||
|
* return -1;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // Clean up when done.
|
||||||
|
* gui::window_destroy(&window);
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
SDL_Window* data;
|
||||||
|
SDL_GLContext gl_context;
|
||||||
|
} window_t;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NAME
|
||||||
|
* window_init - Initializes a window with specified properties.
|
||||||
|
*
|
||||||
|
* SYNOPSIS
|
||||||
|
* #include "gui/gui"
|
||||||
|
*
|
||||||
|
* bool gui::window_init(window_t* window, const char* title, int64_t width, int64_t height);
|
||||||
|
*
|
||||||
|
* DESCRIPTION
|
||||||
|
* The function initializes a window with the given parameters.
|
||||||
|
*
|
||||||
|
* RETURN VALUE
|
||||||
|
* The function returns true if the window successfully initialized with all properties set, false otherwise.
|
||||||
|
*
|
||||||
|
* NOTES
|
||||||
|
* - Ensure that the window is not null before calling this function.
|
||||||
|
* - The window will be created with OpenGL support.
|
||||||
|
*/
|
||||||
|
bool window_init(window_t* window, const char* title, int64_t width, int64_t height);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NAME
|
||||||
|
* window_destroy - Destroys a previously initialized window and its associated OpenGL context.
|
||||||
|
*
|
||||||
|
* SYNOPSIS
|
||||||
|
* #include "gui/gui.h"
|
||||||
|
*
|
||||||
|
* void gui::window_destroy(gui::window_t* window);
|
||||||
|
*
|
||||||
|
* DESCRIPTION
|
||||||
|
* The function cleans up resources associated with a previous initialized window.
|
||||||
|
*
|
||||||
|
* NOTES
|
||||||
|
* - Ensure that the window parameter is valid and points to a previous initialized window structure.
|
||||||
|
* - It is essential to call this function for every window created with gui::window_init() to prevent resource leaks.
|
||||||
|
*/
|
||||||
|
void window_destroy(window_t* window);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NAME
|
||||||
|
* gui_t - Structure representing the main GUI system with window and panel management.
|
||||||
|
*
|
||||||
|
* SYNOPSIS
|
||||||
|
* #include "gui/gui.h"
|
||||||
|
*
|
||||||
|
* typedef struct {
|
||||||
|
* window_t window; // Main window of the GUI with OpenGL context
|
||||||
|
* const char** custom_panels; // Array of pointers to custom panel names
|
||||||
|
* bool* custom_panels_visibility; // Array tracking visibility state of each panel
|
||||||
|
* size_t custom_panels_capacity; // Maximum number of panels that can be managed
|
||||||
|
* } gui_t;
|
||||||
|
*
|
||||||
|
* DESCRIPTION
|
||||||
|
* The gui_t structure represents the core GUI system, managing the main window and any
|
||||||
|
* additional user-defined panels. It provides a container for all resources needed to
|
||||||
|
* maintain and render the graphical interface.
|
||||||
|
*
|
||||||
|
* This structure should be initialized before use and destroyed when no longer needed to
|
||||||
|
* properly manage memory and system resources.
|
||||||
|
*
|
||||||
|
* EXAMPLE
|
||||||
|
*
|
||||||
|
* #include "gui/gui.h"
|
||||||
|
* #include <stdio.h>
|
||||||
|
*
|
||||||
|
* int main() {
|
||||||
|
* gui::gui_t gui;
|
||||||
|
*
|
||||||
|
* // Initialize the GUI system
|
||||||
|
* if (!gui::init(&gui, &main_window)) {
|
||||||
|
* fprintf(stderr, "Failed to initialize the GUI. \n");
|
||||||
|
* return -1;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // Use the GUI for rendering...
|
||||||
|
* ...
|
||||||
|
*
|
||||||
|
* // Clean up when done.
|
||||||
|
* gui::destroy(&gui);
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
window_t window;
|
||||||
|
const char** custom_panels;
|
||||||
|
bool* custom_panels_visibility;
|
||||||
|
size_t custom_panels_capacity;
|
||||||
|
} gui_t;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NAME
|
||||||
|
* init_imgui - Initializes ImGui and its integration with SDL3 and OpenGL.
|
||||||
|
*
|
||||||
|
* SYNOPSIS
|
||||||
|
* #include "gui/gui.h"
|
||||||
|
*
|
||||||
|
* bool gui::init_imgui(gui::window_t* main_window);
|
||||||
|
*
|
||||||
|
* DESCRIPTION
|
||||||
|
* This function initializes the ImGui library along with its integration with
|
||||||
|
* SDL3 for input handling and OpenGL for rendering. It sets up all necessary
|
||||||
|
* configurations and context required for ImGui to work properly.
|
||||||
|
*
|
||||||
|
* RETURN VALUE
|
||||||
|
* Returns true if ImGui initialization was successful, false otherwise.
|
||||||
|
*
|
||||||
|
* NOTES
|
||||||
|
* - The function assumes that the main window has already been created and initialized
|
||||||
|
* - This should be called before any other ImGui functions are used
|
||||||
|
*/
|
||||||
|
bool init_imgui(gui::window_t* main_window);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NAME
|
||||||
|
* render_memu_bar - Renders a main menu bar with standard menus and custom panels.
|
||||||
|
*
|
||||||
|
* SYNOPSIS
|
||||||
|
* #include "gui/gui.h"
|
||||||
|
*
|
||||||
|
* int8_t gui::render_memu_bar(const char** panels, const size_t panels_count,
|
||||||
|
* bool* panels_visibility, bool* imgui_demo_visible);
|
||||||
|
*
|
||||||
|
* DESCRIPTION
|
||||||
|
* This function renders a main menu bar using ImGui that includes standard menus like File and View.
|
||||||
|
*
|
||||||
|
* The File menu contains an Exit option that triggers window closure.
|
||||||
|
* The View menu displays all custom panels registered with their visibility toggles,
|
||||||
|
* plus an optional ImGui demo window toggle.
|
||||||
|
*
|
||||||
|
* PARAMETERS
|
||||||
|
* panels - Array of panel names to be displayed in the View menu
|
||||||
|
* panels_count - Number of panels in the array
|
||||||
|
* panels_visibility - Boolean array indicating current visibility state for each panel
|
||||||
|
* imgui_demo_visible - Pointer to boolean controlling visibility of ImGui demo window
|
||||||
|
*
|
||||||
|
* RETURN VALUE
|
||||||
|
* Returns one of these codes:
|
||||||
|
* GUI_SUCCESS - Normal operation
|
||||||
|
* WINDOW_SHOULD_CLOSE - Exit option was selected
|
||||||
|
*
|
||||||
|
* NOTES
|
||||||
|
* - Panel visibility state is toggled when corresponding menu items are clicked
|
||||||
|
*/
|
||||||
|
int8_t render_memu_bar(const char** panels, size_t panels_count, bool* panels_visibility, bool* imgui_demo_visible);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NAME
|
||||||
|
* destroy - Destroys a GUI system and cleans up resources.
|
||||||
|
*
|
||||||
|
* SYNOPSIS
|
||||||
|
* #include "gui/gui.h"
|
||||||
|
*
|
||||||
|
* void gui::destroy();
|
||||||
|
*
|
||||||
|
* DESCRIPTION
|
||||||
|
* The function cleans up and releases all resources associated with the GUI system.
|
||||||
|
*/
|
||||||
|
void destroy();
|
||||||
|
|
||||||
|
} // namespace gui
|
||||||
|
|
||||||
|
#endif //POUND_GUI_H
|
||||||
118
gui/panels.cpp
Executable file
118
gui/panels.cpp
Executable file
|
|
@ -0,0 +1,118 @@
|
||||||
|
#include "panels.h"
|
||||||
|
#include <math.h>
|
||||||
|
#include "ARM/cpu.h"
|
||||||
|
#include "imgui.h"
|
||||||
|
|
||||||
|
int8_t gui::panel::render_performance_panel(gui::panel::performance_panel_t* panel, performance_data_t* data,
|
||||||
|
std::chrono::steady_clock::time_point* last_render)
|
||||||
|
{
|
||||||
|
bool is_visible = true;
|
||||||
|
(void)::ImGui::Begin(PANEL_NAME_PERFORMANCE, &is_visible);
|
||||||
|
if (false == is_visible)
|
||||||
|
{
|
||||||
|
::ImGui::End();
|
||||||
|
return ERROR_PANEL_IS_CLOSED;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto now = std::chrono::steady_clock::now();
|
||||||
|
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - *last_render);
|
||||||
|
++data->frame_count;
|
||||||
|
if (duration.count() >= 100)
|
||||||
|
{
|
||||||
|
// Every 100ms
|
||||||
|
data->fps = data->frame_count * 1000.0f / duration.count();
|
||||||
|
data->frame_time = duration.count() / (float)data->frame_count;
|
||||||
|
|
||||||
|
panel->fps_history.push_back(data->fps);
|
||||||
|
panel->frame_time_history.push_back(data->frame_time);
|
||||||
|
|
||||||
|
// Keep history size limited
|
||||||
|
while (panel->fps_history.size() > FRAME_TIME_HISTORY_SIZE)
|
||||||
|
{
|
||||||
|
panel->fps_history.pop_front();
|
||||||
|
}
|
||||||
|
while (panel->frame_time_history.size() > FRAME_TIME_HISTORY_SIZE)
|
||||||
|
|
||||||
|
{
|
||||||
|
panel->frame_time_history.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
|
data->frame_count = 0;
|
||||||
|
*last_render = now;
|
||||||
|
|
||||||
|
// TODO(GloriousTaco:gui): Get actual CPU and memory usage
|
||||||
|
data->cpu_usage = 0.0f;
|
||||||
|
data->memory_usage = 0.0f;
|
||||||
|
}
|
||||||
|
::ImGui::Text("FPS: %.1f", data->fps);
|
||||||
|
::ImGui::Text("Frame Time: %.2f ms", data->frame_time);
|
||||||
|
::ImGui::Separator();
|
||||||
|
|
||||||
|
// Frame Time Graph
|
||||||
|
if (false == panel->frame_time_history.empty())
|
||||||
|
{
|
||||||
|
float frame_time_array[FRAME_TIME_HISTORY_SIZE] = {};
|
||||||
|
(void)std::copy(panel->frame_time_history.begin(), panel->frame_time_history.end(), frame_time_array);
|
||||||
|
|
||||||
|
::ImGui::Text("Frame Time History (ms):");
|
||||||
|
::ImGui::PlotLines("##FrameTime", frame_time_array, (int)panel->frame_time_history.size(), 0, nullptr, 0.0f,
|
||||||
|
33.33f, ImVec2(0, 80));
|
||||||
|
}
|
||||||
|
|
||||||
|
::ImGui::Separator();
|
||||||
|
|
||||||
|
// System info (placeholder)
|
||||||
|
::ImGui::Text("CPU Usage: %.1f%%", data->cpu_usage);
|
||||||
|
::ImGui::Text("Memory Usage: %.1f MB", 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();
|
||||||
|
return PANEL_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
int8_t gui::panel::render_cpu_panel(bool* show_cpu_result_popup)
|
||||||
|
{
|
||||||
|
bool is_visible = true;
|
||||||
|
(void)::ImGui::Begin(PANEL_NAME_CPU, &is_visible, ImGuiWindowFlags_NoCollapse);
|
||||||
|
if (false == is_visible)
|
||||||
|
{
|
||||||
|
::ImGui::End();
|
||||||
|
return ERROR_PANEL_IS_CLOSED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (::ImGui::Button("Run CPU Test", ImVec2(120, 0)))
|
||||||
|
{
|
||||||
|
::cpuTest();
|
||||||
|
*show_cpu_result_popup = true;
|
||||||
|
}
|
||||||
|
if (true == *show_cpu_result_popup)
|
||||||
|
{
|
||||||
|
::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_cpu_result_popup = false;
|
||||||
|
::ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
::ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
::ImGui::End();
|
||||||
|
return PANEL_SUCCESS;
|
||||||
|
}
|
||||||
113
gui/panels.h
Executable file
113
gui/panels.h
Executable file
|
|
@ -0,0 +1,113 @@
|
||||||
|
#ifndef POUND_PANELS_H
|
||||||
|
#define POUND_PANELS_H
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <deque>
|
||||||
|
|
||||||
|
namespace gui::panel
|
||||||
|
{
|
||||||
|
#define PANEL_NAME_CPU "Cpu"
|
||||||
|
#define PANEL_NAME_PERFORMANCE "Performance"
|
||||||
|
#define FRAME_TIME_HISTORY_SIZE 128
|
||||||
|
|
||||||
|
#define PANEL_SUCCESS 0
|
||||||
|
#define ERROR_PANEL_IS_CLOSED 1
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NAME
|
||||||
|
* performance_panel_t - Structure for tracking performance metrics history.
|
||||||
|
*
|
||||||
|
* SYNOPSIS
|
||||||
|
* #include "gui/panels.h"
|
||||||
|
*
|
||||||
|
* typedef struct {
|
||||||
|
* std::deque<float_t> fps_history; // Historical FPS values over time
|
||||||
|
* std::deque<float_t> frame_time_history; // Historical frame time values over time
|
||||||
|
* } performance_panel_t;
|
||||||
|
*
|
||||||
|
* DESCRIPTION
|
||||||
|
* The performance_panel_t structure maintains a history of performance metrics for visualization and analysis.
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
std::deque<float_t> fps_history;
|
||||||
|
std::deque<float_t> frame_time_history;
|
||||||
|
} performance_panel_t;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NAME
|
||||||
|
* performance_data_t - Structure for storing current performance metrics.
|
||||||
|
*
|
||||||
|
* SYNOPSIS
|
||||||
|
* #include "gui/panels.h"
|
||||||
|
*
|
||||||
|
* typedef struct {
|
||||||
|
* float_t fps; // Current frames per second
|
||||||
|
* float_t frame_time; // Time taken to render a single frame in seconds
|
||||||
|
* float_t cpu_usage; // CPU usage percentage
|
||||||
|
* float_t memory_usage; // Memory usage percentage
|
||||||
|
* int32_t frame_count; // Total number of frames rendered since startup
|
||||||
|
* } performance_data_t;
|
||||||
|
*
|
||||||
|
* DESCRIPTION
|
||||||
|
* The performance_data_t structure contains current runtime metrics per frame for monitoring
|
||||||
|
* application performance.
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
float_t fps;
|
||||||
|
float_t frame_time;
|
||||||
|
float_t cpu_usage;
|
||||||
|
float_t memory_usage;
|
||||||
|
int32_t frame_count;
|
||||||
|
|
||||||
|
} performance_data_t;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NAME
|
||||||
|
* render_performance_panel - Renders a performance monitoring panel with FPS and system metrics.
|
||||||
|
*
|
||||||
|
* SYNOPSIS
|
||||||
|
* #include "gui/gui.h"
|
||||||
|
*
|
||||||
|
* int8_t gui::panel::render_performance_panel(gui::panel::performance_panel_t* panel,
|
||||||
|
* performance_data_t* data,
|
||||||
|
* std::chrono::steady_clock::time_point* last_render);
|
||||||
|
*
|
||||||
|
* DESCRIPTION
|
||||||
|
* This function renders a performance monitoring panel that tracks and displays:
|
||||||
|
* - Real-time FPS (frames per second) calculation
|
||||||
|
* - Frame time statistics with historical graph visualization
|
||||||
|
* - CPU and memory usage metrics (placeholder values)
|
||||||
|
* - Emulation statistics (placeholder values)
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* RETURN VALUE
|
||||||
|
* Returns one of these codes:
|
||||||
|
* PANEL_SUCCESS - Normal operation
|
||||||
|
* ERROR_PANEL_IS_CLOSED - Panel was closed by user
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
int8_t render_performance_panel(performance_panel_t* panel, performance_data_t* data,
|
||||||
|
std::chrono::steady_clock::time_point* last_render);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NAME
|
||||||
|
* render_cpu_panel - Renders a CPU testing panel with test execution capability.
|
||||||
|
*
|
||||||
|
* SYNOPSIS
|
||||||
|
* #include "gui/panels.cpp"
|
||||||
|
*
|
||||||
|
* int8_t gui::panel::render_cpu_panel(bool* show_cpu_result_popup);
|
||||||
|
*
|
||||||
|
* DESCRIPTION
|
||||||
|
* This function renders a CPU testing panel.
|
||||||
|
*
|
||||||
|
* RETURN VALUE
|
||||||
|
* Returns one of these codes:
|
||||||
|
* PANEL_SUCCESS - Normal operation
|
||||||
|
* ERROR_PANEL_IS_CLOSED - Panel was closed by user
|
||||||
|
*/
|
||||||
|
int8_t render_cpu_panel(bool* show_cpu_result_popup);
|
||||||
|
} // namespace gui::panel
|
||||||
|
#endif //POUND_PANELS_H
|
||||||
|
|
@ -1,143 +0,0 @@
|
||||||
// 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
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
// 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
|
|
||||||
|
|
@ -1,110 +0,0 @@
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
// 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
|
|
||||||
|
|
@ -1,100 +0,0 @@
|
||||||
// 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
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
// 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
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue