5#include "absl/strings/str_format.h"
10#include "imgui/imgui.h"
22constexpr float kStandardSpacing = 8.0f;
24constexpr float kButtonHeight = 30.0f;
33void AddSectionSpacing() {
45 const auto& theme = theme_manager.GetCurrentTheme();
50 if (ImGui::Shortcut(ImGuiKey_Space, ImGuiInputFlags_RouteGlobal)) {
55 if (ImGui::Shortcut(ImGuiKey_F10, ImGuiInputFlags_RouteGlobal)) {
57 emu->
snes().RunFrame();
67 bool is_running = emu->
running();
69 if (ImGui::Button(
ICON_MD_PAUSE, ImVec2(50, kButtonHeight))) {
72 if (ImGui::IsItemHovered()) {
73 ImGui::SetTooltip(
"Pause emulation (Space)");
79 if (ImGui::IsItemHovered()) {
80 ImGui::SetTooltip(
"Start emulation (Space)");
89 emu->
snes().RunFrame();
92 if (ImGui::IsItemHovered()) {
93 ImGui::SetTooltip(
"Step one frame (F10)");
101 LOG_INFO(
"Emulator",
"System reset");
103 if (ImGui::IsItemHovered()) {
104 ImGui::SetTooltip(
"Reset SNES (Ctrl+R)");
112 if (!rom_path.empty()) {
115 if (ext ==
".sfc" || ext ==
".smc" || ext ==
".SFC" || ext ==
".SMC") {
118 std::ifstream rom_file(rom_path, std::ios::binary);
119 if (rom_file.good()) {
120 std::vector<uint8_t> rom_data(
121 (std::istreambuf_iterator<char>(rom_file)),
122 std::istreambuf_iterator<char>()
127 if (!rom_data.empty()) {
129 LOG_INFO(
"Emulator",
"Loaded ROM: %s (%zu bytes)",
132 LOG_ERROR(
"Emulator",
"ROM file is empty: %s", rom_path.c_str());
135 LOG_ERROR(
"Emulator",
"Failed to open ROM file: %s", rom_path.c_str());
137 }
catch (
const std::exception& e) {
138 LOG_ERROR(
"Emulator",
"Error loading ROM: %s", e.what());
141 LOG_WARN(
"Emulator",
"Invalid ROM file extension: %s (expected .sfc or .smc)",
146 if (ImGui::IsItemHovered()) {
147 ImGui::SetTooltip(
"Load a different ROM file\n"
148 "Allows testing hacks with assembly patches applied");
160 if (ImGui::IsItemHovered()) {
161 ImGui::SetTooltip(
"Enable debugger features");
183 if (ImGui::IsItemHovered()) {
184 ImGui::SetTooltip(
"Fast forward (shortcut: hold Tab)");
196 }
else if (fps >= 45.0) {
202 ImGui::TextColored(fps_color,
ICON_MD_SPEED " %.1f FPS", fps);
209 ImVec4 audio_color = audio_status.
is_playing ?
214 audio_status.queued_frames);
216 if (ImGui::IsItemHovered()) {
217 ImGui::SetTooltip(
"Audio Backend: %s\nQueued: %u frames\nPlaying: %s",
219 audio_status.queued_frames,
220 audio_status.is_playing ?
"YES" :
"NO");
226 if (ImGui::Checkbox(
ICON_MD_SETTINGS " SDL Audio Stream", &use_sdl_audio_stream)) {
229 if (ImGui::IsItemHovered()) {
230 ImGui::SetTooltip(
"Use SDL audio stream for audio");
242 ImGuiIO& io = ImGui::GetIO();
243 if (io.WantCaptureKeyboard) {
247 if (ImGui::IsItemHovered()) {
248 ImGui::SetTooltip(
"Keyboard captured by UI\nGame input disabled");
254 if (ImGui::IsItemHovered()) {
255 ImGui::SetTooltip(
"Game input active\nPress F1 for controls");
259 ImGui::PopStyleColor(3);
266 const auto& theme = theme_manager.GetCurrentTheme();
269 ImGui::BeginChild(
"##SNES_PPU", ImVec2(0, 0),
true,
270 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
272 ImVec2 canvas_size = ImGui::GetContentRegionAvail();
273 ImVec2 snes_size = ImVec2(512, 480);
277 float aspect = snes_size.x / snes_size.y;
278 float display_w = canvas_size.x;
279 float display_h = display_w / aspect;
281 if (display_h > canvas_size.y) {
282 display_h = canvas_size.y;
283 display_w = display_h * aspect;
286 float pos_x = (canvas_size.x - display_w) * 0.5f;
287 float pos_y = (canvas_size.y - display_h) * 0.5f;
289 ImGui::SetCursorPos(ImVec2(pos_x, pos_y));
292 ImGui::Image((ImTextureID)(intptr_t)emu->
ppu_texture(),
293 ImVec2(display_w, display_h),
294 ImVec2(0, 0), ImVec2(1, 1));
298 if (ImGui::IsItemHovered()) {
302 ImDrawList* draw_list = ImGui::GetWindowDrawList();
303 ImVec2 screen_pos = ImGui::GetItemRectMin();
304 ImVec2 screen_size = ImGui::GetItemRectMax();
305 draw_list->AddRect(screen_pos, screen_size,
311 ImVec2 text_size = ImGui::CalcTextSize(
"Load a ROM to start emulation");
312 ImGui::SetCursorPos(ImVec2((canvas_size.x - text_size.x) * 0.5f,
313 (canvas_size.y - text_size.y) * 0.5f - 20));
316 ImGui::SetCursorPosX((canvas_size.x - text_size.x) * 0.5f);
318 "Load a ROM to start emulation");
319 ImGui::SetCursorPosX((canvas_size.x - ImGui::CalcTextSize(
"512x480 SNES output").x) * 0.5f);
321 "512x480 SNES output");
325 ImGui::PopStyleColor();
332 const auto& theme = theme_manager.GetCurrentTheme();
335 ImGui::BeginChild(
"##Performance", ImVec2(0, 0),
true);
344 if (ImGui::CollapsingHeader(
ICON_MD_SHOW_CHART " Frame Rate", ImGuiTreeNodeFlags_DefaultOpen)) {
345 ImGui::Text(
"Current: %.2f FPS", metrics.fps);
346 ImGui::Text(
"Target: %.2f FPS", emu->
snes().memory().pal_timing() ? 50.0 : 60.0);
352 if (ImGui::CollapsingHeader(
ICON_MD_MEMORY " CPU Status", ImGuiTreeNodeFlags_DefaultOpen)) {
353 ImGui::Text(
"PC: $%02X:%04X", metrics.cpu_pb, metrics.cpu_pc);
354 ImGui::Text(
"Cycles: %llu", metrics.cycles);
358 if (ImGui::CollapsingHeader(
ICON_MD_AUDIOTRACK " Audio Status", ImGuiTreeNodeFlags_DefaultOpen)) {
362 ImGui::Text(
"Queued: %u frames", audio_status.queued_frames);
363 ImGui::Text(
"Playing: %s", audio_status.is_playing ?
"YES" :
"NO");
370 ImGui::PopStyleColor();
374 if (!show || !*show)
return;
377 const auto& theme = theme_manager.GetCurrentTheme();
380 ImVec2 center = ImGui::GetMainViewport()->GetCenter();
381 ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
382 ImGui::SetNextWindowSize(ImVec2(550, 600), ImGuiCond_Appearing);
388 ImGuiWindowFlags_NoCollapse)) {
395 if (ImGui::BeginTable(
"EmulatorControls", 2, ImGuiTableFlags_Borders)) {
396 ImGui::TableSetupColumn(
"Key", ImGuiTableColumnFlags_WidthFixed, 120);
397 ImGui::TableSetupColumn(
"Action", ImGuiTableColumnFlags_WidthStretch);
398 ImGui::TableHeadersRow();
400 auto AddRow = [](
const char* key,
const char* action) {
401 ImGui::TableNextRow();
402 ImGui::TableNextColumn();
403 ImGui::Text(
"%s", key);
404 ImGui::TableNextColumn();
405 ImGui::Text(
"%s", action);
408 AddRow(
"Space",
"Play/Pause emulation");
409 AddRow(
"F10",
"Step one frame");
410 AddRow(
"Ctrl+R",
"Reset SNES");
411 AddRow(
"Tab (hold)",
"Turbo mode (fast forward)");
412 AddRow(
"F1",
"Show/hide this help");
426 if (ImGui::BeginTable(
"GameControls", 2, ImGuiTableFlags_Borders)) {
427 ImGui::TableSetupColumn(
"Key", ImGuiTableColumnFlags_WidthFixed, 120);
428 ImGui::TableSetupColumn(
"Button", ImGuiTableColumnFlags_WidthStretch);
429 ImGui::TableHeadersRow();
431 auto AddRow = [](
const char* key,
const char* button) {
432 ImGui::TableNextRow();
433 ImGui::TableNextColumn();
434 ImGui::Text(
"%s", key);
435 ImGui::TableNextColumn();
436 ImGui::Text(
"%s", button);
439 AddRow(
"Arrow Keys",
"D-Pad (Up/Down/Left/Right)");
440 AddRow(
"X",
"A Button");
441 AddRow(
"Z",
"B Button");
442 AddRow(
"S",
"X Button");
443 AddRow(
"A",
"Y Button");
444 AddRow(
"D",
"L Shoulder");
445 AddRow(
"C",
"R Shoulder");
446 AddRow(
"Enter",
"Start");
447 AddRow(
"RShift",
"Select");
461 ImGui::BulletText(
"Input is disabled when typing in UI fields");
462 ImGui::BulletText(
"Check the status bar for input capture state");
463 ImGui::BulletText(
"Click the game screen to ensure focus");
464 ImGui::BulletText(
"The emulator continues running in background");
469 if (ImGui::Button(
"Close", ImVec2(-1, 30))) {
475 ImGui::PopStyleColor(2);
482 const auto& theme = theme_manager.GetCurrentTheme();
495 static bool show_shortcuts =
false;
496 if (ImGui::IsKeyPressed(ImGuiKey_F1)) {
497 show_shortcuts = !show_shortcuts;
501 ImGui::PopStyleColor();
A class for emulating and debugging SNES games.
gfx::IRenderer * renderer()
void Initialize(gfx::IRenderer *renderer, const std::vector< uint8_t > &rom_data)
bool is_debugging() const
void set_turbo_mode(bool turbo)
void set_use_sdl_audio_stream(bool enabled)
double GetCurrentFPS() const
void set_debugging(bool debugging)
bool use_sdl_audio_stream() const
bool is_turbo_mode() const
void set_running(bool running)
bool is_snes_initialized() const
audio::IAudioBackend * audio_backend()
auto running() const -> bool
EmulatorMetrics GetMetrics()
virtual std::string GetBackendName() const =0
virtual AudioStatus GetStatus() const =0
static ThemeManager & Get()
static std::string ShowOpenFileDialog()
ShowOpenFileDialog opens a file dialog and returns the selected filepath. Uses global feature flag to...
#define ICON_MD_FOLDER_OPEN
#define ICON_MD_VOLUME_UP
#define ICON_MD_FAST_FORWARD
#define ICON_MD_PLAY_ARROW
#define ICON_MD_VIDEOGAME_ASSET
#define ICON_MD_BUG_REPORT
#define ICON_MD_SHOW_CHART
#define ICON_MD_AUDIOTRACK
#define ICON_MD_SKIP_NEXT
#define ICON_MD_SPORTS_ESPORTS
#define ICON_MD_VOLUME_OFF
#define ICON_MD_RESTART_ALT
#define LOG_ERROR(category, format,...)
#define LOG_WARN(category, format,...)
#define LOG_INFO(category, format,...)
constexpr float kIconSize
constexpr float kSectionSpacing
void RenderPerformanceMonitor(Emulator *emu)
Performance metrics (FPS, frame time, audio status)
void RenderKeyboardShortcuts(bool *show)
Keyboard shortcuts help overlay (F1 in modern emulators)
void RenderSnesPpu(Emulator *emu)
SNES PPU output display.
void RenderNavBar(Emulator *emu)
Navigation bar with play/pause, step, reset controls.
void RenderEmulatorInterface(Emulator *emu)
Main emulator UI interface - renders the emulator window.
Graphical User Interface (GUI) components for the application.
ImVec4 ConvertColorToImVec4(const Color &color)
std::string GetFileName(const std::string &filename)
Gets the filename from a full path.
std::string GetFileExtension(const std::string &filename)
Gets the file extension from a filename.
Main namespace for the application.