yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
sdl2_window_backend.cc
Go to the documentation of this file.
1// sdl2_window_backend.cc - SDL2 Window Backend Implementation
2
4
6
7#include <filesystem>
8
9#include "absl/status/status.h"
10#include "absl/strings/str_format.h"
12#include "app/gui/core/style.h"
14#include "imgui/backends/imgui_impl_sdl2.h"
15#include "imgui/backends/imgui_impl_sdlrenderer2.h"
16#include "imgui/imgui.h"
17#include "util/log.h"
18#include "util/platform_paths.h"
19
20namespace yaze {
21
22// Forward reference to the global resize flag defined in window.cc
23namespace core {
25}
26
27namespace platform {
28
29// Alias to core's resize flag for compatibility
30#define g_window_is_resizing yaze::core::g_window_is_resizing
31
37
38absl::Status SDL2WindowBackend::Initialize(const WindowConfig& config) {
39 if (initialized_) {
40 LOG_WARN("SDL2WindowBackend", "Already initialized, shutting down first");
42 }
43
44 // Initialize SDL2 subsystems
45 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER |
46 SDL_INIT_GAMECONTROLLER) != 0) {
47 return absl::InternalError(
48 absl::StrFormat("SDL_Init failed: %s", SDL_GetError()));
49 }
50
51 // Determine window size
52 int screen_width = config.width;
53 int screen_height = config.height;
54
55 if (screen_width == 0 || screen_height == 0) {
56 // Auto-detect from display
57 SDL_DisplayMode display_mode;
58 if (SDL_GetCurrentDisplayMode(0, &display_mode) == 0) {
59 screen_width = static_cast<int>(display_mode.w * config.display_scale);
60 screen_height = static_cast<int>(display_mode.h * config.display_scale);
61 } else {
62 // Fallback to reasonable defaults
63 screen_width = 1280;
64 screen_height = 720;
65 LOG_WARN("SDL2WindowBackend",
66 "Failed to get display mode, using defaults: %dx%d",
67 screen_width, screen_height);
68 }
69 }
70
71 // Build window flags
72 Uint32 flags = 0;
73 if (config.resizable) {
74 flags |= SDL_WINDOW_RESIZABLE;
75 }
76 if (config.maximized) {
77 flags |= SDL_WINDOW_MAXIMIZED;
78 }
79 if (config.fullscreen) {
80 flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
81 }
82 if (config.high_dpi) {
83 flags |= SDL_WINDOW_ALLOW_HIGHDPI;
84 }
85 if (config.hidden) {
86 flags |= SDL_WINDOW_HIDDEN;
87 }
88
89 // Create window
90 window_ = std::unique_ptr<SDL_Window, util::SDL_Deleter>(
91 SDL_CreateWindow(config.title.c_str(), SDL_WINDOWPOS_UNDEFINED,
92 SDL_WINDOWPOS_UNDEFINED, screen_width, screen_height,
93 flags),
95
96 if (!window_) {
97 SDL_Quit();
98 return absl::InternalError(
99 absl::StrFormat("SDL_CreateWindow failed: %s", SDL_GetError()));
100 }
101
102 // Allocate legacy audio buffer for backwards compatibility
103 const int audio_frequency = 48000;
104 const size_t buffer_size = (audio_frequency / 50) * 2; // Stereo PAL
105 audio_buffer_ = std::shared_ptr<int16_t>(new int16_t[buffer_size],
106 std::default_delete<int16_t[]>());
107
108 LOG_INFO("SDL2WindowBackend", "Initialized: %dx%d, audio buffer: %zu samples",
109 screen_width, screen_height, buffer_size);
110
111 initialized_ = true;
112 active_ = true;
113 return absl::OkStatus();
114}
115
117 if (!initialized_) {
118 return absl::OkStatus();
119 }
120
121 // Pause and close audio device if open
122 if (audio_device_ != 0) {
123 SDL_PauseAudioDevice(audio_device_, 1);
124 SDL_CloseAudioDevice(audio_device_);
125 audio_device_ = 0;
126 }
127
128 // Shutdown ImGui if initialized
129 if (imgui_initialized_) {
131 }
132
133 // Shutdown graphics arena while renderer is still valid
134 LOG_INFO("SDL2WindowBackend", "Shutting down graphics arena...");
136
137 // Destroy window
138 if (window_) {
139 LOG_INFO("SDL2WindowBackend", "Destroying window...");
140 window_.reset();
141 }
142
143 // Quit SDL
144 LOG_INFO("SDL2WindowBackend", "Shutting down SDL...");
145 SDL_Quit();
146
147 initialized_ = false;
148 LOG_INFO("SDL2WindowBackend", "Shutdown complete");
149 return absl::OkStatus();
150}
151
153 SDL_Event sdl_event;
154 if (SDL_PollEvent(&sdl_event)) {
155 // Let ImGui process the event first
156 if (imgui_initialized_) {
157 ImGui_ImplSDL2_ProcessEvent(&sdl_event);
158 }
159
160 // Convert to platform-agnostic event
161 out_event = ConvertSDL2Event(sdl_event);
162 out_event.has_native_event = true;
163 out_event.native_event = sdl_event;
164 return true;
165 }
166 return false;
167}
168
170 if (native_event && imgui_initialized_) {
171 ImGui_ImplSDL2_ProcessEvent(static_cast<SDL_Event*>(native_event));
172 }
173}
174
176 WindowEvent event;
178
179 switch (sdl_event.type) {
180 case SDL_QUIT:
181 event.type = WindowEventType::Quit;
182 break;
183
184 case SDL_KEYDOWN:
185 event.type = WindowEventType::KeyDown;
186 event.key_code = sdl_event.key.keysym.sym;
187 event.scan_code = sdl_event.key.keysym.scancode;
189 event.key_shift = key_shift_;
190 event.key_ctrl = key_ctrl_;
191 event.key_alt = key_alt_;
192 event.key_super = key_super_;
193 break;
194
195 case SDL_KEYUP:
196 event.type = WindowEventType::KeyUp;
197 event.key_code = sdl_event.key.keysym.sym;
198 event.scan_code = sdl_event.key.keysym.scancode;
200 event.key_shift = key_shift_;
201 event.key_ctrl = key_ctrl_;
202 event.key_alt = key_alt_;
203 event.key_super = key_super_;
204 break;
205
206 case SDL_MOUSEMOTION:
207 event.type = WindowEventType::MouseMotion;
208 event.mouse_x = static_cast<float>(sdl_event.motion.x);
209 event.mouse_y = static_cast<float>(sdl_event.motion.y);
210 break;
211
212 case SDL_MOUSEBUTTONDOWN:
214 event.mouse_x = static_cast<float>(sdl_event.button.x);
215 event.mouse_y = static_cast<float>(sdl_event.button.y);
216 event.mouse_button = sdl_event.button.button;
217 break;
218
219 case SDL_MOUSEBUTTONUP:
221 event.mouse_x = static_cast<float>(sdl_event.button.x);
222 event.mouse_y = static_cast<float>(sdl_event.button.y);
223 event.mouse_button = sdl_event.button.button;
224 break;
225
226 case SDL_MOUSEWHEEL:
227 event.type = WindowEventType::MouseWheel;
228 event.wheel_x = static_cast<float>(sdl_event.wheel.x);
229 event.wheel_y = static_cast<float>(sdl_event.wheel.y);
230 break;
231
232 case SDL_DROPFILE:
233 event.type = WindowEventType::DropFile;
234 if (sdl_event.drop.file) {
235 event.dropped_file = sdl_event.drop.file;
236 SDL_free(sdl_event.drop.file);
237 }
238 break;
239
240 case SDL_WINDOWEVENT:
241 switch (sdl_event.window.event) {
242 case SDL_WINDOWEVENT_CLOSE:
243 event.type = WindowEventType::Close;
244 break;
245
246 case SDL_WINDOWEVENT_SIZE_CHANGED:
247 case SDL_WINDOWEVENT_RESIZED:
248 event.type = WindowEventType::Resize;
249 event.window_width = sdl_event.window.data1;
250 event.window_height = sdl_event.window.data2;
251 is_resizing_ = true;
253 break;
254
255 case SDL_WINDOWEVENT_MINIMIZED:
256 event.type = WindowEventType::Minimized;
257 is_resizing_ = false;
258 g_window_is_resizing = false;
259 break;
260
261 case SDL_WINDOWEVENT_MAXIMIZED:
262 event.type = WindowEventType::Maximized;
263 break;
264
265 case SDL_WINDOWEVENT_RESTORED:
266 event.type = WindowEventType::Restored;
267 is_resizing_ = false;
268 g_window_is_resizing = false;
269 break;
270
271 case SDL_WINDOWEVENT_SHOWN:
272 event.type = WindowEventType::Shown;
273 is_resizing_ = false;
274 g_window_is_resizing = false;
275 break;
276
277 case SDL_WINDOWEVENT_HIDDEN:
278 event.type = WindowEventType::Hidden;
279 is_resizing_ = false;
280 g_window_is_resizing = false;
281 break;
282
283 case SDL_WINDOWEVENT_EXPOSED:
284 event.type = WindowEventType::Exposed;
285 is_resizing_ = false;
286 g_window_is_resizing = false;
287 break;
288
289 case SDL_WINDOWEVENT_FOCUS_GAINED:
290 event.type = WindowEventType::FocusGained;
291 break;
292
293 case SDL_WINDOWEVENT_FOCUS_LOST:
294 event.type = WindowEventType::FocusLost;
295 break;
296 }
297 break;
298 }
299
300 return event;
301}
302
304 SDL_Keymod mod = SDL_GetModState();
305 key_shift_ = (mod & KMOD_SHIFT) != 0;
306 key_ctrl_ = (mod & KMOD_CTRL) != 0;
307 key_alt_ = (mod & KMOD_ALT) != 0;
308 key_super_ = (mod & KMOD_GUI) != 0;
309}
310
312 WindowStatus status;
313 status.is_active = active_;
314 status.is_resizing = is_resizing_;
315
316 if (window_) {
317 Uint32 flags = SDL_GetWindowFlags(window_.get());
318 status.is_minimized = (flags & SDL_WINDOW_MINIMIZED) != 0;
319 status.is_maximized = (flags & SDL_WINDOW_MAXIMIZED) != 0;
320 status.is_fullscreen =
321 (flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_FULLSCREEN_DESKTOP)) != 0;
322 status.is_focused = (flags & SDL_WINDOW_INPUT_FOCUS) != 0;
323
324 SDL_GetWindowSize(window_.get(), &status.width, &status.height);
325 }
326
327 return status;
328}
329
330void SDL2WindowBackend::GetSize(int* width, int* height) const {
331 if (window_) {
332 SDL_GetWindowSize(window_.get(), width, height);
333 } else {
334 if (width)
335 *width = 0;
336 if (height)
337 *height = 0;
338 }
339}
340
341void SDL2WindowBackend::SetSize(int width, int height) {
342 if (window_) {
343 SDL_SetWindowSize(window_.get(), width, height);
344 }
345}
346
347std::string SDL2WindowBackend::GetTitle() const {
348 if (window_) {
349 const char* title = SDL_GetWindowTitle(window_.get());
350 return title ? title : "";
351 }
352 return "";
353}
354
355void SDL2WindowBackend::SetTitle(const std::string& title) {
356 if (window_) {
357 SDL_SetWindowTitle(window_.get(), title.c_str());
358 }
359}
360
362 if (window_) {
363 SDL_ShowWindow(window_.get());
364 }
365}
366
368 if (window_) {
369 SDL_HideWindow(window_.get());
370 }
371}
372
374 if (!window_ || !renderer) {
375 return false;
376 }
377
378 if (renderer->GetBackendRenderer()) {
379 // Already initialized
380 return true;
381 }
382
383 return renderer->Initialize(window_.get());
384}
385
387 if (imgui_initialized_) {
388 return absl::OkStatus();
389 }
390
391 if (!renderer) {
392 return absl::InvalidArgumentError("Renderer is null");
393 }
394
395 IMGUI_CHECKVERSION();
396 ImGui::CreateContext();
397
398 ImGuiIO& io = ImGui::GetIO();
399 io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
400 io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
401 // Note: ViewportsEnable is intentionally NOT set for SDL2 + SDL_Renderer
402 // It causes scaling issues on macOS Retina displays
403
404 // Ensure macOS-style behavior (Cmd acts as Ctrl for shortcuts)
405 // ImGui should set this automatically based on __APPLE__, but force it to be safe
406#ifdef __APPLE__
407 io.ConfigMacOSXBehaviors = true;
408 LOG_INFO("SDL2WindowBackend", "Enabled ConfigMacOSXBehaviors for macOS");
409#endif
410
411 if (auto ini_path = util::PlatformPaths::GetImGuiIniPath(); ini_path.ok()) {
412 static std::string ini_path_str;
413 if (ini_path_str.empty()) {
414 ini_path_str = ini_path->string();
415 }
416 io.IniFilename = ini_path_str.c_str();
417 } else {
418 io.IniFilename = nullptr;
419 LOG_WARN("SDL2WindowBackend", "Failed to resolve ImGui ini path: %s",
420 ini_path.status().ToString().c_str());
421 }
422
423 // Initialize ImGui backends
424 SDL_Renderer* sdl_renderer =
425 static_cast<SDL_Renderer*>(renderer->GetBackendRenderer());
426
427 if (!sdl_renderer) {
428 return absl::InternalError("Failed to get SDL renderer from IRenderer");
429 }
430
431 if (!ImGui_ImplSDL2_InitForSDLRenderer(window_.get(), sdl_renderer)) {
432 return absl::InternalError("ImGui_ImplSDL2_InitForSDLRenderer failed");
433 }
434
435 if (!ImGui_ImplSDLRenderer2_Init(sdl_renderer)) {
436 ImGui_ImplSDL2_Shutdown();
437 return absl::InternalError("ImGui_ImplSDLRenderer2_Init failed");
438 }
439
440 // Load fonts
442
443 // Apply default style
445
446 imgui_initialized_ = true;
447 LOG_INFO("SDL2WindowBackend", "ImGui initialized successfully");
448 return absl::OkStatus();
449}
450
452 if (!imgui_initialized_) {
453 return;
454 }
455
456 LOG_INFO("SDL2WindowBackend", "Shutting down ImGui implementations...");
457 ImGui_ImplSDLRenderer2_Shutdown();
458 ImGui_ImplSDL2_Shutdown();
459
460 LOG_INFO("SDL2WindowBackend", "Destroying ImGui context...");
461 ImGui::DestroyContext();
462
463 imgui_initialized_ = false;
464}
465
467 if (!imgui_initialized_) {
468 return;
469 }
470
471 ImGui_ImplSDLRenderer2_NewFrame();
472 ImGui_ImplSDL2_NewFrame();
473 // ImGui_ImplSDL2_NewFrame() automatically handles DisplaySize and
474 // DisplayFramebufferScale via ImGui_ImplSDL2_GetWindowSizeAndFramebufferScale()
475 // which uses SDL_GetRendererOutputSize() when renderer is available.
476}
477
479 if (!imgui_initialized_) {
480 return;
481 }
482
483 // Finalize ImGui frame and render draw data
484 ImGui::Render();
485
486 if (renderer) {
487 SDL_Renderer* sdl_renderer =
488 static_cast<SDL_Renderer*>(renderer->GetBackendRenderer());
489 if (sdl_renderer) {
490 ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(), sdl_renderer);
491 }
492 }
493
494 // Multi-viewport support
495 ImGuiIO& io = ImGui::GetIO();
496 if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
497 ImGui::UpdatePlatformWindows();
498 ImGui::RenderPlatformWindowsDefault();
499 }
500}
501
502} // namespace platform
503} // namespace yaze
void Shutdown()
Definition arena.cc:371
static Arena & Get()
Definition arena.cc:21
Defines an abstract interface for all rendering operations.
Definition irenderer.h:60
virtual bool Initialize(SDL_Window *window)=0
Initializes the renderer with a given window.
virtual void * GetBackendRenderer()=0
Provides an escape hatch to get the underlying, concrete renderer object.
void ShowWindow() override
Show the window.
absl::Status InitializeImGui(gfx::IRenderer *renderer) override
Initialize ImGui backends for this window/renderer combo.
void SetTitle(const std::string &title) override
Set window title.
absl::Status Shutdown() override
Shutdown the window backend and release resources.
bool PollEvent(WindowEvent &out_event) override
Poll and process pending events.
WindowStatus GetStatus() const override
Get current window status.
void GetSize(int *width, int *height) const override
Get window dimensions.
void ProcessNativeEvent(void *native_event) override
Process a native SDL event (for ImGui integration)
bool InitializeRenderer(gfx::IRenderer *renderer) override
Initialize renderer for this window.
void SetSize(int width, int height) override
Set window dimensions.
std::shared_ptr< int16_t > audio_buffer_
absl::Status Initialize(const WindowConfig &config) override
Initialize the window backend with configuration.
void ShutdownImGui() override
Shutdown ImGui backends.
void RenderImGui(gfx::IRenderer *renderer) override
Render ImGui draw data (and viewports if enabled)
WindowEvent ConvertSDL2Event(const SDL_Event &sdl_event)
std::unique_ptr< SDL_Window, util::SDL_Deleter > window_
void NewImGuiFrame() override
Start a new ImGui frame.
void HideWindow() override
Hide the window.
std::string GetTitle() const override
Get window title.
static absl::StatusOr< std::filesystem::path > GetImGuiIniPath()
Get the ImGui ini path for YAZE.
#define LOG_WARN(category, format,...)
Definition log.h:107
#define LOG_INFO(category, format,...)
Definition log.h:105
void ColorsYaze()
Definition style.cc:33
absl::Status LoadPackageFonts()
#define g_window_is_resizing
SDL2/SDL3 compatibility layer.
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
Window configuration parameters.
Definition iwindow.h:24
Platform-agnostic window event data.
Definition iwindow.h:65
WindowEventType type
Definition iwindow.h:66
Window backend status information.
Definition iwindow.h:98
Deleter for SDL_Window and SDL_Renderer.
Definition sdl_deleter.h:19