yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
sdl2_window_backend.cc
Go to the documentation of this file.
1// sdl2_window_backend.cc - SDL2 Window Backend Implementation
2
4
6
7#include <filesystem>
8
9#include "absl/status/status.h"
10#include "absl/strings/str_format.h"
12#include "app/gui/core/style.h"
14#include "imgui/backends/imgui_impl_sdl2.h"
15#include "imgui/backends/imgui_impl_sdlrenderer2.h"
16#include "imgui/imgui.h"
17#include "util/log.h"
18
19namespace yaze {
20
21// Forward reference to the global resize flag defined in window.cc
22namespace core {
24}
25
26namespace platform {
27
28// Alias to core's resize flag for compatibility
29#define g_window_is_resizing yaze::core::g_window_is_resizing
30
36
37absl::Status SDL2WindowBackend::Initialize(const WindowConfig& config) {
38 if (initialized_) {
39 LOG_WARN("SDL2WindowBackend", "Already initialized, shutting down first");
41 }
42
43 // Initialize SDL2 subsystems
44 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER) != 0) {
45 return absl::InternalError(
46 absl::StrFormat("SDL_Init failed: %s", SDL_GetError()));
47 }
48
49 // Determine window size
50 int screen_width = config.width;
51 int screen_height = config.height;
52
53 if (screen_width == 0 || screen_height == 0) {
54 // Auto-detect from display
55 SDL_DisplayMode display_mode;
56 if (SDL_GetCurrentDisplayMode(0, &display_mode) == 0) {
57 screen_width = static_cast<int>(display_mode.w * config.display_scale);
58 screen_height = static_cast<int>(display_mode.h * config.display_scale);
59 } else {
60 // Fallback to reasonable defaults
61 screen_width = 1280;
62 screen_height = 720;
63 LOG_WARN("SDL2WindowBackend",
64 "Failed to get display mode, using defaults: %dx%d",
65 screen_width, screen_height);
66 }
67 }
68
69 // Build window flags
70 Uint32 flags = 0;
71 if (config.resizable) {
72 flags |= SDL_WINDOW_RESIZABLE;
73 }
74 if (config.maximized) {
75 flags |= SDL_WINDOW_MAXIMIZED;
76 }
77 if (config.fullscreen) {
78 flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
79 }
80 if (config.high_dpi) {
81 flags |= SDL_WINDOW_ALLOW_HIGHDPI;
82 }
83
84 // Create window
85 window_ = std::unique_ptr<SDL_Window, util::SDL_Deleter>(
86 SDL_CreateWindow(config.title.c_str(), SDL_WINDOWPOS_UNDEFINED,
87 SDL_WINDOWPOS_UNDEFINED, screen_width, screen_height,
88 flags),
90
91 if (!window_) {
92 SDL_Quit();
93 return absl::InternalError(
94 absl::StrFormat("SDL_CreateWindow failed: %s", SDL_GetError()));
95 }
96
97 // Allocate legacy audio buffer for backwards compatibility
98 const int audio_frequency = 48000;
99 const size_t buffer_size = (audio_frequency / 50) * 2; // Stereo PAL
100 audio_buffer_ = std::shared_ptr<int16_t>(new int16_t[buffer_size],
101 std::default_delete<int16_t[]>());
102
103 LOG_INFO("SDL2WindowBackend",
104 "Initialized: %dx%d, audio buffer: %zu samples", screen_width,
105 screen_height, buffer_size);
106
107 initialized_ = true;
108 active_ = true;
109 return absl::OkStatus();
110}
111
113 if (!initialized_) {
114 return absl::OkStatus();
115 }
116
117 // Pause and close audio device if open
118 if (audio_device_ != 0) {
119 SDL_PauseAudioDevice(audio_device_, 1);
120 SDL_CloseAudioDevice(audio_device_);
121 audio_device_ = 0;
122 }
123
124 // Shutdown ImGui if initialized
125 if (imgui_initialized_) {
127 }
128
129 // Shutdown graphics arena while renderer is still valid
130 LOG_INFO("SDL2WindowBackend", "Shutting down graphics arena...");
132
133 // Destroy window
134 if (window_) {
135 LOG_INFO("SDL2WindowBackend", "Destroying window...");
136 window_.reset();
137 }
138
139 // Quit SDL
140 LOG_INFO("SDL2WindowBackend", "Shutting down SDL...");
141 SDL_Quit();
142
143 initialized_ = false;
144 LOG_INFO("SDL2WindowBackend", "Shutdown complete");
145 return absl::OkStatus();
146}
147
149 SDL_Event sdl_event;
150 if (SDL_PollEvent(&sdl_event)) {
151 // Let ImGui process the event first
152 if (imgui_initialized_) {
153 ImGui_ImplSDL2_ProcessEvent(&sdl_event);
154 }
155
156 // Convert to platform-agnostic event
157 out_event = ConvertSDL2Event(sdl_event);
158 out_event.has_native_event = true;
159 out_event.native_event = sdl_event;
160 return true;
161 }
162 return false;
163}
164
166 if (native_event && imgui_initialized_) {
167 ImGui_ImplSDL2_ProcessEvent(static_cast<SDL_Event*>(native_event));
168 }
169}
170
172 WindowEvent event;
174
175 switch (sdl_event.type) {
176 case SDL_QUIT:
177 event.type = WindowEventType::Quit;
178 active_ = false;
179 break;
180
181 case SDL_KEYDOWN:
182 event.type = WindowEventType::KeyDown;
183 event.key_code = sdl_event.key.keysym.sym;
184 event.scan_code = sdl_event.key.keysym.scancode;
186 event.key_shift = key_shift_;
187 event.key_ctrl = key_ctrl_;
188 event.key_alt = key_alt_;
189 event.key_super = key_super_;
190 break;
191
192 case SDL_KEYUP:
193 event.type = WindowEventType::KeyUp;
194 event.key_code = sdl_event.key.keysym.sym;
195 event.scan_code = sdl_event.key.keysym.scancode;
197 event.key_shift = key_shift_;
198 event.key_ctrl = key_ctrl_;
199 event.key_alt = key_alt_;
200 event.key_super = key_super_;
201 break;
202
203 case SDL_MOUSEMOTION:
204 event.type = WindowEventType::MouseMotion;
205 event.mouse_x = static_cast<float>(sdl_event.motion.x);
206 event.mouse_y = static_cast<float>(sdl_event.motion.y);
207 break;
208
209 case SDL_MOUSEBUTTONDOWN:
211 event.mouse_x = static_cast<float>(sdl_event.button.x);
212 event.mouse_y = static_cast<float>(sdl_event.button.y);
213 event.mouse_button = sdl_event.button.button;
214 break;
215
216 case SDL_MOUSEBUTTONUP:
218 event.mouse_x = static_cast<float>(sdl_event.button.x);
219 event.mouse_y = static_cast<float>(sdl_event.button.y);
220 event.mouse_button = sdl_event.button.button;
221 break;
222
223 case SDL_MOUSEWHEEL:
224 event.type = WindowEventType::MouseWheel;
225 event.wheel_x = static_cast<float>(sdl_event.wheel.x);
226 event.wheel_y = static_cast<float>(sdl_event.wheel.y);
227 break;
228
229 case SDL_DROPFILE:
230 event.type = WindowEventType::DropFile;
231 if (sdl_event.drop.file) {
232 event.dropped_file = sdl_event.drop.file;
233 SDL_free(sdl_event.drop.file);
234 }
235 break;
236
237 case SDL_WINDOWEVENT:
238 switch (sdl_event.window.event) {
239 case SDL_WINDOWEVENT_CLOSE:
240 event.type = WindowEventType::Close;
241 active_ = false;
242 break;
243
244 case SDL_WINDOWEVENT_SIZE_CHANGED:
245 case SDL_WINDOWEVENT_RESIZED:
246 event.type = WindowEventType::Resize;
247 event.window_width = sdl_event.window.data1;
248 event.window_height = sdl_event.window.data2;
249 is_resizing_ = true;
251 break;
252
253 case SDL_WINDOWEVENT_MINIMIZED:
254 event.type = WindowEventType::Minimized;
255 is_resizing_ = false;
256 g_window_is_resizing = false;
257 break;
258
259 case SDL_WINDOWEVENT_MAXIMIZED:
260 event.type = WindowEventType::Maximized;
261 break;
262
263 case SDL_WINDOWEVENT_RESTORED:
264 event.type = WindowEventType::Restored;
265 is_resizing_ = false;
266 g_window_is_resizing = false;
267 break;
268
269 case SDL_WINDOWEVENT_SHOWN:
270 event.type = WindowEventType::Shown;
271 is_resizing_ = false;
272 g_window_is_resizing = false;
273 break;
274
275 case SDL_WINDOWEVENT_HIDDEN:
276 event.type = WindowEventType::Hidden;
277 is_resizing_ = false;
278 g_window_is_resizing = false;
279 break;
280
281 case SDL_WINDOWEVENT_EXPOSED:
282 event.type = WindowEventType::Exposed;
283 is_resizing_ = false;
284 g_window_is_resizing = false;
285 break;
286
287 case SDL_WINDOWEVENT_FOCUS_GAINED:
288 event.type = WindowEventType::FocusGained;
289 break;
290
291 case SDL_WINDOWEVENT_FOCUS_LOST:
292 event.type = WindowEventType::FocusLost;
293 break;
294 }
295 break;
296 }
297
298 return event;
299}
300
302 SDL_Keymod mod = SDL_GetModState();
303 key_shift_ = (mod & KMOD_SHIFT) != 0;
304 key_ctrl_ = (mod & KMOD_CTRL) != 0;
305 key_alt_ = (mod & KMOD_ALT) != 0;
306 key_super_ = (mod & KMOD_GUI) != 0;
307}
308
310 WindowStatus status;
311 status.is_active = active_;
312 status.is_resizing = is_resizing_;
313
314 if (window_) {
315 Uint32 flags = SDL_GetWindowFlags(window_.get());
316 status.is_minimized = (flags & SDL_WINDOW_MINIMIZED) != 0;
317 status.is_maximized = (flags & SDL_WINDOW_MAXIMIZED) != 0;
318 status.is_fullscreen =
319 (flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_FULLSCREEN_DESKTOP)) != 0;
320 status.is_focused = (flags & SDL_WINDOW_INPUT_FOCUS) != 0;
321
322 SDL_GetWindowSize(window_.get(), &status.width, &status.height);
323 }
324
325 return status;
326}
327
328void SDL2WindowBackend::GetSize(int* width, int* height) const {
329 if (window_) {
330 SDL_GetWindowSize(window_.get(), width, height);
331 } else {
332 if (width) *width = 0;
333 if (height) *height = 0;
334 }
335}
336
337void SDL2WindowBackend::SetSize(int width, int height) {
338 if (window_) {
339 SDL_SetWindowSize(window_.get(), width, height);
340 }
341}
342
343std::string SDL2WindowBackend::GetTitle() const {
344 if (window_) {
345 const char* title = SDL_GetWindowTitle(window_.get());
346 return title ? title : "";
347 }
348 return "";
349}
350
351void SDL2WindowBackend::SetTitle(const std::string& title) {
352 if (window_) {
353 SDL_SetWindowTitle(window_.get(), title.c_str());
354 }
355}
356
358 if (!window_ || !renderer) {
359 return false;
360 }
361
362 if (renderer->GetBackendRenderer()) {
363 // Already initialized
364 return true;
365 }
366
367 return renderer->Initialize(window_.get());
368}
369
371 if (imgui_initialized_) {
372 return absl::OkStatus();
373 }
374
375 if (!renderer) {
376 return absl::InvalidArgumentError("Renderer is null");
377 }
378
379 IMGUI_CHECKVERSION();
380 ImGui::CreateContext();
381
382 ImGuiIO& io = ImGui::GetIO();
383 io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
384 io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
385 // Note: ViewportsEnable is intentionally NOT set for SDL2 + SDL_Renderer
386 // It causes scaling issues on macOS Retina displays
387
388 // Ensure macOS-style behavior (Cmd acts as Ctrl for shortcuts)
389 // ImGui should set this automatically based on __APPLE__, but force it to be safe
390#ifdef __APPLE__
391 io.ConfigMacOSXBehaviors = true;
392 LOG_INFO("SDL2WindowBackend", "Enabled ConfigMacOSXBehaviors for macOS");
393#endif
394
395 // Initialize ImGui backends
396 SDL_Renderer* sdl_renderer =
397 static_cast<SDL_Renderer*>(renderer->GetBackendRenderer());
398
399 if (!sdl_renderer) {
400 return absl::InternalError("Failed to get SDL renderer from IRenderer");
401 }
402
403 if (!ImGui_ImplSDL2_InitForSDLRenderer(window_.get(), sdl_renderer)) {
404 return absl::InternalError("ImGui_ImplSDL2_InitForSDLRenderer failed");
405 }
406
407 if (!ImGui_ImplSDLRenderer2_Init(sdl_renderer)) {
408 ImGui_ImplSDL2_Shutdown();
409 return absl::InternalError("ImGui_ImplSDLRenderer2_Init failed");
410 }
411
412 // Load fonts
414
415 // Apply default style
417
418 imgui_initialized_ = true;
419 LOG_INFO("SDL2WindowBackend", "ImGui initialized successfully");
420 return absl::OkStatus();
421}
422
424 if (!imgui_initialized_) {
425 return;
426 }
427
428 LOG_INFO("SDL2WindowBackend", "Shutting down ImGui implementations...");
429 ImGui_ImplSDLRenderer2_Shutdown();
430 ImGui_ImplSDL2_Shutdown();
431
432 LOG_INFO("SDL2WindowBackend", "Destroying ImGui context...");
433 ImGui::DestroyContext();
434
435 imgui_initialized_ = false;
436}
437
439 if (!imgui_initialized_) {
440 return;
441 }
442
443 ImGui_ImplSDLRenderer2_NewFrame();
444 ImGui_ImplSDL2_NewFrame();
445 // ImGui_ImplSDL2_NewFrame() automatically handles DisplaySize and
446 // DisplayFramebufferScale via ImGui_ImplSDL2_GetWindowSizeAndFramebufferScale()
447 // which uses SDL_GetRendererOutputSize() when renderer is available.
448}
449
451 if (!imgui_initialized_) {
452 return;
453 }
454
455 // Finalize ImGui frame and render draw data
456 ImGui::Render();
457
458 if (renderer) {
459 SDL_Renderer* sdl_renderer =
460 static_cast<SDL_Renderer*>(renderer->GetBackendRenderer());
461 if (sdl_renderer) {
462 ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(), sdl_renderer);
463 }
464 }
465
466 // Multi-viewport support
467 ImGuiIO& io = ImGui::GetIO();
468 if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
469 ImGui::UpdatePlatformWindows();
470 ImGui::RenderPlatformWindowsDefault();
471 }
472}
473
474} // namespace platform
475} // namespace yaze
void Shutdown()
Definition arena.cc:294
static Arena & Get()
Definition arena.cc:19
Defines an abstract interface for all rendering operations.
Definition irenderer.h:40
virtual bool Initialize(SDL_Window *window)=0
Initializes the renderer with a given window.
virtual void * GetBackendRenderer()=0
Provides an escape hatch to get the underlying, concrete renderer object.
absl::Status InitializeImGui(gfx::IRenderer *renderer) override
Initialize ImGui backends for this window/renderer combo.
void SetTitle(const std::string &title) override
Set window title.
absl::Status Shutdown() override
Shutdown the window backend and release resources.
bool PollEvent(WindowEvent &out_event) override
Poll and process pending events.
WindowStatus GetStatus() const override
Get current window status.
void GetSize(int *width, int *height) const override
Get window dimensions.
void ProcessNativeEvent(void *native_event) override
Process a native SDL event (for ImGui integration)
bool InitializeRenderer(gfx::IRenderer *renderer) override
Initialize renderer for this window.
void SetSize(int width, int height) override
Set window dimensions.
std::shared_ptr< int16_t > audio_buffer_
absl::Status Initialize(const WindowConfig &config) override
Initialize the window backend with configuration.
void ShutdownImGui() override
Shutdown ImGui backends.
void RenderImGui(gfx::IRenderer *renderer) override
Render ImGui draw data (and viewports if enabled)
WindowEvent ConvertSDL2Event(const SDL_Event &sdl_event)
std::unique_ptr< SDL_Window, util::SDL_Deleter > window_
void NewImGuiFrame() override
Start a new ImGui frame.
std::string GetTitle() const override
Get window title.
#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
SDL2/SDL3 compatibility layer.
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
Window configuration parameters.
Definition iwindow.h:24
Platform-agnostic window event data.
Definition iwindow.h:63
WindowEventType type
Definition iwindow.h:64
Window backend status information.
Definition iwindow.h:96
Deleter for SDL_Window and SDL_Renderer.
Definition sdl_deleter.h:19