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", "Initialized: %dx%d, audio buffer: %zu samples",
104 screen_width, screen_height, buffer_size);
105
106 initialized_ = true;
107 active_ = true;
108 return absl::OkStatus();
109}
110
112 if (!initialized_) {
113 return absl::OkStatus();
114 }
115
116 // Pause and close audio device if open
117 if (audio_device_ != 0) {
118 SDL_PauseAudioDevice(audio_device_, 1);
119 SDL_CloseAudioDevice(audio_device_);
120 audio_device_ = 0;
121 }
122
123 // Shutdown ImGui if initialized
124 if (imgui_initialized_) {
126 }
127
128 // Shutdown graphics arena while renderer is still valid
129 LOG_INFO("SDL2WindowBackend", "Shutting down graphics arena...");
131
132 // Destroy window
133 if (window_) {
134 LOG_INFO("SDL2WindowBackend", "Destroying window...");
135 window_.reset();
136 }
137
138 // Quit SDL
139 LOG_INFO("SDL2WindowBackend", "Shutting down SDL...");
140 SDL_Quit();
141
142 initialized_ = false;
143 LOG_INFO("SDL2WindowBackend", "Shutdown complete");
144 return absl::OkStatus();
145}
146
148 SDL_Event sdl_event;
149 if (SDL_PollEvent(&sdl_event)) {
150 // Let ImGui process the event first
151 if (imgui_initialized_) {
152 ImGui_ImplSDL2_ProcessEvent(&sdl_event);
153 }
154
155 // Convert to platform-agnostic event
156 out_event = ConvertSDL2Event(sdl_event);
157 out_event.has_native_event = true;
158 out_event.native_event = sdl_event;
159 return true;
160 }
161 return false;
162}
163
165 if (native_event && imgui_initialized_) {
166 ImGui_ImplSDL2_ProcessEvent(static_cast<SDL_Event*>(native_event));
167 }
168}
169
171 WindowEvent event;
173
174 switch (sdl_event.type) {
175 case SDL_QUIT:
176 event.type = WindowEventType::Quit;
177 active_ = false;
178 break;
179
180 case SDL_KEYDOWN:
181 event.type = WindowEventType::KeyDown;
182 event.key_code = sdl_event.key.keysym.sym;
183 event.scan_code = sdl_event.key.keysym.scancode;
185 event.key_shift = key_shift_;
186 event.key_ctrl = key_ctrl_;
187 event.key_alt = key_alt_;
188 event.key_super = key_super_;
189 break;
190
191 case SDL_KEYUP:
192 event.type = WindowEventType::KeyUp;
193 event.key_code = sdl_event.key.keysym.sym;
194 event.scan_code = sdl_event.key.keysym.scancode;
196 event.key_shift = key_shift_;
197 event.key_ctrl = key_ctrl_;
198 event.key_alt = key_alt_;
199 event.key_super = key_super_;
200 break;
201
202 case SDL_MOUSEMOTION:
203 event.type = WindowEventType::MouseMotion;
204 event.mouse_x = static_cast<float>(sdl_event.motion.x);
205 event.mouse_y = static_cast<float>(sdl_event.motion.y);
206 break;
207
208 case SDL_MOUSEBUTTONDOWN:
210 event.mouse_x = static_cast<float>(sdl_event.button.x);
211 event.mouse_y = static_cast<float>(sdl_event.button.y);
212 event.mouse_button = sdl_event.button.button;
213 break;
214
215 case SDL_MOUSEBUTTONUP:
217 event.mouse_x = static_cast<float>(sdl_event.button.x);
218 event.mouse_y = static_cast<float>(sdl_event.button.y);
219 event.mouse_button = sdl_event.button.button;
220 break;
221
222 case SDL_MOUSEWHEEL:
223 event.type = WindowEventType::MouseWheel;
224 event.wheel_x = static_cast<float>(sdl_event.wheel.x);
225 event.wheel_y = static_cast<float>(sdl_event.wheel.y);
226 break;
227
228 case SDL_DROPFILE:
229 event.type = WindowEventType::DropFile;
230 if (sdl_event.drop.file) {
231 event.dropped_file = sdl_event.drop.file;
232 SDL_free(sdl_event.drop.file);
233 }
234 break;
235
236 case SDL_WINDOWEVENT:
237 switch (sdl_event.window.event) {
238 case SDL_WINDOWEVENT_CLOSE:
239 event.type = WindowEventType::Close;
240 active_ = false;
241 break;
242
243 case SDL_WINDOWEVENT_SIZE_CHANGED:
244 case SDL_WINDOWEVENT_RESIZED:
245 event.type = WindowEventType::Resize;
246 event.window_width = sdl_event.window.data1;
247 event.window_height = sdl_event.window.data2;
248 is_resizing_ = true;
250 break;
251
252 case SDL_WINDOWEVENT_MINIMIZED:
253 event.type = WindowEventType::Minimized;
254 is_resizing_ = false;
255 g_window_is_resizing = false;
256 break;
257
258 case SDL_WINDOWEVENT_MAXIMIZED:
259 event.type = WindowEventType::Maximized;
260 break;
261
262 case SDL_WINDOWEVENT_RESTORED:
263 event.type = WindowEventType::Restored;
264 is_resizing_ = false;
265 g_window_is_resizing = false;
266 break;
267
268 case SDL_WINDOWEVENT_SHOWN:
269 event.type = WindowEventType::Shown;
270 is_resizing_ = false;
271 g_window_is_resizing = false;
272 break;
273
274 case SDL_WINDOWEVENT_HIDDEN:
275 event.type = WindowEventType::Hidden;
276 is_resizing_ = false;
277 g_window_is_resizing = false;
278 break;
279
280 case SDL_WINDOWEVENT_EXPOSED:
281 event.type = WindowEventType::Exposed;
282 is_resizing_ = false;
283 g_window_is_resizing = false;
284 break;
285
286 case SDL_WINDOWEVENT_FOCUS_GAINED:
287 event.type = WindowEventType::FocusGained;
288 break;
289
290 case SDL_WINDOWEVENT_FOCUS_LOST:
291 event.type = WindowEventType::FocusLost;
292 break;
293 }
294 break;
295 }
296
297 return event;
298}
299
301 SDL_Keymod mod = SDL_GetModState();
302 key_shift_ = (mod & KMOD_SHIFT) != 0;
303 key_ctrl_ = (mod & KMOD_CTRL) != 0;
304 key_alt_ = (mod & KMOD_ALT) != 0;
305 key_super_ = (mod & KMOD_GUI) != 0;
306}
307
309 WindowStatus status;
310 status.is_active = active_;
311 status.is_resizing = is_resizing_;
312
313 if (window_) {
314 Uint32 flags = SDL_GetWindowFlags(window_.get());
315 status.is_minimized = (flags & SDL_WINDOW_MINIMIZED) != 0;
316 status.is_maximized = (flags & SDL_WINDOW_MAXIMIZED) != 0;
317 status.is_fullscreen =
318 (flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_FULLSCREEN_DESKTOP)) != 0;
319 status.is_focused = (flags & SDL_WINDOW_INPUT_FOCUS) != 0;
320
321 SDL_GetWindowSize(window_.get(), &status.width, &status.height);
322 }
323
324 return status;
325}
326
327void SDL2WindowBackend::GetSize(int* width, int* height) const {
328 if (window_) {
329 SDL_GetWindowSize(window_.get(), width, height);
330 } else {
331 if (width)
332 *width = 0;
333 if (height)
334 *height = 0;
335 }
336}
337
338void SDL2WindowBackend::SetSize(int width, int height) {
339 if (window_) {
340 SDL_SetWindowSize(window_.get(), width, height);
341 }
342}
343
344std::string SDL2WindowBackend::GetTitle() const {
345 if (window_) {
346 const char* title = SDL_GetWindowTitle(window_.get());
347 return title ? title : "";
348 }
349 return "";
350}
351
352void SDL2WindowBackend::SetTitle(const std::string& title) {
353 if (window_) {
354 SDL_SetWindowTitle(window_.get(), title.c_str());
355 }
356}
357
359 if (!window_ || !renderer) {
360 return false;
361 }
362
363 if (renderer->GetBackendRenderer()) {
364 // Already initialized
365 return true;
366 }
367
368 return renderer->Initialize(window_.get());
369}
370
372 if (imgui_initialized_) {
373 return absl::OkStatus();
374 }
375
376 if (!renderer) {
377 return absl::InvalidArgumentError("Renderer is null");
378 }
379
380 IMGUI_CHECKVERSION();
381 ImGui::CreateContext();
382
383 ImGuiIO& io = ImGui::GetIO();
384 io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
385 io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
386 // Note: ViewportsEnable is intentionally NOT set for SDL2 + SDL_Renderer
387 // It causes scaling issues on macOS Retina displays
388
389 // Ensure macOS-style behavior (Cmd acts as Ctrl for shortcuts)
390 // ImGui should set this automatically based on __APPLE__, but force it to be safe
391#ifdef __APPLE__
392 io.ConfigMacOSXBehaviors = true;
393 LOG_INFO("SDL2WindowBackend", "Enabled ConfigMacOSXBehaviors for macOS");
394#endif
395
396 // Initialize ImGui backends
397 SDL_Renderer* sdl_renderer =
398 static_cast<SDL_Renderer*>(renderer->GetBackendRenderer());
399
400 if (!sdl_renderer) {
401 return absl::InternalError("Failed to get SDL renderer from IRenderer");
402 }
403
404 if (!ImGui_ImplSDL2_InitForSDLRenderer(window_.get(), sdl_renderer)) {
405 return absl::InternalError("ImGui_ImplSDL2_InitForSDLRenderer failed");
406 }
407
408 if (!ImGui_ImplSDLRenderer2_Init(sdl_renderer)) {
409 ImGui_ImplSDL2_Shutdown();
410 return absl::InternalError("ImGui_ImplSDLRenderer2_Init failed");
411 }
412
413 // Load fonts
415
416 // Apply default style
418
419 imgui_initialized_ = true;
420 LOG_INFO("SDL2WindowBackend", "ImGui initialized successfully");
421 return absl::OkStatus();
422}
423
425 if (!imgui_initialized_) {
426 return;
427 }
428
429 LOG_INFO("SDL2WindowBackend", "Shutting down ImGui implementations...");
430 ImGui_ImplSDLRenderer2_Shutdown();
431 ImGui_ImplSDL2_Shutdown();
432
433 LOG_INFO("SDL2WindowBackend", "Destroying ImGui context...");
434 ImGui::DestroyContext();
435
436 imgui_initialized_ = false;
437}
438
440 if (!imgui_initialized_) {
441 return;
442 }
443
444 ImGui_ImplSDLRenderer2_NewFrame();
445 ImGui_ImplSDL2_NewFrame();
446 // ImGui_ImplSDL2_NewFrame() automatically handles DisplaySize and
447 // DisplayFramebufferScale via ImGui_ImplSDL2_GetWindowSizeAndFramebufferScale()
448 // which uses SDL_GetRendererOutputSize() when renderer is available.
449}
450
452 if (!imgui_initialized_) {
453 return;
454 }
455
456 // Finalize ImGui frame and render draw data
457 ImGui::Render();
458
459 if (renderer) {
460 SDL_Renderer* sdl_renderer =
461 static_cast<SDL_Renderer*>(renderer->GetBackendRenderer());
462 if (sdl_renderer) {
463 ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(), sdl_renderer);
464 }
465 }
466
467 // Multi-viewport support
468 ImGuiIO& io = ImGui::GetIO();
469 if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
470 ImGui::UpdatePlatformWindows();
471 ImGui::RenderPlatformWindowsDefault();
472 }
473}
474
475} // namespace platform
476} // namespace yaze
void Shutdown()
Definition arena.cc:304
static Arena & Get()
Definition arena.cc:20
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