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