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 // A null event pointer waits without removing the event from the queue.
171 return SDL_WaitEventTimeout(nullptr, timeout_ms) == 1;
172}
173
175 if (native_event && imgui_initialized_) {
176 ImGui_ImplSDL2_ProcessEvent(static_cast<SDL_Event*>(native_event));
177 }
178}
179
181 WindowEvent event;
183
184 switch (sdl_event.type) {
185 case SDL_QUIT:
186 event.type = WindowEventType::Quit;
187 break;
188
189 case SDL_KEYDOWN:
190 event.type = WindowEventType::KeyDown;
191 event.key_code = sdl_event.key.keysym.sym;
192 event.scan_code = sdl_event.key.keysym.scancode;
194 event.key_shift = key_shift_;
195 event.key_ctrl = key_ctrl_;
196 event.key_alt = key_alt_;
197 event.key_super = key_super_;
198 break;
199
200 case SDL_KEYUP:
201 event.type = WindowEventType::KeyUp;
202 event.key_code = sdl_event.key.keysym.sym;
203 event.scan_code = sdl_event.key.keysym.scancode;
205 event.key_shift = key_shift_;
206 event.key_ctrl = key_ctrl_;
207 event.key_alt = key_alt_;
208 event.key_super = key_super_;
209 break;
210
211 case SDL_MOUSEMOTION:
212 event.type = WindowEventType::MouseMotion;
213 event.mouse_x = static_cast<float>(sdl_event.motion.x);
214 event.mouse_y = static_cast<float>(sdl_event.motion.y);
215 break;
216
217 case SDL_MOUSEBUTTONDOWN:
219 event.mouse_x = static_cast<float>(sdl_event.button.x);
220 event.mouse_y = static_cast<float>(sdl_event.button.y);
221 event.mouse_button = sdl_event.button.button;
222 break;
223
224 case SDL_MOUSEBUTTONUP:
226 event.mouse_x = static_cast<float>(sdl_event.button.x);
227 event.mouse_y = static_cast<float>(sdl_event.button.y);
228 event.mouse_button = sdl_event.button.button;
229 break;
230
231 case SDL_MOUSEWHEEL:
232 event.type = WindowEventType::MouseWheel;
233 event.wheel_x = static_cast<float>(sdl_event.wheel.x);
234 event.wheel_y = static_cast<float>(sdl_event.wheel.y);
235 break;
236
237 case SDL_DROPFILE:
238 event.type = WindowEventType::DropFile;
239 if (sdl_event.drop.file) {
240 event.dropped_file = sdl_event.drop.file;
241 SDL_free(sdl_event.drop.file);
242 }
243 break;
244
245 case SDL_WINDOWEVENT:
246 switch (sdl_event.window.event) {
247 case SDL_WINDOWEVENT_CLOSE:
248 event.type = WindowEventType::Close;
249 break;
250
251 case SDL_WINDOWEVENT_SIZE_CHANGED:
252 case SDL_WINDOWEVENT_RESIZED:
253 event.type = WindowEventType::Resize;
254 event.window_width = sdl_event.window.data1;
255 event.window_height = sdl_event.window.data2;
256 is_resizing_ = true;
258 break;
259
260 case SDL_WINDOWEVENT_MINIMIZED:
261 event.type = WindowEventType::Minimized;
262 is_resizing_ = false;
263 g_window_is_resizing = false;
264 break;
265
266 case SDL_WINDOWEVENT_MAXIMIZED:
267 event.type = WindowEventType::Maximized;
268 break;
269
270 case SDL_WINDOWEVENT_RESTORED:
271 event.type = WindowEventType::Restored;
272 is_resizing_ = false;
273 g_window_is_resizing = false;
274 break;
275
276 case SDL_WINDOWEVENT_SHOWN:
277 event.type = WindowEventType::Shown;
278 is_resizing_ = false;
279 g_window_is_resizing = false;
280 break;
281
282 case SDL_WINDOWEVENT_HIDDEN:
283 event.type = WindowEventType::Hidden;
284 is_resizing_ = false;
285 g_window_is_resizing = false;
286 break;
287
288 case SDL_WINDOWEVENT_EXPOSED:
289 event.type = WindowEventType::Exposed;
290 is_resizing_ = false;
291 g_window_is_resizing = false;
292 break;
293
294 case SDL_WINDOWEVENT_FOCUS_GAINED:
295 event.type = WindowEventType::FocusGained;
296 break;
297
298 case SDL_WINDOWEVENT_FOCUS_LOST:
299 event.type = WindowEventType::FocusLost;
300 break;
301 }
302 break;
303 }
304
305 return event;
306}
307
309 SDL_Keymod mod = SDL_GetModState();
310 key_shift_ = (mod & KMOD_SHIFT) != 0;
311 key_ctrl_ = (mod & KMOD_CTRL) != 0;
312 key_alt_ = (mod & KMOD_ALT) != 0;
313 key_super_ = (mod & KMOD_GUI) != 0;
314}
315
317 WindowStatus status;
318 status.is_active = active_;
319 status.is_resizing = is_resizing_;
320
321 if (window_) {
322 Uint32 flags = SDL_GetWindowFlags(window_.get());
323 status.is_minimized = (flags & SDL_WINDOW_MINIMIZED) != 0;
324 status.is_maximized = (flags & SDL_WINDOW_MAXIMIZED) != 0;
325 status.is_fullscreen =
326 (flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_FULLSCREEN_DESKTOP)) != 0;
327 status.is_focused = (flags & SDL_WINDOW_INPUT_FOCUS) != 0;
328
329 SDL_GetWindowSize(window_.get(), &status.width, &status.height);
330 }
331
332 return status;
333}
334
335void SDL2WindowBackend::GetSize(int* width, int* height) const {
336 if (window_) {
337 SDL_GetWindowSize(window_.get(), width, height);
338 } else {
339 if (width)
340 *width = 0;
341 if (height)
342 *height = 0;
343 }
344}
345
346void SDL2WindowBackend::SetSize(int width, int height) {
347 if (window_) {
348 SDL_SetWindowSize(window_.get(), width, height);
349 }
350}
351
352std::string SDL2WindowBackend::GetTitle() const {
353 if (window_) {
354 const char* title = SDL_GetWindowTitle(window_.get());
355 return title ? title : "";
356 }
357 return "";
358}
359
360void SDL2WindowBackend::SetTitle(const std::string& title) {
361 if (window_) {
362 SDL_SetWindowTitle(window_.get(), title.c_str());
363 }
364}
365
367 if (window_) {
368 SDL_ShowWindow(window_.get());
369 }
370}
371
373 if (window_) {
374 SDL_HideWindow(window_.get());
375 }
376}
377
379 if (!window_ || !renderer) {
380 return false;
381 }
382
383 if (renderer->GetBackendRenderer()) {
384 // Already initialized
385 return true;
386 }
387
388 return renderer->Initialize(window_.get());
389}
390
392 if (imgui_initialized_) {
393 return absl::OkStatus();
394 }
395
396 if (!renderer) {
397 return absl::InvalidArgumentError("Renderer is null");
398 }
399
400 IMGUI_CHECKVERSION();
401 ImGui::CreateContext();
402
403 ImGuiIO& io = ImGui::GetIO();
404 io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
405 io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
406 // Note: ViewportsEnable is intentionally NOT set for SDL2 + SDL_Renderer
407 // It causes scaling issues on macOS Retina displays
408
409 // Ensure macOS-style behavior (Cmd acts as Ctrl for shortcuts)
410 // ImGui should set this automatically based on __APPLE__, but force it to be safe
411#ifdef __APPLE__
412 io.ConfigMacOSXBehaviors = true;
413 LOG_INFO("SDL2WindowBackend", "Enabled ConfigMacOSXBehaviors for macOS");
414#endif
415
416 if (auto ini_path = util::PlatformPaths::GetImGuiIniPath(); ini_path.ok()) {
417 static std::string ini_path_str;
418 if (ini_path_str.empty()) {
419 ini_path_str = ini_path->string();
420 }
421 io.IniFilename = ini_path_str.c_str();
422 } else {
423 io.IniFilename = nullptr;
424 LOG_WARN("SDL2WindowBackend", "Failed to resolve ImGui ini path: %s",
425 ini_path.status().ToString().c_str());
426 }
427
428 // Initialize ImGui backends
429 SDL_Renderer* sdl_renderer =
430 static_cast<SDL_Renderer*>(renderer->GetBackendRenderer());
431
432 if (!sdl_renderer) {
433 return absl::InternalError("Failed to get SDL renderer from IRenderer");
434 }
435
436 if (!ImGui_ImplSDL2_InitForSDLRenderer(window_.get(), sdl_renderer)) {
437 return absl::InternalError("ImGui_ImplSDL2_InitForSDLRenderer failed");
438 }
439
440 if (!ImGui_ImplSDLRenderer2_Init(sdl_renderer)) {
441 ImGui_ImplSDL2_Shutdown();
442 return absl::InternalError("ImGui_ImplSDLRenderer2_Init failed");
443 }
444
445 // Load fonts
447
448 // Apply default style
450
451 imgui_initialized_ = true;
452 LOG_INFO("SDL2WindowBackend", "ImGui initialized successfully");
453 return absl::OkStatus();
454}
455
457 if (!imgui_initialized_) {
458 return;
459 }
460
461 LOG_INFO("SDL2WindowBackend", "Shutting down ImGui implementations...");
463 ImGui_ImplSDL2_Shutdown();
464
465 LOG_INFO("SDL2WindowBackend", "Destroying ImGui context...");
466 ImGui::DestroyContext();
467
468 imgui_initialized_ = false;
469}
470
472 if (!imgui_initialized_) {
473 return;
474 }
475
477 ImGui_ImplSDL2_NewFrame();
478 // ImGui_ImplSDL2_NewFrame() automatically handles DisplaySize and
479 // DisplayFramebufferScale via ImGui_ImplSDL2_GetWindowSizeAndFramebufferScale()
480 // which uses SDL_GetRendererOutputSize() when renderer is available.
481}
482
484 if (!imgui_initialized_) {
485 return;
486 }
487
488 // Finalize ImGui frame and render draw data
489 ImGui::Render();
490
491 if (renderer) {
492 SDL_Renderer* sdl_renderer =
493 static_cast<SDL_Renderer*>(renderer->GetBackendRenderer());
494 if (sdl_renderer) {
495 ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(), sdl_renderer);
496 }
497 }
498
499 // Multi-viewport support
500 ImGuiIO& io = ImGui::GetIO();
501 if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
502 ImGui::UpdatePlatformWindows();
503 ImGui::RenderPlatformWindowsDefault();
504 }
505}
506
507} // namespace platform
508} // namespace yaze
void Shutdown()
Definition arena.cc:489
static Arena & Get()
Definition arena.cc:24
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.
bool WaitForEvent(int timeout_ms) override
Block until an event is pending or the timeout passes.
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.
void ImGui_ImplSDLRenderer2_NewFrame()
void ImGui_ImplSDLRenderer2_RenderDrawData(ImDrawData *draw_data, SDL_Renderer *renderer)
bool ImGui_ImplSDLRenderer2_Init(SDL_Renderer *renderer)
void ImGui_ImplSDLRenderer2_Shutdown()
#define LOG_WARN(category, format,...)
Definition log.h:138
#define LOG_INFO(category, format,...)
Definition log.h:136
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