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