yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
emulator_ui.cc
Go to the documentation of this file.
2
3#include <fstream>
4
5#include "absl/strings/str_format.h"
6#include "app/emu/emulator.h"
10#include "imgui/imgui.h"
11#include "util/file_util.h"
12#include "util/log.h"
13
14namespace yaze {
15namespace emu {
16namespace ui {
17
18using namespace yaze::gui;
19
20namespace {
21// UI Constants for consistent spacing
22constexpr float kStandardSpacing = 8.0f;
23constexpr float kSectionSpacing = 16.0f;
24constexpr float kButtonHeight = 30.0f;
25constexpr float kIconSize = 24.0f;
26
27// Helper to add consistent spacing
28void AddSpacing() {
29 ImGui::Spacing();
30 ImGui::Spacing();
31}
32
33void AddSectionSpacing() {
34 ImGui::Spacing();
35 ImGui::Separator();
36 ImGui::Spacing();
37}
38
39} // namespace
40
42 if (!emu) return;
43
44 auto& theme_manager = ThemeManager::Get();
45 const auto& theme = theme_manager.GetCurrentTheme();
46
47 // Handle keyboard shortcuts for emulator control
48 // IMPORTANT: Use Shortcut() to avoid conflicts with game input
49 // Space - toggle play/pause (only when not typing in text fields)
50 if (ImGui::Shortcut(ImGuiKey_Space, ImGuiInputFlags_RouteGlobal)) {
51 emu->set_running(!emu->running());
52 }
53
54 // F10 - step one frame
55 if (ImGui::Shortcut(ImGuiKey_F10, ImGuiInputFlags_RouteGlobal)) {
56 if (!emu->running()) {
57 emu->snes().RunFrame();
58 }
59 }
60
61 // Navbar with theme colors
62 ImGui::PushStyleColor(ImGuiCol_Button, ConvertColorToImVec4(theme.button));
63 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ConvertColorToImVec4(theme.button_hovered));
64 ImGui::PushStyleColor(ImGuiCol_ButtonActive, ConvertColorToImVec4(theme.button_active));
65
66 // Play/Pause button with icon
67 bool is_running = emu->running();
68 if (is_running) {
69 if (ImGui::Button(ICON_MD_PAUSE, ImVec2(50, kButtonHeight))) {
70 emu->set_running(false);
71 }
72 if (ImGui::IsItemHovered()) {
73 ImGui::SetTooltip("Pause emulation (Space)");
74 }
75 } else {
76 if (ImGui::Button(ICON_MD_PLAY_ARROW, ImVec2(50, kButtonHeight))) {
77 emu->set_running(true);
78 }
79 if (ImGui::IsItemHovered()) {
80 ImGui::SetTooltip("Start emulation (Space)");
81 }
82 }
83
84 ImGui::SameLine();
85
86 // Step button
87 if (ImGui::Button(ICON_MD_SKIP_NEXT, ImVec2(50, kButtonHeight))) {
88 if (!is_running) {
89 emu->snes().RunFrame();
90 }
91 }
92 if (ImGui::IsItemHovered()) {
93 ImGui::SetTooltip("Step one frame (F10)");
94 }
95
96 ImGui::SameLine();
97
98 // Reset button
99 if (ImGui::Button(ICON_MD_RESTART_ALT, ImVec2(50, kButtonHeight))) {
100 emu->snes().Reset();
101 LOG_INFO("Emulator", "System reset");
102 }
103 if (ImGui::IsItemHovered()) {
104 ImGui::SetTooltip("Reset SNES (Ctrl+R)");
105 }
106
107 ImGui::SameLine();
108
109 // Load ROM button
110 if (ImGui::Button(ICON_MD_FOLDER_OPEN " Load ROM", ImVec2(110, kButtonHeight))) {
111 std::string rom_path = util::FileDialogWrapper::ShowOpenFileDialog();
112 if (!rom_path.empty()) {
113 // Check if it's a valid ROM file extension
114 std::string ext = util::GetFileExtension(rom_path);
115 if (ext == ".sfc" || ext == ".smc" || ext == ".SFC" || ext == ".SMC") {
116 try {
117 // Read ROM file into memory
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>()
123 );
124 rom_file.close();
125
126 // Reinitialize emulator with new ROM
127 if (!rom_data.empty()) {
128 emu->Initialize(emu->renderer(), rom_data);
129 LOG_INFO("Emulator", "Loaded ROM: %s (%zu bytes)",
130 util::GetFileName(rom_path).c_str(), rom_data.size());
131 } else {
132 LOG_ERROR("Emulator", "ROM file is empty: %s", rom_path.c_str());
133 }
134 } else {
135 LOG_ERROR("Emulator", "Failed to open ROM file: %s", rom_path.c_str());
136 }
137 } catch (const std::exception& e) {
138 LOG_ERROR("Emulator", "Error loading ROM: %s", e.what());
139 }
140 } else {
141 LOG_WARN("Emulator", "Invalid ROM file extension: %s (expected .sfc or .smc)",
142 ext.c_str());
143 }
144 }
145 }
146 if (ImGui::IsItemHovered()) {
147 ImGui::SetTooltip("Load a different ROM file\n"
148 "Allows testing hacks with assembly patches applied");
149 }
150
151 ImGui::SameLine();
152 ImGui::Separator();
153 ImGui::SameLine();
154
155 // Debugger toggle
156 bool is_debugging = emu->is_debugging();
157 if (ImGui::Checkbox(ICON_MD_BUG_REPORT " Debug", &is_debugging)) {
158 emu->set_debugging(is_debugging);
159 }
160 if (ImGui::IsItemHovered()) {
161 ImGui::SetTooltip("Enable debugger features");
162 }
163
164 ImGui::SameLine();
165
166 // Recording toggle (for DisassemblyViewer)
167 // Access through emulator's disassembly viewer
168 // bool recording = emu->disassembly_viewer().IsRecording();
169 // if (ImGui::Checkbox(ICON_MD_FIBER_MANUAL_RECORD " Rec", &recording)) {
170 // emu->disassembly_viewer().SetRecording(recording);
171 // }
172 // if (ImGui::IsItemHovered()) {
173 // ImGui::SetTooltip("Record instructions to Disassembly Viewer\n(Lightweight - uses sparse address map)");
174 // }
175
176 ImGui::SameLine();
177
178 // Turbo mode
179 bool turbo = emu->is_turbo_mode();
180 if (ImGui::Checkbox(ICON_MD_FAST_FORWARD " Turbo", &turbo)) {
181 emu->set_turbo_mode(turbo);
182 }
183 if (ImGui::IsItemHovered()) {
184 ImGui::SetTooltip("Fast forward (shortcut: hold Tab)");
185 }
186
187 ImGui::SameLine();
188 ImGui::Separator();
189 ImGui::SameLine();
190
191 // FPS Counter with color coding
192 double fps = emu->GetCurrentFPS();
193 ImVec4 fps_color;
194 if (fps >= 58.0) {
195 fps_color = ConvertColorToImVec4(theme.success); // Green for good FPS
196 } else if (fps >= 45.0) {
197 fps_color = ConvertColorToImVec4(theme.warning); // Yellow for okay FPS
198 } else {
199 fps_color = ConvertColorToImVec4(theme.error); // Red for bad FPS
200 }
201
202 ImGui::TextColored(fps_color, ICON_MD_SPEED " %.1f FPS", fps);
203
204 ImGui::SameLine();
205
206 // Audio backend status
207 if (emu->audio_backend()) {
208 auto audio_status = emu->audio_backend()->GetStatus();
209 ImVec4 audio_color = audio_status.is_playing ?
210 ConvertColorToImVec4(theme.success) : ConvertColorToImVec4(theme.text_disabled);
211
212 ImGui::TextColored(audio_color, ICON_MD_VOLUME_UP " %s | %u frames",
213 emu->audio_backend()->GetBackendName().c_str(),
214 audio_status.queued_frames);
215
216 if (ImGui::IsItemHovered()) {
217 ImGui::SetTooltip("Audio Backend: %s\nQueued: %u frames\nPlaying: %s",
218 emu->audio_backend()->GetBackendName().c_str(),
219 audio_status.queued_frames,
220 audio_status.is_playing ? "YES" : "NO");
221 }
222
223
224 ImGui::SameLine();
225 static bool use_sdl_audio_stream = emu->use_sdl_audio_stream();
226 if (ImGui::Checkbox(ICON_MD_SETTINGS " SDL Audio Stream", &use_sdl_audio_stream)) {
227 emu->set_use_sdl_audio_stream(use_sdl_audio_stream);
228 }
229 if (ImGui::IsItemHovered()) {
230 ImGui::SetTooltip("Use SDL audio stream for audio");
231 }
232 } else {
233 ImGui::TextColored(ConvertColorToImVec4(theme.error),
234 ICON_MD_VOLUME_OFF " No Backend");
235 }
236
237 ImGui::SameLine();
238 ImGui::Separator();
239 ImGui::SameLine();
240
241 // Input capture status indicator (like modern emulators)
242 ImGuiIO& io = ImGui::GetIO();
243 if (io.WantCaptureKeyboard) {
244 // ImGui is capturing keyboard (typing in UI)
245 ImGui::TextColored(ConvertColorToImVec4(theme.warning),
246 ICON_MD_KEYBOARD " UI");
247 if (ImGui::IsItemHovered()) {
248 ImGui::SetTooltip("Keyboard captured by UI\nGame input disabled");
249 }
250 } else {
251 // Emulator can receive input
252 ImGui::TextColored(ConvertColorToImVec4(theme.success),
253 ICON_MD_SPORTS_ESPORTS " Game");
254 if (ImGui::IsItemHovered()) {
255 ImGui::SetTooltip("Game input active\nPress F1 for controls");
256 }
257 }
258
259 ImGui::PopStyleColor(3);
260}
261
263 if (!emu) return;
264
265 auto& theme_manager = ThemeManager::Get();
266 const auto& theme = theme_manager.GetCurrentTheme();
267
268 ImGui::PushStyleColor(ImGuiCol_ChildBg, ConvertColorToImVec4(theme.editor_background));
269 ImGui::BeginChild("##SNES_PPU", ImVec2(0, 0), true,
270 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
271
272 ImVec2 canvas_size = ImGui::GetContentRegionAvail();
273 ImVec2 snes_size = ImVec2(512, 480);
274
275 if (emu->is_snes_initialized() && emu->ppu_texture()) {
276 // Center the SNES display with aspect ratio preservation
277 float aspect = snes_size.x / snes_size.y;
278 float display_w = canvas_size.x;
279 float display_h = display_w / aspect;
280
281 if (display_h > canvas_size.y) {
282 display_h = canvas_size.y;
283 display_w = display_h * aspect;
284 }
285
286 float pos_x = (canvas_size.x - display_w) * 0.5f;
287 float pos_y = (canvas_size.y - display_h) * 0.5f;
288
289 ImGui::SetCursorPos(ImVec2(pos_x, pos_y));
290
291 // Render PPU texture with click detection for focus
292 ImGui::Image((ImTextureID)(intptr_t)emu->ppu_texture(),
293 ImVec2(display_w, display_h),
294 ImVec2(0, 0), ImVec2(1, 1));
295
296 // Allow clicking on the display to ensure focus
297 // Modern emulators make the game area "sticky" for input
298 if (ImGui::IsItemHovered()) {
299 // ImGui::SetTooltip("Click to ensure game input focus");
300
301 // Visual feedback when hovered (subtle border)
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,
306 ImGui::ColorConvertFloat4ToU32(ConvertColorToImVec4(theme.accent)),
307 0.0f, 0, 2.0f);
308 }
309 } else {
310 // Not initialized - show helpful placeholder
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));
314 ImGui::TextColored(ConvertColorToImVec4(theme.text_disabled),
316 ImGui::SetCursorPosX((canvas_size.x - text_size.x) * 0.5f);
317 ImGui::TextColored(ConvertColorToImVec4(theme.text_primary),
318 "Load a ROM to start emulation");
319 ImGui::SetCursorPosX((canvas_size.x - ImGui::CalcTextSize("512x480 SNES output").x) * 0.5f);
320 ImGui::TextColored(ConvertColorToImVec4(theme.text_disabled),
321 "512x480 SNES output");
322 }
323
324 ImGui::EndChild();
325 ImGui::PopStyleColor();
326}
327
329 if (!emu) return;
330
331 auto& theme_manager = ThemeManager::Get();
332 const auto& theme = theme_manager.GetCurrentTheme();
333
334 ImGui::PushStyleColor(ImGuiCol_ChildBg, ConvertColorToImVec4(theme.child_bg));
335 ImGui::BeginChild("##Performance", ImVec2(0, 0), true);
336
337 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
338 ICON_MD_SPEED " Performance Monitor");
339 AddSectionSpacing();
340
341 auto metrics = emu->GetMetrics();
342
343 // FPS Graph
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);
347
348 // TODO: Add FPS graph with ImPlot
349 }
350
351 // CPU Stats
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);
355 }
356
357 // Audio Stats
358 if (ImGui::CollapsingHeader(ICON_MD_AUDIOTRACK " Audio Status", ImGuiTreeNodeFlags_DefaultOpen)) {
359 if (emu->audio_backend()) {
360 auto audio_status = emu->audio_backend()->GetStatus();
361 ImGui::Text("Backend: %s", emu->audio_backend()->GetBackendName().c_str());
362 ImGui::Text("Queued: %u frames", audio_status.queued_frames);
363 ImGui::Text("Playing: %s", audio_status.is_playing ? "YES" : "NO");
364 } else {
365 ImGui::TextColored(ConvertColorToImVec4(theme.error), "No audio backend");
366 }
367 }
368
369 ImGui::EndChild();
370 ImGui::PopStyleColor();
371}
372
373void RenderKeyboardShortcuts(bool* show) {
374 if (!show || !*show) return;
375
376 auto& theme_manager = ThemeManager::Get();
377 const auto& theme = theme_manager.GetCurrentTheme();
378
379 // Center the window
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);
383
384 ImGui::PushStyleColor(ImGuiCol_TitleBg, ConvertColorToImVec4(theme.accent));
385 ImGui::PushStyleColor(ImGuiCol_TitleBgActive, ConvertColorToImVec4(theme.accent));
386
387 if (ImGui::Begin(ICON_MD_KEYBOARD " Keyboard Shortcuts", show,
388 ImGuiWindowFlags_NoCollapse)) {
389 // Emulator controls section
390 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
391 ICON_MD_VIDEOGAME_ASSET " Emulator Controls");
392 ImGui::Separator();
393 ImGui::Spacing();
394
395 if (ImGui::BeginTable("EmulatorControls", 2, ImGuiTableFlags_Borders)) {
396 ImGui::TableSetupColumn("Key", ImGuiTableColumnFlags_WidthFixed, 120);
397 ImGui::TableSetupColumn("Action", ImGuiTableColumnFlags_WidthStretch);
398 ImGui::TableHeadersRow();
399
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);
406 };
407
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");
413
414 ImGui::EndTable();
415 }
416
417 ImGui::Spacing();
418 ImGui::Spacing();
419
420 // Game controls section
421 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
422 ICON_MD_SPORTS_ESPORTS " SNES Controller");
423 ImGui::Separator();
424 ImGui::Spacing();
425
426 if (ImGui::BeginTable("GameControls", 2, ImGuiTableFlags_Borders)) {
427 ImGui::TableSetupColumn("Key", ImGuiTableColumnFlags_WidthFixed, 120);
428 ImGui::TableSetupColumn("Button", ImGuiTableColumnFlags_WidthStretch);
429 ImGui::TableHeadersRow();
430
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);
437 };
438
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");
448
449 ImGui::EndTable();
450 }
451
452 ImGui::Spacing();
453 ImGui::Spacing();
454
455 // Tips section
456 ImGui::TextColored(ConvertColorToImVec4(theme.info),
457 ICON_MD_INFO " Tips");
458 ImGui::Separator();
459 ImGui::Spacing();
460
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");
465
466 ImGui::Spacing();
467 ImGui::Spacing();
468
469 if (ImGui::Button("Close", ImVec2(-1, 30))) {
470 *show = false;
471 }
472 }
473 ImGui::End();
474
475 ImGui::PopStyleColor(2);
476}
477
479 if (!emu) return;
480
481 auto& theme_manager = ThemeManager::Get();
482 const auto& theme = theme_manager.GetCurrentTheme();
483
484 // Main layout
485 ImGui::PushStyleColor(ImGuiCol_ChildBg, ConvertColorToImVec4(theme.window_bg));
486
487 RenderNavBar(emu);
488
489 ImGui::Separator();
490
491 // Main content area
492 RenderSnesPpu(emu);
493
494 // Keyboard shortcuts overlay (F1 to toggle)
495 static bool show_shortcuts = false;
496 if (ImGui::IsKeyPressed(ImGuiKey_F1)) {
497 show_shortcuts = !show_shortcuts;
498 }
499 RenderKeyboardShortcuts(&show_shortcuts);
500
501 ImGui::PopStyleColor();
502}
503
504} // namespace ui
505} // namespace emu
506} // namespace yaze
507
A class for emulating and debugging SNES games.
Definition emulator.h:36
gfx::IRenderer * renderer()
Definition emulator.h:63
void Initialize(gfx::IRenderer *renderer, const std::vector< uint8_t > &rom_data)
Definition emulator.cc:61
bool is_debugging() const
Definition emulator.h:74
void set_turbo_mode(bool turbo)
Definition emulator.h:68
void * ppu_texture()
Definition emulator.h:64
void set_use_sdl_audio_stream(bool enabled)
Definition emulator.cc:54
double GetCurrentFPS() const
Definition emulator.h:81
void set_debugging(bool debugging)
Definition emulator.h:75
bool use_sdl_audio_stream() const
Definition emulator.h:58
bool is_turbo_mode() const
Definition emulator.h:67
void set_running(bool running)
Definition emulator.h:49
bool is_snes_initialized() const
Definition emulator.h:77
audio::IAudioBackend * audio_backend()
Definition emulator.h:52
auto snes() -> Snes &
Definition emulator.h:47
auto running() const -> bool
Definition emulator.h:48
EmulatorMetrics GetMetrics()
Definition emulator.h:99
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
Definition icons.h:811
#define ICON_MD_PAUSE
Definition icons.h:1387
#define ICON_MD_SETTINGS
Definition icons.h:1697
#define ICON_MD_INFO
Definition icons.h:991
#define ICON_MD_MEMORY
Definition icons.h:1193
#define ICON_MD_VOLUME_UP
Definition icons.h:2109
#define ICON_MD_FAST_FORWARD
Definition icons.h:722
#define ICON_MD_PLAY_ARROW
Definition icons.h:1477
#define ICON_MD_VIDEOGAME_ASSET
Definition icons.h:2074
#define ICON_MD_BUG_REPORT
Definition icons.h:325
#define ICON_MD_SHOW_CHART
Definition icons.h:1734
#define ICON_MD_SPEED
Definition icons.h:1815
#define ICON_MD_AUDIOTRACK
Definition icons.h:211
#define ICON_MD_KEYBOARD
Definition icons.h:1026
#define ICON_MD_SKIP_NEXT
Definition icons.h:1771
#define ICON_MD_SPORTS_ESPORTS
Definition icons.h:1824
#define ICON_MD_VOLUME_OFF
Definition icons.h:2108
#define ICON_MD_RESTART_ALT
Definition icons.h:1600
#define LOG_ERROR(category, format,...)
Definition log.h:110
#define LOG_WARN(category, format,...)
Definition log.h:108
#define LOG_INFO(category, format,...)
Definition log.h:106
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)
Definition color.h:21
std::string GetFileName(const std::string &filename)
Gets the filename from a full path.
Definition file_util.cc:19
std::string GetFileExtension(const std::string &filename)
Gets the file extension from a filename.
Definition file_util.cc:15
Main namespace for the application.
Definition controller.cc:20