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