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
167void SDL3WindowBackend::ProcessNativeEvent(void* native_event) {
168 if (native_event && imgui_initialized_) {
169 ImGui_ImplSDL3_ProcessEvent(static_cast<SDL_Event*>(native_event));
170 }
171}
172
173WindowEvent SDL3WindowBackend::ConvertSDL3Event(const SDL_Event& sdl_event) {
174 WindowEvent event;
175 event.type = WindowEventType::None;
176
177 switch (sdl_event.type) {
178 // =========================================================================
179 // Application Events
180 // =========================================================================
181 case SDL_EVENT_QUIT:
182 event.type = WindowEventType::Quit;
183 active_ = false;
184 break;
185
186 // =========================================================================
187 // Keyboard Events
188 // Note: SDL3 uses event.key.key instead of event.key.keysym.sym
189 // =========================================================================
190 case SDL_EVENT_KEY_DOWN:
191 event.type = WindowEventType::KeyDown;
192 event.key_code = sdl_event.key.key;
193 event.scan_code = sdl_event.key.scancode;
194 UpdateModifierState();
195 event.key_shift = key_shift_;
196 event.key_ctrl = key_ctrl_;
197 event.key_alt = key_alt_;
198 event.key_super = key_super_;
199 break;
200
201 case SDL_EVENT_KEY_UP:
202 event.type = WindowEventType::KeyUp;
203 event.key_code = sdl_event.key.key;
204 event.scan_code = sdl_event.key.scancode;
205 UpdateModifierState();
206 event.key_shift = key_shift_;
207 event.key_ctrl = key_ctrl_;
208 event.key_alt = key_alt_;
209 event.key_super = key_super_;
210 break;
211
212 // =========================================================================
213 // Mouse Events
214 // Note: SDL3 uses float coordinates
215 // =========================================================================
216 case SDL_EVENT_MOUSE_MOTION:
217 event.type = WindowEventType::MouseMotion;
218 event.mouse_x = sdl_event.motion.x;
219 event.mouse_y = sdl_event.motion.y;
220 break;
221
222 case SDL_EVENT_MOUSE_BUTTON_DOWN:
224 event.mouse_x = sdl_event.button.x;
225 event.mouse_y = sdl_event.button.y;
226 event.mouse_button = sdl_event.button.button;
227 break;
228
229 case SDL_EVENT_MOUSE_BUTTON_UP:
231 event.mouse_x = sdl_event.button.x;
232 event.mouse_y = sdl_event.button.y;
233 event.mouse_button = sdl_event.button.button;
234 break;
235
236 case SDL_EVENT_MOUSE_WHEEL:
237 event.type = WindowEventType::MouseWheel;
238 event.wheel_x = sdl_event.wheel.x;
239 event.wheel_y = sdl_event.wheel.y;
240 break;
241
242 // =========================================================================
243 // Drop Events
244 // =========================================================================
245 case SDL_EVENT_DROP_FILE:
246 event.type = WindowEventType::DropFile;
247 if (sdl_event.drop.data) {
248 event.dropped_file = sdl_event.drop.data;
249 // Note: SDL3 drop.data is managed by SDL, don't free it
250 }
251 break;
252
253 // =========================================================================
254 // Window Events - SDL3 Major Change
255 // SDL3 no longer uses SDL_WINDOWEVENT with sub-types.
256 // Each window event type is now a top-level event.
257 // =========================================================================
258 case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
259 event.type = WindowEventType::Close;
260 active_ = false;
261 break;
262
263 case SDL_EVENT_WINDOW_RESIZED:
264 event.type = WindowEventType::Resize;
265 event.window_width = sdl_event.window.data1;
266 event.window_height = sdl_event.window.data2;
267 is_resizing_ = true;
269 break;
270
271 case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
272 // This is the SDL3 equivalent of SDL_WINDOWEVENT_SIZE_CHANGED
273 event.type = WindowEventType::Resize;
274 event.window_width = sdl_event.window.data1;
275 event.window_height = sdl_event.window.data2;
276 is_resizing_ = true;
278 break;
279
280 case SDL_EVENT_WINDOW_MINIMIZED:
281 event.type = WindowEventType::Minimized;
282 is_resizing_ = false;
283 g_window_is_resizing = false;
284 break;
285
286 case SDL_EVENT_WINDOW_MAXIMIZED:
287 event.type = WindowEventType::Maximized;
288 break;
289
290 case SDL_EVENT_WINDOW_RESTORED:
291 event.type = WindowEventType::Restored;
292 is_resizing_ = false;
293 g_window_is_resizing = false;
294 break;
295
296 case SDL_EVENT_WINDOW_SHOWN:
297 event.type = WindowEventType::Shown;
298 is_resizing_ = false;
299 g_window_is_resizing = false;
300 break;
301
302 case SDL_EVENT_WINDOW_HIDDEN:
303 event.type = WindowEventType::Hidden;
304 is_resizing_ = false;
305 g_window_is_resizing = false;
306 break;
307
308 case SDL_EVENT_WINDOW_EXPOSED:
309 event.type = WindowEventType::Exposed;
310 is_resizing_ = false;
311 g_window_is_resizing = false;
312 break;
313
314 case SDL_EVENT_WINDOW_FOCUS_GAINED:
315 event.type = WindowEventType::FocusGained;
316 break;
317
318 case SDL_EVENT_WINDOW_FOCUS_LOST:
319 event.type = WindowEventType::FocusLost;
320 break;
321 }
322
323 return event;
324}
325
326void SDL3WindowBackend::UpdateModifierState() {
327 // SDL3 uses SDL_GetModState which returns SDL_Keymod
328 SDL_Keymod mod = SDL_GetModState();
329 key_shift_ = (mod & SDL_KMOD_SHIFT) != 0;
330 key_ctrl_ = (mod & SDL_KMOD_CTRL) != 0;
331 key_alt_ = (mod & SDL_KMOD_ALT) != 0;
332 key_super_ = (mod & SDL_KMOD_GUI) != 0;
333}
334
335WindowStatus SDL3WindowBackend::GetStatus() const {
336 WindowStatus status;
337 status.is_active = active_;
338 status.is_resizing = is_resizing_;
339
340 if (window_) {
341 SDL_WindowFlags flags = SDL_GetWindowFlags(window_.get());
342 status.is_minimized = (flags & SDL_WINDOW_MINIMIZED) != 0;
343 status.is_maximized = (flags & SDL_WINDOW_MAXIMIZED) != 0;
344 status.is_fullscreen = (flags & SDL_WINDOW_FULLSCREEN) != 0;
345 status.is_focused = (flags & SDL_WINDOW_INPUT_FOCUS) != 0;
346
347 SDL_GetWindowSize(window_.get(), &status.width, &status.height);
348 }
349
350 return status;
351}
352
353void SDL3WindowBackend::GetSize(int* width, int* height) const {
354 if (window_) {
355 SDL_GetWindowSize(window_.get(), width, height);
356 } else {
357 if (width)
358 *width = 0;
359 if (height)
360 *height = 0;
361 }
362}
363
364void SDL3WindowBackend::SetSize(int width, int height) {
365 if (window_) {
366 SDL_SetWindowSize(window_.get(), width, height);
367 }
368}
369
370std::string SDL3WindowBackend::GetTitle() const {
371 if (window_) {
372 const char* title = SDL_GetWindowTitle(window_.get());
373 return title ? title : "";
374 }
375 return "";
376}
377
378void SDL3WindowBackend::SetTitle(const std::string& title) {
379 if (window_) {
380 SDL_SetWindowTitle(window_.get(), title.c_str());
381 }
382}
383
384void SDL3WindowBackend::ShowWindow() {
385 if (window_) {
386 SDL_ShowWindow(window_.get());
387 }
388}
389
390void SDL3WindowBackend::HideWindow() {
391 if (window_) {
392 SDL_HideWindow(window_.get());
393 }
394}
395
396bool SDL3WindowBackend::InitializeRenderer(gfx::IRenderer* renderer) {
397 if (!window_ || !renderer) {
398 return false;
399 }
400
401 if (renderer->GetBackendRenderer()) {
402 // Already initialized
403 return true;
404 }
405
406 return renderer->Initialize(window_.get());
407}
408
409absl::Status SDL3WindowBackend::InitializeImGui(gfx::IRenderer* renderer) {
410 if (imgui_initialized_) {
411 return absl::OkStatus();
412 }
413
414 if (!renderer) {
415 return absl::InvalidArgumentError("Renderer is null");
416 }
417
418 IMGUI_CHECKVERSION();
419 ImGui::CreateContext();
420
421 ImGuiIO& io = ImGui::GetIO();
422 io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
423 io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
424 io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
425
426 // Ensure macOS-style behavior (Cmd acts as Ctrl for shortcuts)
427#ifdef __APPLE__
428 io.ConfigMacOSXBehaviors = true;
429#endif
430
431 if (auto ini_path = util::PlatformPaths::GetImGuiIniPath(); ini_path.ok()) {
432 static std::string ini_path_str;
433 if (ini_path_str.empty()) {
434 ini_path_str = ini_path->string();
435 }
436 io.IniFilename = ini_path_str.c_str();
437 } else {
438 io.IniFilename = nullptr;
439 LOG_WARN("SDL3WindowBackend", "Failed to resolve ImGui ini path: %s",
440 ini_path.status().ToString().c_str());
441 }
442
443 // Initialize ImGui backends for SDL3
444 SDL_Renderer* sdl_renderer =
445 static_cast<SDL_Renderer*>(renderer->GetBackendRenderer());
446
447 if (!sdl_renderer) {
448 return absl::InternalError("Failed to get SDL renderer from IRenderer");
449 }
450
451 // Note: SDL3 uses different ImGui backend functions
452 if (!ImGui_ImplSDL3_InitForSDLRenderer(window_.get(), sdl_renderer)) {
453 return absl::InternalError("ImGui_ImplSDL3_InitForSDLRenderer failed");
454 }
455
456 if (!ImGui_ImplSDLRenderer3_Init(sdl_renderer)) {
457 ImGui_ImplSDL3_Shutdown();
458 return absl::InternalError("ImGui_ImplSDLRenderer3_Init failed");
459 }
460
461 // Load fonts
463
464 // Apply default style
466
467 imgui_initialized_ = true;
468 LOG_INFO("SDL3WindowBackend", "ImGui initialized successfully");
469 return absl::OkStatus();
470}
471
472void SDL3WindowBackend::ShutdownImGui() {
473 if (!imgui_initialized_) {
474 return;
475 }
476
477 LOG_INFO("SDL3WindowBackend", "Shutting down ImGui implementations...");
478 ImGui_ImplSDLRenderer3_Shutdown();
479 ImGui_ImplSDL3_Shutdown();
480
481 LOG_INFO("SDL3WindowBackend", "Destroying ImGui context...");
482 ImGui::DestroyContext();
483
484 imgui_initialized_ = false;
485}
486
487void SDL3WindowBackend::NewImGuiFrame() {
488 if (!imgui_initialized_) {
489 return;
490 }
491
492 ImGui_ImplSDLRenderer3_NewFrame();
493 ImGui_ImplSDL3_NewFrame();
494}
495
496void SDL3WindowBackend::RenderImGui(gfx::IRenderer* renderer) {
497 if (!imgui_initialized_) {
498 return;
499 }
500
501 // Finalize ImGui frame and render draw data
502 ImGui::Render();
503
504 if (renderer) {
505 SDL_Renderer* sdl_renderer =
506 static_cast<SDL_Renderer*>(renderer->GetBackendRenderer());
507 if (sdl_renderer) {
508 ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData(), sdl_renderer);
509 }
510 }
511
512 // Multi-viewport support
513 ImGuiIO& io = ImGui::GetIO();
514 if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
515 ImGui::UpdatePlatformWindows();
516 ImGui::RenderPlatformWindowsDefault();
517 }
518}
519
520} // namespace platform
521} // namespace yaze
522
523#endif // YAZE_USE_SDL3
void Shutdown()
Definition arena.cc:371
static Arena & Get()
Definition arena.cc:21
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:32
absl::Status LoadPackageFonts()
#define g_window_is_resizing
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22