yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
sdl3_window_backend.cc
Go to the documentation of this file.
1// sdl3_window_backend.cc - SDL3 Window Backend Implementation
2
3// Only compile SDL3 backend when YAZE_USE_SDL3 is defined
4#ifdef YAZE_USE_SDL3
5
7
8#include <SDL3/SDL.h>
9
10#include "absl/status/status.h"
11#include "absl/strings/str_format.h"
13#include "app/gui/core/style.h"
15#include "imgui/backends/imgui_impl_sdl3.h"
16#include "imgui/backends/imgui_impl_sdlrenderer3.h"
17#include "imgui/imgui.h"
18#include "util/log.h"
19
20namespace yaze {
21
22// Forward reference to the global resize flag defined in window.cc
23namespace core {
24extern bool g_window_is_resizing;
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
32SDL3WindowBackend::~SDL3WindowBackend() {
33 if (initialized_) {
34 Shutdown();
35 }
36}
37
38absl::Status SDL3WindowBackend::Initialize(const WindowConfig& config) {
39 if (initialized_) {
40 LOG_WARN("SDL3WindowBackend", "Already initialized, shutting down first");
41 RETURN_IF_ERROR(Shutdown());
42 }
43
44 // Initialize SDL3 subsystems
45 // Note: SDL3 removed SDL_INIT_TIMER (timer is always available)
46 if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_EVENTS)) {
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 // SDL3 uses SDL_GetPrimaryDisplay() and SDL_GetCurrentDisplayMode()
58 SDL_DisplayID display_id = SDL_GetPrimaryDisplay();
59 const SDL_DisplayMode* mode = SDL_GetCurrentDisplayMode(display_id);
60
61 if (mode) {
62 screen_width = static_cast<int>(mode->w * config.display_scale);
63 screen_height = static_cast<int>(mode->h * config.display_scale);
64 } else {
65 // Fallback to reasonable defaults
66 screen_width = 1280;
67 screen_height = 720;
68 LOG_WARN("SDL3WindowBackend",
69 "Failed to get display mode, using defaults: %dx%d",
70 screen_width, screen_height);
71 }
72 }
73
74 // Build window flags
75 // Note: SDL3 changed some flag names
76 SDL_WindowFlags flags = 0;
77 if (config.resizable) {
78 flags |= SDL_WINDOW_RESIZABLE;
79 }
80 if (config.maximized) {
81 flags |= SDL_WINDOW_MAXIMIZED;
82 }
83 if (config.fullscreen) {
84 flags |= SDL_WINDOW_FULLSCREEN;
85 }
86 if (config.high_dpi) {
87 flags |= SDL_WINDOW_HIGH_PIXEL_DENSITY;
88 }
89
90 // Create window
91 // Note: SDL3 uses SDL_CreateWindow with different signature
92 SDL_Window* raw_window =
93 SDL_CreateWindow(config.title.c_str(), screen_width, screen_height, flags);
94
95 if (!raw_window) {
96 SDL_Quit();
97 return absl::InternalError(
98 absl::StrFormat("SDL_CreateWindow failed: %s", SDL_GetError()));
99 }
100
101 window_ = std::unique_ptr<SDL_Window, SDL3WindowDeleter>(raw_window);
102
103 // Allocate legacy audio buffer for backwards compatibility
104 const int audio_frequency = 48000;
105 const size_t buffer_size = (audio_frequency / 50) * 2; // Stereo PAL
106 audio_buffer_ = std::shared_ptr<int16_t>(new int16_t[buffer_size],
107 std::default_delete<int16_t[]>());
108
109 LOG_INFO("SDL3WindowBackend",
110 "Initialized: %dx%d, audio buffer: %zu samples", screen_width,
111 screen_height, buffer_size);
112
113 initialized_ = true;
114 active_ = true;
115 return absl::OkStatus();
116}
117
118absl::Status SDL3WindowBackend::Shutdown() {
119 if (!initialized_) {
120 return absl::OkStatus();
121 }
122
123 // Shutdown ImGui if initialized
124 if (imgui_initialized_) {
125 ShutdownImGui();
126 }
127
128 // Shutdown graphics arena while renderer is still valid
129 LOG_INFO("SDL3WindowBackend", "Shutting down graphics arena...");
131
132 // Destroy window
133 if (window_) {
134 LOG_INFO("SDL3WindowBackend", "Destroying window...");
135 window_.reset();
136 }
137
138 // Quit SDL
139 LOG_INFO("SDL3WindowBackend", "Shutting down SDL...");
140 SDL_Quit();
141
142 initialized_ = false;
143 LOG_INFO("SDL3WindowBackend", "Shutdown complete");
144 return absl::OkStatus();
145}
146
147bool SDL3WindowBackend::PollEvent(WindowEvent& out_event) {
148 SDL_Event sdl_event;
149 if (SDL_PollEvent(&sdl_event)) {
150 // Let ImGui process the event first
151 if (imgui_initialized_) {
152 ImGui_ImplSDL3_ProcessEvent(&sdl_event);
153 }
154
155 // Convert to platform-agnostic event
156 out_event = ConvertSDL3Event(sdl_event);
157 out_event.has_native_event = true;
158 out_event.native_event = sdl_event;
159 return true;
160 }
161 return false;
162}
163
164void SDL3WindowBackend::ProcessNativeEvent(void* native_event) {
165 if (native_event && imgui_initialized_) {
166 ImGui_ImplSDL3_ProcessEvent(static_cast<SDL_Event*>(native_event));
167 }
168}
169
170WindowEvent SDL3WindowBackend::ConvertSDL3Event(const SDL_Event& sdl_event) {
171 WindowEvent event;
172 event.type = WindowEventType::None;
173
174 switch (sdl_event.type) {
175 // =========================================================================
176 // Application Events
177 // =========================================================================
178 case SDL_EVENT_QUIT:
179 event.type = WindowEventType::Quit;
180 active_ = false;
181 break;
182
183 // =========================================================================
184 // Keyboard Events
185 // Note: SDL3 uses event.key.key instead of event.key.keysym.sym
186 // =========================================================================
187 case SDL_EVENT_KEY_DOWN:
188 event.type = WindowEventType::KeyDown;
189 event.key_code = sdl_event.key.key;
190 event.scan_code = sdl_event.key.scancode;
191 UpdateModifierState();
192 event.key_shift = key_shift_;
193 event.key_ctrl = key_ctrl_;
194 event.key_alt = key_alt_;
195 event.key_super = key_super_;
196 break;
197
198 case SDL_EVENT_KEY_UP:
199 event.type = WindowEventType::KeyUp;
200 event.key_code = sdl_event.key.key;
201 event.scan_code = sdl_event.key.scancode;
202 UpdateModifierState();
203 event.key_shift = key_shift_;
204 event.key_ctrl = key_ctrl_;
205 event.key_alt = key_alt_;
206 event.key_super = key_super_;
207 break;
208
209 // =========================================================================
210 // Mouse Events
211 // Note: SDL3 uses float coordinates
212 // =========================================================================
213 case SDL_EVENT_MOUSE_MOTION:
214 event.type = WindowEventType::MouseMotion;
215 event.mouse_x = sdl_event.motion.x;
216 event.mouse_y = sdl_event.motion.y;
217 break;
218
219 case SDL_EVENT_MOUSE_BUTTON_DOWN:
221 event.mouse_x = sdl_event.button.x;
222 event.mouse_y = sdl_event.button.y;
223 event.mouse_button = sdl_event.button.button;
224 break;
225
226 case SDL_EVENT_MOUSE_BUTTON_UP:
228 event.mouse_x = sdl_event.button.x;
229 event.mouse_y = sdl_event.button.y;
230 event.mouse_button = sdl_event.button.button;
231 break;
232
233 case SDL_EVENT_MOUSE_WHEEL:
234 event.type = WindowEventType::MouseWheel;
235 event.wheel_x = sdl_event.wheel.x;
236 event.wheel_y = sdl_event.wheel.y;
237 break;
238
239 // =========================================================================
240 // Drop Events
241 // =========================================================================
242 case SDL_EVENT_DROP_FILE:
243 event.type = WindowEventType::DropFile;
244 if (sdl_event.drop.data) {
245 event.dropped_file = sdl_event.drop.data;
246 // Note: SDL3 drop.data is managed by SDL, don't free it
247 }
248 break;
249
250 // =========================================================================
251 // Window Events - SDL3 Major Change
252 // SDL3 no longer uses SDL_WINDOWEVENT with sub-types.
253 // Each window event type is now a top-level event.
254 // =========================================================================
255 case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
256 event.type = WindowEventType::Close;
257 active_ = false;
258 break;
259
260 case SDL_EVENT_WINDOW_RESIZED:
261 event.type = WindowEventType::Resize;
262 event.window_width = sdl_event.window.data1;
263 event.window_height = sdl_event.window.data2;
264 is_resizing_ = true;
266 break;
267
268 case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
269 // This is the SDL3 equivalent of SDL_WINDOWEVENT_SIZE_CHANGED
270 event.type = WindowEventType::Resize;
271 event.window_width = sdl_event.window.data1;
272 event.window_height = sdl_event.window.data2;
273 is_resizing_ = true;
275 break;
276
277 case SDL_EVENT_WINDOW_MINIMIZED:
278 event.type = WindowEventType::Minimized;
279 is_resizing_ = false;
280 g_window_is_resizing = false;
281 break;
282
283 case SDL_EVENT_WINDOW_MAXIMIZED:
284 event.type = WindowEventType::Maximized;
285 break;
286
287 case SDL_EVENT_WINDOW_RESTORED:
288 event.type = WindowEventType::Restored;
289 is_resizing_ = false;
290 g_window_is_resizing = false;
291 break;
292
293 case SDL_EVENT_WINDOW_SHOWN:
294 event.type = WindowEventType::Shown;
295 is_resizing_ = false;
296 g_window_is_resizing = false;
297 break;
298
299 case SDL_EVENT_WINDOW_HIDDEN:
300 event.type = WindowEventType::Hidden;
301 is_resizing_ = false;
302 g_window_is_resizing = false;
303 break;
304
305 case SDL_EVENT_WINDOW_EXPOSED:
306 event.type = WindowEventType::Exposed;
307 is_resizing_ = false;
308 g_window_is_resizing = false;
309 break;
310
311 case SDL_EVENT_WINDOW_FOCUS_GAINED:
312 event.type = WindowEventType::FocusGained;
313 break;
314
315 case SDL_EVENT_WINDOW_FOCUS_LOST:
316 event.type = WindowEventType::FocusLost;
317 break;
318 }
319
320 return event;
321}
322
323void SDL3WindowBackend::UpdateModifierState() {
324 // SDL3 uses SDL_GetModState which returns SDL_Keymod
325 SDL_Keymod mod = SDL_GetModState();
326 key_shift_ = (mod & SDL_KMOD_SHIFT) != 0;
327 key_ctrl_ = (mod & SDL_KMOD_CTRL) != 0;
328 key_alt_ = (mod & SDL_KMOD_ALT) != 0;
329 key_super_ = (mod & SDL_KMOD_GUI) != 0;
330}
331
332WindowStatus SDL3WindowBackend::GetStatus() const {
333 WindowStatus status;
334 status.is_active = active_;
335 status.is_resizing = is_resizing_;
336
337 if (window_) {
338 SDL_WindowFlags flags = SDL_GetWindowFlags(window_.get());
339 status.is_minimized = (flags & SDL_WINDOW_MINIMIZED) != 0;
340 status.is_maximized = (flags & SDL_WINDOW_MAXIMIZED) != 0;
341 status.is_fullscreen = (flags & SDL_WINDOW_FULLSCREEN) != 0;
342 status.is_focused = (flags & SDL_WINDOW_INPUT_FOCUS) != 0;
343
344 SDL_GetWindowSize(window_.get(), &status.width, &status.height);
345 }
346
347 return status;
348}
349
350void SDL3WindowBackend::GetSize(int* width, int* height) const {
351 if (window_) {
352 SDL_GetWindowSize(window_.get(), width, height);
353 } else {
354 if (width) *width = 0;
355 if (height) *height = 0;
356 }
357}
358
359void SDL3WindowBackend::SetSize(int width, int height) {
360 if (window_) {
361 SDL_SetWindowSize(window_.get(), width, height);
362 }
363}
364
365std::string SDL3WindowBackend::GetTitle() const {
366 if (window_) {
367 const char* title = SDL_GetWindowTitle(window_.get());
368 return title ? title : "";
369 }
370 return "";
371}
372
373void SDL3WindowBackend::SetTitle(const std::string& title) {
374 if (window_) {
375 SDL_SetWindowTitle(window_.get(), title.c_str());
376 }
377}
378
379bool SDL3WindowBackend::InitializeRenderer(gfx::IRenderer* renderer) {
380 if (!window_ || !renderer) {
381 return false;
382 }
383
384 if (renderer->GetBackendRenderer()) {
385 // Already initialized
386 return true;
387 }
388
389 return renderer->Initialize(window_.get());
390}
391
392absl::Status SDL3WindowBackend::InitializeImGui(gfx::IRenderer* renderer) {
393 if (imgui_initialized_) {
394 return absl::OkStatus();
395 }
396
397 if (!renderer) {
398 return absl::InvalidArgumentError("Renderer is null");
399 }
400
401 IMGUI_CHECKVERSION();
402 ImGui::CreateContext();
403
404 ImGuiIO& io = ImGui::GetIO();
405 io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
406 io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
407 io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
408
409 // Ensure macOS-style behavior (Cmd acts as Ctrl for shortcuts)
410#ifdef __APPLE__
411 io.ConfigMacOSXBehaviors = true;
412#endif
413
414 // Initialize ImGui backends for SDL3
415 SDL_Renderer* sdl_renderer =
416 static_cast<SDL_Renderer*>(renderer->GetBackendRenderer());
417
418 if (!sdl_renderer) {
419 return absl::InternalError("Failed to get SDL renderer from IRenderer");
420 }
421
422 // Note: SDL3 uses different ImGui backend functions
423 if (!ImGui_ImplSDL3_InitForSDLRenderer(window_.get(), sdl_renderer)) {
424 return absl::InternalError("ImGui_ImplSDL3_InitForSDLRenderer failed");
425 }
426
427 if (!ImGui_ImplSDLRenderer3_Init(sdl_renderer)) {
428 ImGui_ImplSDL3_Shutdown();
429 return absl::InternalError("ImGui_ImplSDLRenderer3_Init failed");
430 }
431
432 // Load fonts
434
435 // Apply default style
437
438 imgui_initialized_ = true;
439 LOG_INFO("SDL3WindowBackend", "ImGui initialized successfully");
440 return absl::OkStatus();
441}
442
443void SDL3WindowBackend::ShutdownImGui() {
444 if (!imgui_initialized_) {
445 return;
446 }
447
448 LOG_INFO("SDL3WindowBackend", "Shutting down ImGui implementations...");
449 ImGui_ImplSDLRenderer3_Shutdown();
450 ImGui_ImplSDL3_Shutdown();
451
452 LOG_INFO("SDL3WindowBackend", "Destroying ImGui context...");
453 ImGui::DestroyContext();
454
455 imgui_initialized_ = false;
456}
457
458void SDL3WindowBackend::NewImGuiFrame() {
459 if (!imgui_initialized_) {
460 return;
461 }
462
463 ImGui_ImplSDLRenderer3_NewFrame();
464 ImGui_ImplSDL3_NewFrame();
465}
466
467void SDL3WindowBackend::RenderImGui(gfx::IRenderer* renderer) {
468 if (!imgui_initialized_) {
469 return;
470 }
471
472 // Finalize ImGui frame and render draw data
473 ImGui::Render();
474
475 if (renderer) {
476 SDL_Renderer* sdl_renderer =
477 static_cast<SDL_Renderer*>(renderer->GetBackendRenderer());
478 if (sdl_renderer) {
479 ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData(), sdl_renderer);
480 }
481 }
482
483 // Multi-viewport support
484 ImGuiIO& io = ImGui::GetIO();
485 if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
486 ImGui::UpdatePlatformWindows();
487 ImGui::RenderPlatformWindowsDefault();
488 }
489}
490
491} // namespace platform
492} // namespace yaze
493
494#endif // YAZE_USE_SDL3
void Shutdown()
Definition arena.cc:294
static Arena & Get()
Definition arena.cc:19
#define LOG_WARN(category, format,...)
Definition log.h:107
#define LOG_INFO(category, format,...)
Definition log.h:105
void ColorsYaze()
Definition style.cc:32
absl::Status LoadPackageFonts()
#define g_window_is_resizing
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22