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 = SDL_CreateWindow(config.title.c_str(), screen_width,
93 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", "Initialized: %dx%d, audio buffer: %zu samples",
110 screen_width, screen_height, buffer_size);
111
112 initialized_ = true;
113 active_ = true;
114 return absl::OkStatus();
115}
116
117absl::Status SDL3WindowBackend::Shutdown() {
118 if (!initialized_) {
119 return absl::OkStatus();
120 }
121
122 // Shutdown ImGui if initialized
123 if (imgui_initialized_) {
124 ShutdownImGui();
125 }
126
127 // Shutdown graphics arena while renderer is still valid
128 LOG_INFO("SDL3WindowBackend", "Shutting down graphics arena...");
130
131 // Destroy window
132 if (window_) {
133 LOG_INFO("SDL3WindowBackend", "Destroying window...");
134 window_.reset();
135 }
136
137 // Quit SDL
138 LOG_INFO("SDL3WindowBackend", "Shutting down SDL...");
139 SDL_Quit();
140
141 initialized_ = false;
142 LOG_INFO("SDL3WindowBackend", "Shutdown complete");
143 return absl::OkStatus();
144}
145
146bool SDL3WindowBackend::PollEvent(WindowEvent& out_event) {
147 SDL_Event sdl_event;
148 if (SDL_PollEvent(&sdl_event)) {
149 // Let ImGui process the event first
150 if (imgui_initialized_) {
151 ImGui_ImplSDL3_ProcessEvent(&sdl_event);
152 }
153
154 // Convert to platform-agnostic event
155 out_event = ConvertSDL3Event(sdl_event);
156 out_event.has_native_event = true;
157 out_event.native_event = sdl_event;
158 return true;
159 }
160 return false;
161}
162
163void SDL3WindowBackend::ProcessNativeEvent(void* native_event) {
164 if (native_event && imgui_initialized_) {
165 ImGui_ImplSDL3_ProcessEvent(static_cast<SDL_Event*>(native_event));
166 }
167}
168
169WindowEvent SDL3WindowBackend::ConvertSDL3Event(const SDL_Event& sdl_event) {
170 WindowEvent event;
171 event.type = WindowEventType::None;
172
173 switch (sdl_event.type) {
174 // =========================================================================
175 // Application Events
176 // =========================================================================
177 case SDL_EVENT_QUIT:
178 event.type = WindowEventType::Quit;
179 active_ = false;
180 break;
181
182 // =========================================================================
183 // Keyboard Events
184 // Note: SDL3 uses event.key.key instead of event.key.keysym.sym
185 // =========================================================================
186 case SDL_EVENT_KEY_DOWN:
187 event.type = WindowEventType::KeyDown;
188 event.key_code = sdl_event.key.key;
189 event.scan_code = sdl_event.key.scancode;
190 UpdateModifierState();
191 event.key_shift = key_shift_;
192 event.key_ctrl = key_ctrl_;
193 event.key_alt = key_alt_;
194 event.key_super = key_super_;
195 break;
196
197 case SDL_EVENT_KEY_UP:
198 event.type = WindowEventType::KeyUp;
199 event.key_code = sdl_event.key.key;
200 event.scan_code = sdl_event.key.scancode;
201 UpdateModifierState();
202 event.key_shift = key_shift_;
203 event.key_ctrl = key_ctrl_;
204 event.key_alt = key_alt_;
205 event.key_super = key_super_;
206 break;
207
208 // =========================================================================
209 // Mouse Events
210 // Note: SDL3 uses float coordinates
211 // =========================================================================
212 case SDL_EVENT_MOUSE_MOTION:
213 event.type = WindowEventType::MouseMotion;
214 event.mouse_x = sdl_event.motion.x;
215 event.mouse_y = sdl_event.motion.y;
216 break;
217
218 case SDL_EVENT_MOUSE_BUTTON_DOWN:
220 event.mouse_x = sdl_event.button.x;
221 event.mouse_y = sdl_event.button.y;
222 event.mouse_button = sdl_event.button.button;
223 break;
224
225 case SDL_EVENT_MOUSE_BUTTON_UP:
227 event.mouse_x = sdl_event.button.x;
228 event.mouse_y = sdl_event.button.y;
229 event.mouse_button = sdl_event.button.button;
230 break;
231
232 case SDL_EVENT_MOUSE_WHEEL:
233 event.type = WindowEventType::MouseWheel;
234 event.wheel_x = sdl_event.wheel.x;
235 event.wheel_y = sdl_event.wheel.y;
236 break;
237
238 // =========================================================================
239 // Drop Events
240 // =========================================================================
241 case SDL_EVENT_DROP_FILE:
242 event.type = WindowEventType::DropFile;
243 if (sdl_event.drop.data) {
244 event.dropped_file = sdl_event.drop.data;
245 // Note: SDL3 drop.data is managed by SDL, don't free it
246 }
247 break;
248
249 // =========================================================================
250 // Window Events - SDL3 Major Change
251 // SDL3 no longer uses SDL_WINDOWEVENT with sub-types.
252 // Each window event type is now a top-level event.
253 // =========================================================================
254 case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
255 event.type = WindowEventType::Close;
256 active_ = false;
257 break;
258
259 case SDL_EVENT_WINDOW_RESIZED:
260 event.type = WindowEventType::Resize;
261 event.window_width = sdl_event.window.data1;
262 event.window_height = sdl_event.window.data2;
263 is_resizing_ = true;
265 break;
266
267 case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
268 // This is the SDL3 equivalent of SDL_WINDOWEVENT_SIZE_CHANGED
269 event.type = WindowEventType::Resize;
270 event.window_width = sdl_event.window.data1;
271 event.window_height = sdl_event.window.data2;
272 is_resizing_ = true;
274 break;
275
276 case SDL_EVENT_WINDOW_MINIMIZED:
277 event.type = WindowEventType::Minimized;
278 is_resizing_ = false;
279 g_window_is_resizing = false;
280 break;
281
282 case SDL_EVENT_WINDOW_MAXIMIZED:
283 event.type = WindowEventType::Maximized;
284 break;
285
286 case SDL_EVENT_WINDOW_RESTORED:
287 event.type = WindowEventType::Restored;
288 is_resizing_ = false;
289 g_window_is_resizing = false;
290 break;
291
292 case SDL_EVENT_WINDOW_SHOWN:
293 event.type = WindowEventType::Shown;
294 is_resizing_ = false;
295 g_window_is_resizing = false;
296 break;
297
298 case SDL_EVENT_WINDOW_HIDDEN:
299 event.type = WindowEventType::Hidden;
300 is_resizing_ = false;
301 g_window_is_resizing = false;
302 break;
303
304 case SDL_EVENT_WINDOW_EXPOSED:
305 event.type = WindowEventType::Exposed;
306 is_resizing_ = false;
307 g_window_is_resizing = false;
308 break;
309
310 case SDL_EVENT_WINDOW_FOCUS_GAINED:
311 event.type = WindowEventType::FocusGained;
312 break;
313
314 case SDL_EVENT_WINDOW_FOCUS_LOST:
315 event.type = WindowEventType::FocusLost;
316 break;
317 }
318
319 return event;
320}
321
322void SDL3WindowBackend::UpdateModifierState() {
323 // SDL3 uses SDL_GetModState which returns SDL_Keymod
324 SDL_Keymod mod = SDL_GetModState();
325 key_shift_ = (mod & SDL_KMOD_SHIFT) != 0;
326 key_ctrl_ = (mod & SDL_KMOD_CTRL) != 0;
327 key_alt_ = (mod & SDL_KMOD_ALT) != 0;
328 key_super_ = (mod & SDL_KMOD_GUI) != 0;
329}
330
331WindowStatus SDL3WindowBackend::GetStatus() const {
332 WindowStatus status;
333 status.is_active = active_;
334 status.is_resizing = is_resizing_;
335
336 if (window_) {
337 SDL_WindowFlags flags = SDL_GetWindowFlags(window_.get());
338 status.is_minimized = (flags & SDL_WINDOW_MINIMIZED) != 0;
339 status.is_maximized = (flags & SDL_WINDOW_MAXIMIZED) != 0;
340 status.is_fullscreen = (flags & SDL_WINDOW_FULLSCREEN) != 0;
341 status.is_focused = (flags & SDL_WINDOW_INPUT_FOCUS) != 0;
342
343 SDL_GetWindowSize(window_.get(), &status.width, &status.height);
344 }
345
346 return status;
347}
348
349void SDL3WindowBackend::GetSize(int* width, int* height) const {
350 if (window_) {
351 SDL_GetWindowSize(window_.get(), width, height);
352 } else {
353 if (width)
354 *width = 0;
355 if (height)
356 *height = 0;
357 }
358}
359
360void SDL3WindowBackend::SetSize(int width, int height) {
361 if (window_) {
362 SDL_SetWindowSize(window_.get(), width, height);
363 }
364}
365
366std::string SDL3WindowBackend::GetTitle() const {
367 if (window_) {
368 const char* title = SDL_GetWindowTitle(window_.get());
369 return title ? title : "";
370 }
371 return "";
372}
373
374void SDL3WindowBackend::SetTitle(const std::string& title) {
375 if (window_) {
376 SDL_SetWindowTitle(window_.get(), title.c_str());
377 }
378}
379
380bool SDL3WindowBackend::InitializeRenderer(gfx::IRenderer* renderer) {
381 if (!window_ || !renderer) {
382 return false;
383 }
384
385 if (renderer->GetBackendRenderer()) {
386 // Already initialized
387 return true;
388 }
389
390 return renderer->Initialize(window_.get());
391}
392
393absl::Status SDL3WindowBackend::InitializeImGui(gfx::IRenderer* renderer) {
394 if (imgui_initialized_) {
395 return absl::OkStatus();
396 }
397
398 if (!renderer) {
399 return absl::InvalidArgumentError("Renderer is null");
400 }
401
402 IMGUI_CHECKVERSION();
403 ImGui::CreateContext();
404
405 ImGuiIO& io = ImGui::GetIO();
406 io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
407 io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
408 io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
409
410 // Ensure macOS-style behavior (Cmd acts as Ctrl for shortcuts)
411#ifdef __APPLE__
412 io.ConfigMacOSXBehaviors = true;
413#endif
414
415 // Initialize ImGui backends for SDL3
416 SDL_Renderer* sdl_renderer =
417 static_cast<SDL_Renderer*>(renderer->GetBackendRenderer());
418
419 if (!sdl_renderer) {
420 return absl::InternalError("Failed to get SDL renderer from IRenderer");
421 }
422
423 // Note: SDL3 uses different ImGui backend functions
424 if (!ImGui_ImplSDL3_InitForSDLRenderer(window_.get(), sdl_renderer)) {
425 return absl::InternalError("ImGui_ImplSDL3_InitForSDLRenderer failed");
426 }
427
428 if (!ImGui_ImplSDLRenderer3_Init(sdl_renderer)) {
429 ImGui_ImplSDL3_Shutdown();
430 return absl::InternalError("ImGui_ImplSDLRenderer3_Init failed");
431 }
432
433 // Load fonts
435
436 // Apply default style
438
439 imgui_initialized_ = true;
440 LOG_INFO("SDL3WindowBackend", "ImGui initialized successfully");
441 return absl::OkStatus();
442}
443
444void SDL3WindowBackend::ShutdownImGui() {
445 if (!imgui_initialized_) {
446 return;
447 }
448
449 LOG_INFO("SDL3WindowBackend", "Shutting down ImGui implementations...");
450 ImGui_ImplSDLRenderer3_Shutdown();
451 ImGui_ImplSDL3_Shutdown();
452
453 LOG_INFO("SDL3WindowBackend", "Destroying ImGui context...");
454 ImGui::DestroyContext();
455
456 imgui_initialized_ = false;
457}
458
459void SDL3WindowBackend::NewImGuiFrame() {
460 if (!imgui_initialized_) {
461 return;
462 }
463
464 ImGui_ImplSDLRenderer3_NewFrame();
465 ImGui_ImplSDL3_NewFrame();
466}
467
468void SDL3WindowBackend::RenderImGui(gfx::IRenderer* renderer) {
469 if (!imgui_initialized_) {
470 return;
471 }
472
473 // Finalize ImGui frame and render draw data
474 ImGui::Render();
475
476 if (renderer) {
477 SDL_Renderer* sdl_renderer =
478 static_cast<SDL_Renderer*>(renderer->GetBackendRenderer());
479 if (sdl_renderer) {
480 ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData(), sdl_renderer);
481 }
482 }
483
484 // Multi-viewport support
485 ImGuiIO& io = ImGui::GetIO();
486 if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
487 ImGui::UpdatePlatformWindows();
488 ImGui::RenderPlatformWindowsDefault();
489 }
490}
491
492} // namespace platform
493} // namespace yaze
494
495#endif // YAZE_USE_SDL3
void Shutdown()
Definition arena.cc:304
static Arena & Get()
Definition arena.cc:20
#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