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"
7#include "app/gui/color.h"
8#include "app/gui/icons.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 " Pause", ImVec2(100, 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 " Play", ImVec2(100, 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 " Step", ImVec2(80, 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 " Reset", ImVec2(80, 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 } else {
223 ImGui::TextColored(ConvertColorToImVec4(theme.error),
224 ICON_MD_VOLUME_OFF " No Backend");
225 }
226
227 ImGui::SameLine();
228 ImGui::Separator();
229 ImGui::SameLine();
230
231 // Input capture status indicator (like modern emulators)
232 ImGuiIO& io = ImGui::GetIO();
233 if (io.WantCaptureKeyboard) {
234 // ImGui is capturing keyboard (typing in UI)
235 ImGui::TextColored(ConvertColorToImVec4(theme.warning),
236 ICON_MD_KEYBOARD " UI");
237 if (ImGui::IsItemHovered()) {
238 ImGui::SetTooltip("Keyboard captured by UI\nGame input disabled");
239 }
240 } else {
241 // Emulator can receive input
242 ImGui::TextColored(ConvertColorToImVec4(theme.success),
243 ICON_MD_SPORTS_ESPORTS " Game");
244 if (ImGui::IsItemHovered()) {
245 ImGui::SetTooltip("Game input active\nPress F1 for controls");
246 }
247 }
248
249 ImGui::PopStyleColor(3);
250}
251
253 if (!emu) return;
254
255 auto& theme_manager = ThemeManager::Get();
256 const auto& theme = theme_manager.GetCurrentTheme();
257
258 ImGui::PushStyleColor(ImGuiCol_ChildBg, ConvertColorToImVec4(theme.editor_background));
259 ImGui::BeginChild("##SNES_PPU", ImVec2(0, 0), true,
260 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
261
262 ImVec2 canvas_size = ImGui::GetContentRegionAvail();
263 ImVec2 snes_size = ImVec2(512, 480);
264
265 if (emu->is_snes_initialized() && emu->ppu_texture()) {
266 // Center the SNES display with aspect ratio preservation
267 float aspect = snes_size.x / snes_size.y;
268 float display_w = canvas_size.x;
269 float display_h = display_w / aspect;
270
271 if (display_h > canvas_size.y) {
272 display_h = canvas_size.y;
273 display_w = display_h * aspect;
274 }
275
276 float pos_x = (canvas_size.x - display_w) * 0.5f;
277 float pos_y = (canvas_size.y - display_h) * 0.5f;
278
279 ImGui::SetCursorPos(ImVec2(pos_x, pos_y));
280
281 // Render PPU texture with click detection for focus
282 ImGui::Image((ImTextureID)(intptr_t)emu->ppu_texture(),
283 ImVec2(display_w, display_h),
284 ImVec2(0, 0), ImVec2(1, 1));
285
286 // Allow clicking on the display to ensure focus
287 // Modern emulators make the game area "sticky" for input
288 if (ImGui::IsItemHovered()) {
289 // ImGui::SetTooltip("Click to ensure game input focus");
290
291 // Visual feedback when hovered (subtle border)
292 ImDrawList* draw_list = ImGui::GetWindowDrawList();
293 ImVec2 screen_pos = ImGui::GetItemRectMin();
294 ImVec2 screen_size = ImGui::GetItemRectMax();
295 draw_list->AddRect(screen_pos, screen_size,
296 ImGui::ColorConvertFloat4ToU32(ConvertColorToImVec4(theme.accent)),
297 0.0f, 0, 2.0f);
298 }
299 } else {
300 // Not initialized - show helpful placeholder
301 ImVec2 text_size = ImGui::CalcTextSize("Load a ROM to start emulation");
302 ImGui::SetCursorPos(ImVec2((canvas_size.x - text_size.x) * 0.5f,
303 (canvas_size.y - text_size.y) * 0.5f - 20));
304 ImGui::TextColored(ConvertColorToImVec4(theme.text_disabled),
306 ImGui::SetCursorPosX((canvas_size.x - text_size.x) * 0.5f);
307 ImGui::TextColored(ConvertColorToImVec4(theme.text_primary),
308 "Load a ROM to start emulation");
309 ImGui::SetCursorPosX((canvas_size.x - ImGui::CalcTextSize("512x480 SNES output").x) * 0.5f);
310 ImGui::TextColored(ConvertColorToImVec4(theme.text_disabled),
311 "512x480 SNES output");
312 }
313
314 ImGui::EndChild();
315 ImGui::PopStyleColor();
316}
317
319 if (!emu) return;
320
321 auto& theme_manager = ThemeManager::Get();
322 const auto& theme = theme_manager.GetCurrentTheme();
323
324 ImGui::PushStyleColor(ImGuiCol_ChildBg, ConvertColorToImVec4(theme.child_bg));
325 ImGui::BeginChild("##Performance", ImVec2(0, 0), true);
326
327 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
328 ICON_MD_SPEED " Performance Monitor");
329 AddSectionSpacing();
330
331 auto metrics = emu->GetMetrics();
332
333 // FPS Graph
334 if (ImGui::CollapsingHeader(ICON_MD_SHOW_CHART " Frame Rate", ImGuiTreeNodeFlags_DefaultOpen)) {
335 ImGui::Text("Current: %.2f FPS", metrics.fps);
336 ImGui::Text("Target: %.2f FPS", emu->snes().memory().pal_timing() ? 50.0 : 60.0);
337
338 // TODO: Add FPS graph with ImPlot
339 }
340
341 // CPU Stats
342 if (ImGui::CollapsingHeader(ICON_MD_MEMORY " CPU Status", ImGuiTreeNodeFlags_DefaultOpen)) {
343 ImGui::Text("PC: $%02X:%04X", metrics.cpu_pb, metrics.cpu_pc);
344 ImGui::Text("Cycles: %llu", metrics.cycles);
345 }
346
347 // Audio Stats
348 if (ImGui::CollapsingHeader(ICON_MD_AUDIOTRACK " Audio Status", ImGuiTreeNodeFlags_DefaultOpen)) {
349 if (emu->audio_backend()) {
350 auto audio_status = emu->audio_backend()->GetStatus();
351 ImGui::Text("Backend: %s", emu->audio_backend()->GetBackendName().c_str());
352 ImGui::Text("Queued: %u frames", audio_status.queued_frames);
353 ImGui::Text("Playing: %s", audio_status.is_playing ? "YES" : "NO");
354 } else {
355 ImGui::TextColored(ConvertColorToImVec4(theme.error), "No audio backend");
356 }
357 }
358
359 ImGui::EndChild();
360 ImGui::PopStyleColor();
361}
362
363void RenderKeyboardShortcuts(bool* show) {
364 if (!show || !*show) return;
365
366 auto& theme_manager = ThemeManager::Get();
367 const auto& theme = theme_manager.GetCurrentTheme();
368
369 // Center the window
370 ImVec2 center = ImGui::GetMainViewport()->GetCenter();
371 ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
372 ImGui::SetNextWindowSize(ImVec2(550, 600), ImGuiCond_Appearing);
373
374 ImGui::PushStyleColor(ImGuiCol_TitleBg, ConvertColorToImVec4(theme.accent));
375 ImGui::PushStyleColor(ImGuiCol_TitleBgActive, ConvertColorToImVec4(theme.accent));
376
377 if (ImGui::Begin(ICON_MD_KEYBOARD " Keyboard Shortcuts", show,
378 ImGuiWindowFlags_NoCollapse)) {
379 // Emulator controls section
380 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
381 ICON_MD_VIDEOGAME_ASSET " Emulator Controls");
382 ImGui::Separator();
383 ImGui::Spacing();
384
385 if (ImGui::BeginTable("EmulatorControls", 2, ImGuiTableFlags_Borders)) {
386 ImGui::TableSetupColumn("Key", ImGuiTableColumnFlags_WidthFixed, 120);
387 ImGui::TableSetupColumn("Action", ImGuiTableColumnFlags_WidthStretch);
388 ImGui::TableHeadersRow();
389
390 auto AddRow = [](const char* key, const char* action) {
391 ImGui::TableNextRow();
392 ImGui::TableNextColumn();
393 ImGui::Text("%s", key);
394 ImGui::TableNextColumn();
395 ImGui::Text("%s", action);
396 };
397
398 AddRow("Space", "Play/Pause emulation");
399 AddRow("F10", "Step one frame");
400 AddRow("Ctrl+R", "Reset SNES");
401 AddRow("Tab (hold)", "Turbo mode (fast forward)");
402 AddRow("F1", "Show/hide this help");
403
404 ImGui::EndTable();
405 }
406
407 ImGui::Spacing();
408 ImGui::Spacing();
409
410 // Game controls section
411 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
412 ICON_MD_SPORTS_ESPORTS " SNES Controller");
413 ImGui::Separator();
414 ImGui::Spacing();
415
416 if (ImGui::BeginTable("GameControls", 2, ImGuiTableFlags_Borders)) {
417 ImGui::TableSetupColumn("Key", ImGuiTableColumnFlags_WidthFixed, 120);
418 ImGui::TableSetupColumn("Button", ImGuiTableColumnFlags_WidthStretch);
419 ImGui::TableHeadersRow();
420
421 auto AddRow = [](const char* key, const char* button) {
422 ImGui::TableNextRow();
423 ImGui::TableNextColumn();
424 ImGui::Text("%s", key);
425 ImGui::TableNextColumn();
426 ImGui::Text("%s", button);
427 };
428
429 AddRow("Arrow Keys", "D-Pad (Up/Down/Left/Right)");
430 AddRow("X", "A Button");
431 AddRow("Z", "B Button");
432 AddRow("S", "X Button");
433 AddRow("A", "Y Button");
434 AddRow("D", "L Shoulder");
435 AddRow("C", "R Shoulder");
436 AddRow("Enter", "Start");
437 AddRow("RShift", "Select");
438
439 ImGui::EndTable();
440 }
441
442 ImGui::Spacing();
443 ImGui::Spacing();
444
445 // Tips section
446 ImGui::TextColored(ConvertColorToImVec4(theme.info),
447 ICON_MD_INFO " Tips");
448 ImGui::Separator();
449 ImGui::Spacing();
450
451 ImGui::BulletText("Input is disabled when typing in UI fields");
452 ImGui::BulletText("Check the status bar for input capture state");
453 ImGui::BulletText("Click the game screen to ensure focus");
454 ImGui::BulletText("The emulator continues running in background");
455
456 ImGui::Spacing();
457 ImGui::Spacing();
458
459 if (ImGui::Button("Close", ImVec2(-1, 30))) {
460 *show = false;
461 }
462 }
463 ImGui::End();
464
465 ImGui::PopStyleColor(2);
466}
467
469 if (!emu) return;
470
471 auto& theme_manager = ThemeManager::Get();
472 const auto& theme = theme_manager.GetCurrentTheme();
473
474 // Main layout
475 ImGui::PushStyleColor(ImGuiCol_ChildBg, ConvertColorToImVec4(theme.window_bg));
476
477 RenderNavBar(emu);
478
479 ImGui::Separator();
480
481 // Main content area
482 RenderSnesPpu(emu);
483
484 // Keyboard shortcuts overlay (F1 to toggle)
485 static bool show_shortcuts = false;
486 if (ImGui::IsKeyPressed(ImGuiKey_F1)) {
487 show_shortcuts = !show_shortcuts;
488 }
489 RenderKeyboardShortcuts(&show_shortcuts);
490
491 ImGui::PopStyleColor();
492}
493
494} // namespace ui
495} // namespace emu
496} // namespace yaze
497
A class for emulating and debugging SNES games.
Definition emulator.h:33
gfx::IRenderer * renderer()
Definition emulator.h:61
void Initialize(gfx::IRenderer *renderer, const std::vector< uint8_t > &rom_data)
Definition emulator.cc:47
bool is_debugging() const
Definition emulator.h:72
void set_turbo_mode(bool turbo)
Definition emulator.h:66
void * ppu_texture()
Definition emulator.h:62
double GetCurrentFPS() const
Definition emulator.h:79
void set_debugging(bool debugging)
Definition emulator.h:73
bool is_turbo_mode() const
Definition emulator.h:65
void set_running(bool running)
Definition emulator.h:49
bool is_snes_initialized() const
Definition emulator.h:75
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:97
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_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.