backend-infra-engineer: Release v0.3.9-hotfix7 snapshot
This commit is contained in:
293
src/app/platform/iwindow.h
Normal file
293
src/app/platform/iwindow.h
Normal file
@@ -0,0 +1,293 @@
|
||||
// iwindow.h - Window Backend Abstraction Layer
|
||||
// Provides interface for swapping window implementations (SDL2, SDL3)
|
||||
|
||||
#ifndef YAZE_APP_PLATFORM_IWINDOW_H_
|
||||
#define YAZE_APP_PLATFORM_IWINDOW_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/gfx/backend/irenderer.h"
|
||||
|
||||
// Forward declarations to avoid SDL header dependency in interface
|
||||
struct SDL_Window;
|
||||
|
||||
namespace yaze {
|
||||
namespace platform {
|
||||
|
||||
/**
|
||||
* @brief Window configuration parameters
|
||||
*/
|
||||
struct WindowConfig {
|
||||
std::string title = "Yet Another Zelda3 Editor";
|
||||
int width = 0; // 0 means auto-detect from display
|
||||
int height = 0; // 0 means auto-detect from display
|
||||
float display_scale = 0.8f; // Percentage of display to use when auto-detect
|
||||
bool resizable = true;
|
||||
bool maximized = false;
|
||||
bool fullscreen = false;
|
||||
bool high_dpi = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Window event types (platform-agnostic)
|
||||
*/
|
||||
enum class WindowEventType {
|
||||
None,
|
||||
Close,
|
||||
Resize,
|
||||
Minimized,
|
||||
Maximized,
|
||||
Restored,
|
||||
Shown,
|
||||
Hidden,
|
||||
Exposed,
|
||||
FocusGained,
|
||||
FocusLost,
|
||||
KeyDown,
|
||||
KeyUp,
|
||||
MouseMotion,
|
||||
MouseButtonDown,
|
||||
MouseButtonUp,
|
||||
MouseWheel,
|
||||
Quit,
|
||||
DropFile
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Platform-agnostic window event data
|
||||
*/
|
||||
struct WindowEvent {
|
||||
WindowEventType type = WindowEventType::None;
|
||||
|
||||
// Window resize data
|
||||
int window_width = 0;
|
||||
int window_height = 0;
|
||||
|
||||
// Keyboard data
|
||||
int key_code = 0;
|
||||
int scan_code = 0;
|
||||
bool key_shift = false;
|
||||
bool key_ctrl = false;
|
||||
bool key_alt = false;
|
||||
bool key_super = false;
|
||||
|
||||
// Mouse data
|
||||
float mouse_x = 0.0f;
|
||||
float mouse_y = 0.0f;
|
||||
int mouse_button = 0;
|
||||
float wheel_x = 0.0f;
|
||||
float wheel_y = 0.0f;
|
||||
|
||||
// Drop file data
|
||||
std::string dropped_file;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Window backend status information
|
||||
*/
|
||||
struct WindowStatus {
|
||||
bool is_active = true;
|
||||
bool is_minimized = false;
|
||||
bool is_maximized = false;
|
||||
bool is_fullscreen = false;
|
||||
bool is_focused = true;
|
||||
bool is_resizing = false;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Abstract window backend interface
|
||||
*
|
||||
* Provides platform-agnostic window management, allowing different
|
||||
* SDL versions or other windowing libraries to be swapped without
|
||||
* changing application code.
|
||||
*/
|
||||
class IWindowBackend {
|
||||
public:
|
||||
virtual ~IWindowBackend() = default;
|
||||
|
||||
// =========================================================================
|
||||
// Lifecycle Management
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* @brief Initialize the window backend with configuration
|
||||
* @param config Window configuration parameters
|
||||
* @return Status indicating success or failure
|
||||
*/
|
||||
virtual absl::Status Initialize(const WindowConfig& config) = 0;
|
||||
|
||||
/**
|
||||
* @brief Shutdown the window backend and release resources
|
||||
* @return Status indicating success or failure
|
||||
*/
|
||||
virtual absl::Status Shutdown() = 0;
|
||||
|
||||
/**
|
||||
* @brief Check if the backend is initialized
|
||||
*/
|
||||
virtual bool IsInitialized() const = 0;
|
||||
|
||||
// =========================================================================
|
||||
// Event Processing
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* @brief Poll and process pending events
|
||||
* @param out_event Output parameter for the next event
|
||||
* @return True if an event was available, false otherwise
|
||||
*/
|
||||
virtual bool PollEvent(WindowEvent& out_event) = 0;
|
||||
|
||||
/**
|
||||
* @brief Process a native SDL event (for ImGui integration)
|
||||
* @param native_event Pointer to native SDL_Event
|
||||
*/
|
||||
virtual void ProcessNativeEvent(void* native_event) = 0;
|
||||
|
||||
// =========================================================================
|
||||
// Window State
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* @brief Get current window status
|
||||
*/
|
||||
virtual WindowStatus GetStatus() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Check if window is still active (not closed)
|
||||
*/
|
||||
virtual bool IsActive() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Set window active state
|
||||
*/
|
||||
virtual void SetActive(bool active) = 0;
|
||||
|
||||
/**
|
||||
* @brief Get window dimensions
|
||||
*/
|
||||
virtual void GetSize(int* width, int* height) const = 0;
|
||||
|
||||
/**
|
||||
* @brief Set window dimensions
|
||||
*/
|
||||
virtual void SetSize(int width, int height) = 0;
|
||||
|
||||
/**
|
||||
* @brief Get window title
|
||||
*/
|
||||
virtual std::string GetTitle() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Set window title
|
||||
*/
|
||||
virtual void SetTitle(const std::string& title) = 0;
|
||||
|
||||
// =========================================================================
|
||||
// Renderer Integration
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* @brief Initialize renderer for this window
|
||||
* @param renderer The renderer to initialize
|
||||
* @return True if successful
|
||||
*/
|
||||
virtual bool InitializeRenderer(gfx::IRenderer* renderer) = 0;
|
||||
|
||||
/**
|
||||
* @brief Get the underlying SDL_Window pointer for ImGui integration
|
||||
* @return Native window handle (SDL_Window*)
|
||||
*/
|
||||
virtual SDL_Window* GetNativeWindow() = 0;
|
||||
|
||||
// =========================================================================
|
||||
// ImGui Integration
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* @brief Initialize ImGui backends for this window/renderer combo
|
||||
* @param renderer The renderer (for backend-specific init)
|
||||
* @return Status indicating success or failure
|
||||
*/
|
||||
virtual absl::Status InitializeImGui(gfx::IRenderer* renderer) = 0;
|
||||
|
||||
/**
|
||||
* @brief Shutdown ImGui backends
|
||||
*/
|
||||
virtual void ShutdownImGui() = 0;
|
||||
|
||||
/**
|
||||
* @brief Start a new ImGui frame
|
||||
*/
|
||||
virtual void NewImGuiFrame() = 0;
|
||||
|
||||
// =========================================================================
|
||||
// Audio Support (Legacy compatibility)
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* @brief Get audio device ID (for legacy audio buffer management)
|
||||
*/
|
||||
virtual uint32_t GetAudioDevice() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Get audio buffer (for legacy audio management)
|
||||
*/
|
||||
virtual std::shared_ptr<int16_t> GetAudioBuffer() const = 0;
|
||||
|
||||
// =========================================================================
|
||||
// Backend Information
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* @brief Get backend name for debugging/logging
|
||||
*/
|
||||
virtual std::string GetBackendName() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Get SDL version being used
|
||||
*/
|
||||
virtual int GetSDLVersion() const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Backend type enumeration for factory
|
||||
*/
|
||||
enum class WindowBackendType {
|
||||
SDL2,
|
||||
SDL3,
|
||||
Auto // Automatically select based on availability
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Factory for creating window backends
|
||||
*/
|
||||
class WindowBackendFactory {
|
||||
public:
|
||||
/**
|
||||
* @brief Create a window backend of the specified type
|
||||
* @param type The type of backend to create
|
||||
* @return Unique pointer to the created backend
|
||||
*/
|
||||
static std::unique_ptr<IWindowBackend> Create(WindowBackendType type);
|
||||
|
||||
/**
|
||||
* @brief Get the default backend type for this build
|
||||
*/
|
||||
static WindowBackendType GetDefaultType();
|
||||
|
||||
/**
|
||||
* @brief Check if a backend type is available
|
||||
*/
|
||||
static bool IsAvailable(WindowBackendType type);
|
||||
};
|
||||
|
||||
} // namespace platform
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_PLATFORM_IWINDOW_H_
|
||||
435
src/app/platform/sdl2_window_backend.cc
Normal file
435
src/app/platform/sdl2_window_backend.cc
Normal file
@@ -0,0 +1,435 @@
|
||||
// sdl2_window_backend.cc - SDL2 Window Backend Implementation
|
||||
|
||||
#include "app/platform/sdl2_window_backend.h"
|
||||
|
||||
#include "app/platform/sdl_compat.h"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "app/gfx/resource/arena.h"
|
||||
#include "app/gui/core/style.h"
|
||||
#include "app/platform/font_loader.h"
|
||||
#include "imgui/backends/imgui_impl_sdl2.h"
|
||||
#include "imgui/backends/imgui_impl_sdlrenderer2.h"
|
||||
#include "imgui/imgui.h"
|
||||
#include "util/log.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace platform {
|
||||
|
||||
// Global flag for window resize state (used by emulator to pause)
|
||||
// This maintains compatibility with the legacy window.cc
|
||||
extern bool g_window_is_resizing;
|
||||
|
||||
SDL2WindowBackend::~SDL2WindowBackend() {
|
||||
if (initialized_) {
|
||||
Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
absl::Status SDL2WindowBackend::Initialize(const WindowConfig& config) {
|
||||
if (initialized_) {
|
||||
LOG_WARN("SDL2WindowBackend", "Already initialized, shutting down first");
|
||||
RETURN_IF_ERROR(Shutdown());
|
||||
}
|
||||
|
||||
// Initialize SDL2 subsystems
|
||||
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER) != 0) {
|
||||
return absl::InternalError(
|
||||
absl::StrFormat("SDL_Init failed: %s", SDL_GetError()));
|
||||
}
|
||||
|
||||
// Determine window size
|
||||
int screen_width = config.width;
|
||||
int screen_height = config.height;
|
||||
|
||||
if (screen_width == 0 || screen_height == 0) {
|
||||
// Auto-detect from display
|
||||
SDL_DisplayMode display_mode;
|
||||
if (SDL_GetCurrentDisplayMode(0, &display_mode) == 0) {
|
||||
screen_width = static_cast<int>(display_mode.w * config.display_scale);
|
||||
screen_height = static_cast<int>(display_mode.h * config.display_scale);
|
||||
} else {
|
||||
// Fallback to reasonable defaults
|
||||
screen_width = 1280;
|
||||
screen_height = 720;
|
||||
LOG_WARN("SDL2WindowBackend",
|
||||
"Failed to get display mode, using defaults: %dx%d",
|
||||
screen_width, screen_height);
|
||||
}
|
||||
}
|
||||
|
||||
// Build window flags
|
||||
Uint32 flags = 0;
|
||||
if (config.resizable) {
|
||||
flags |= SDL_WINDOW_RESIZABLE;
|
||||
}
|
||||
if (config.maximized) {
|
||||
flags |= SDL_WINDOW_MAXIMIZED;
|
||||
}
|
||||
if (config.fullscreen) {
|
||||
flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||
}
|
||||
if (config.high_dpi) {
|
||||
flags |= SDL_WINDOW_ALLOW_HIGHDPI;
|
||||
}
|
||||
|
||||
// Create window
|
||||
window_ = std::unique_ptr<SDL_Window, util::SDL_Deleter>(
|
||||
SDL_CreateWindow(config.title.c_str(), SDL_WINDOWPOS_UNDEFINED,
|
||||
SDL_WINDOWPOS_UNDEFINED, screen_width, screen_height,
|
||||
flags),
|
||||
util::SDL_Deleter());
|
||||
|
||||
if (!window_) {
|
||||
SDL_Quit();
|
||||
return absl::InternalError(
|
||||
absl::StrFormat("SDL_CreateWindow failed: %s", SDL_GetError()));
|
||||
}
|
||||
|
||||
// Allocate legacy audio buffer for backwards compatibility
|
||||
const int audio_frequency = 48000;
|
||||
const size_t buffer_size = (audio_frequency / 50) * 2; // Stereo PAL
|
||||
audio_buffer_ = std::shared_ptr<int16_t>(new int16_t[buffer_size],
|
||||
std::default_delete<int16_t[]>());
|
||||
|
||||
LOG_INFO("SDL2WindowBackend",
|
||||
"Initialized: %dx%d, audio buffer: %zu samples", screen_width,
|
||||
screen_height, buffer_size);
|
||||
|
||||
initialized_ = true;
|
||||
active_ = true;
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status SDL2WindowBackend::Shutdown() {
|
||||
if (!initialized_) {
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
// Pause and close audio device if open
|
||||
if (audio_device_ != 0) {
|
||||
SDL_PauseAudioDevice(audio_device_, 1);
|
||||
SDL_CloseAudioDevice(audio_device_);
|
||||
audio_device_ = 0;
|
||||
}
|
||||
|
||||
// Shutdown ImGui if initialized
|
||||
if (imgui_initialized_) {
|
||||
ShutdownImGui();
|
||||
}
|
||||
|
||||
// Shutdown graphics arena while renderer is still valid
|
||||
LOG_INFO("SDL2WindowBackend", "Shutting down graphics arena...");
|
||||
gfx::Arena::Get().Shutdown();
|
||||
|
||||
// Destroy window
|
||||
if (window_) {
|
||||
LOG_INFO("SDL2WindowBackend", "Destroying window...");
|
||||
window_.reset();
|
||||
}
|
||||
|
||||
// Quit SDL
|
||||
LOG_INFO("SDL2WindowBackend", "Shutting down SDL...");
|
||||
SDL_Quit();
|
||||
|
||||
initialized_ = false;
|
||||
LOG_INFO("SDL2WindowBackend", "Shutdown complete");
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
bool SDL2WindowBackend::PollEvent(WindowEvent& out_event) {
|
||||
SDL_Event sdl_event;
|
||||
if (SDL_PollEvent(&sdl_event)) {
|
||||
// Let ImGui process the event first
|
||||
if (imgui_initialized_) {
|
||||
ImGui_ImplSDL2_ProcessEvent(&sdl_event);
|
||||
}
|
||||
|
||||
// Convert to platform-agnostic event
|
||||
out_event = ConvertSDL2Event(sdl_event);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SDL2WindowBackend::ProcessNativeEvent(void* native_event) {
|
||||
if (native_event && imgui_initialized_) {
|
||||
ImGui_ImplSDL2_ProcessEvent(static_cast<SDL_Event*>(native_event));
|
||||
}
|
||||
}
|
||||
|
||||
WindowEvent SDL2WindowBackend::ConvertSDL2Event(const SDL_Event& sdl_event) {
|
||||
WindowEvent event;
|
||||
event.type = WindowEventType::None;
|
||||
|
||||
switch (sdl_event.type) {
|
||||
case SDL_QUIT:
|
||||
event.type = WindowEventType::Quit;
|
||||
active_ = false;
|
||||
break;
|
||||
|
||||
case SDL_KEYDOWN:
|
||||
event.type = WindowEventType::KeyDown;
|
||||
event.key_code = sdl_event.key.keysym.sym;
|
||||
event.scan_code = sdl_event.key.keysym.scancode;
|
||||
UpdateModifierState();
|
||||
event.key_shift = key_shift_;
|
||||
event.key_ctrl = key_ctrl_;
|
||||
event.key_alt = key_alt_;
|
||||
event.key_super = key_super_;
|
||||
break;
|
||||
|
||||
case SDL_KEYUP:
|
||||
event.type = WindowEventType::KeyUp;
|
||||
event.key_code = sdl_event.key.keysym.sym;
|
||||
event.scan_code = sdl_event.key.keysym.scancode;
|
||||
UpdateModifierState();
|
||||
event.key_shift = key_shift_;
|
||||
event.key_ctrl = key_ctrl_;
|
||||
event.key_alt = key_alt_;
|
||||
event.key_super = key_super_;
|
||||
break;
|
||||
|
||||
case SDL_MOUSEMOTION:
|
||||
event.type = WindowEventType::MouseMotion;
|
||||
event.mouse_x = static_cast<float>(sdl_event.motion.x);
|
||||
event.mouse_y = static_cast<float>(sdl_event.motion.y);
|
||||
break;
|
||||
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
event.type = WindowEventType::MouseButtonDown;
|
||||
event.mouse_x = static_cast<float>(sdl_event.button.x);
|
||||
event.mouse_y = static_cast<float>(sdl_event.button.y);
|
||||
event.mouse_button = sdl_event.button.button;
|
||||
break;
|
||||
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
event.type = WindowEventType::MouseButtonUp;
|
||||
event.mouse_x = static_cast<float>(sdl_event.button.x);
|
||||
event.mouse_y = static_cast<float>(sdl_event.button.y);
|
||||
event.mouse_button = sdl_event.button.button;
|
||||
break;
|
||||
|
||||
case SDL_MOUSEWHEEL:
|
||||
event.type = WindowEventType::MouseWheel;
|
||||
event.wheel_x = static_cast<float>(sdl_event.wheel.x);
|
||||
event.wheel_y = static_cast<float>(sdl_event.wheel.y);
|
||||
break;
|
||||
|
||||
case SDL_DROPFILE:
|
||||
event.type = WindowEventType::DropFile;
|
||||
if (sdl_event.drop.file) {
|
||||
event.dropped_file = sdl_event.drop.file;
|
||||
SDL_free(sdl_event.drop.file);
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT:
|
||||
switch (sdl_event.window.event) {
|
||||
case SDL_WINDOWEVENT_CLOSE:
|
||||
event.type = WindowEventType::Close;
|
||||
active_ = false;
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||
case SDL_WINDOWEVENT_RESIZED:
|
||||
event.type = WindowEventType::Resize;
|
||||
event.window_width = sdl_event.window.data1;
|
||||
event.window_height = sdl_event.window.data2;
|
||||
is_resizing_ = true;
|
||||
g_window_is_resizing = true;
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_MINIMIZED:
|
||||
event.type = WindowEventType::Minimized;
|
||||
is_resizing_ = false;
|
||||
g_window_is_resizing = false;
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_MAXIMIZED:
|
||||
event.type = WindowEventType::Maximized;
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_RESTORED:
|
||||
event.type = WindowEventType::Restored;
|
||||
is_resizing_ = false;
|
||||
g_window_is_resizing = false;
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_SHOWN:
|
||||
event.type = WindowEventType::Shown;
|
||||
is_resizing_ = false;
|
||||
g_window_is_resizing = false;
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_HIDDEN:
|
||||
event.type = WindowEventType::Hidden;
|
||||
is_resizing_ = false;
|
||||
g_window_is_resizing = false;
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_EXPOSED:
|
||||
event.type = WindowEventType::Exposed;
|
||||
is_resizing_ = false;
|
||||
g_window_is_resizing = false;
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_FOCUS_GAINED:
|
||||
event.type = WindowEventType::FocusGained;
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_FOCUS_LOST:
|
||||
event.type = WindowEventType::FocusLost;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
void SDL2WindowBackend::UpdateModifierState() {
|
||||
SDL_Keymod mod = SDL_GetModState();
|
||||
key_shift_ = (mod & KMOD_SHIFT) != 0;
|
||||
key_ctrl_ = (mod & KMOD_CTRL) != 0;
|
||||
key_alt_ = (mod & KMOD_ALT) != 0;
|
||||
key_super_ = (mod & KMOD_GUI) != 0;
|
||||
}
|
||||
|
||||
WindowStatus SDL2WindowBackend::GetStatus() const {
|
||||
WindowStatus status;
|
||||
status.is_active = active_;
|
||||
status.is_resizing = is_resizing_;
|
||||
|
||||
if (window_) {
|
||||
Uint32 flags = SDL_GetWindowFlags(window_.get());
|
||||
status.is_minimized = (flags & SDL_WINDOW_MINIMIZED) != 0;
|
||||
status.is_maximized = (flags & SDL_WINDOW_MAXIMIZED) != 0;
|
||||
status.is_fullscreen =
|
||||
(flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_FULLSCREEN_DESKTOP)) != 0;
|
||||
status.is_focused = (flags & SDL_WINDOW_INPUT_FOCUS) != 0;
|
||||
|
||||
SDL_GetWindowSize(window_.get(), &status.width, &status.height);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
void SDL2WindowBackend::GetSize(int* width, int* height) const {
|
||||
if (window_) {
|
||||
SDL_GetWindowSize(window_.get(), width, height);
|
||||
} else {
|
||||
if (width) *width = 0;
|
||||
if (height) *height = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void SDL2WindowBackend::SetSize(int width, int height) {
|
||||
if (window_) {
|
||||
SDL_SetWindowSize(window_.get(), width, height);
|
||||
}
|
||||
}
|
||||
|
||||
std::string SDL2WindowBackend::GetTitle() const {
|
||||
if (window_) {
|
||||
const char* title = SDL_GetWindowTitle(window_.get());
|
||||
return title ? title : "";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
void SDL2WindowBackend::SetTitle(const std::string& title) {
|
||||
if (window_) {
|
||||
SDL_SetWindowTitle(window_.get(), title.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
bool SDL2WindowBackend::InitializeRenderer(gfx::IRenderer* renderer) {
|
||||
if (!window_ || !renderer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (renderer->GetBackendRenderer()) {
|
||||
// Already initialized
|
||||
return true;
|
||||
}
|
||||
|
||||
return renderer->Initialize(window_.get());
|
||||
}
|
||||
|
||||
absl::Status SDL2WindowBackend::InitializeImGui(gfx::IRenderer* renderer) {
|
||||
if (imgui_initialized_) {
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
if (!renderer) {
|
||||
return absl::InvalidArgumentError("Renderer is null");
|
||||
}
|
||||
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
||||
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
||||
|
||||
// Initialize ImGui backends
|
||||
SDL_Renderer* sdl_renderer =
|
||||
static_cast<SDL_Renderer*>(renderer->GetBackendRenderer());
|
||||
|
||||
if (!sdl_renderer) {
|
||||
return absl::InternalError("Failed to get SDL renderer from IRenderer");
|
||||
}
|
||||
|
||||
if (!ImGui_ImplSDL2_InitForSDLRenderer(window_.get(), sdl_renderer)) {
|
||||
return absl::InternalError("ImGui_ImplSDL2_InitForSDLRenderer failed");
|
||||
}
|
||||
|
||||
if (!ImGui_ImplSDLRenderer2_Init(sdl_renderer)) {
|
||||
ImGui_ImplSDL2_Shutdown();
|
||||
return absl::InternalError("ImGui_ImplSDLRenderer2_Init failed");
|
||||
}
|
||||
|
||||
// Load fonts
|
||||
RETURN_IF_ERROR(LoadPackageFonts());
|
||||
|
||||
// Apply default style
|
||||
gui::ColorsYaze();
|
||||
|
||||
imgui_initialized_ = true;
|
||||
LOG_INFO("SDL2WindowBackend", "ImGui initialized successfully");
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
void SDL2WindowBackend::ShutdownImGui() {
|
||||
if (!imgui_initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO("SDL2WindowBackend", "Shutting down ImGui implementations...");
|
||||
ImGui_ImplSDLRenderer2_Shutdown();
|
||||
ImGui_ImplSDL2_Shutdown();
|
||||
|
||||
LOG_INFO("SDL2WindowBackend", "Destroying ImGui context...");
|
||||
ImGui::DestroyContext();
|
||||
|
||||
imgui_initialized_ = false;
|
||||
}
|
||||
|
||||
void SDL2WindowBackend::NewImGuiFrame() {
|
||||
if (!imgui_initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui_ImplSDLRenderer2_NewFrame();
|
||||
ImGui_ImplSDL2_NewFrame();
|
||||
}
|
||||
|
||||
// Define the global variable for backward compatibility
|
||||
bool g_window_is_resizing = false;
|
||||
|
||||
} // namespace platform
|
||||
} // namespace yaze
|
||||
91
src/app/platform/sdl2_window_backend.h
Normal file
91
src/app/platform/sdl2_window_backend.h
Normal file
@@ -0,0 +1,91 @@
|
||||
// sdl2_window_backend.h - SDL2 Window Backend Implementation
|
||||
|
||||
#ifndef YAZE_APP_PLATFORM_SDL2_WINDOW_BACKEND_H_
|
||||
#define YAZE_APP_PLATFORM_SDL2_WINDOW_BACKEND_H_
|
||||
|
||||
#include "app/platform/sdl_compat.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/platform/iwindow.h"
|
||||
#include "util/sdl_deleter.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace platform {
|
||||
|
||||
/**
|
||||
* @brief SDL2 implementation of the window backend interface
|
||||
*
|
||||
* Wraps SDL2 window management, event handling, and ImGui integration
|
||||
* for the main YAZE application window.
|
||||
*/
|
||||
class SDL2WindowBackend : public IWindowBackend {
|
||||
public:
|
||||
SDL2WindowBackend() = default;
|
||||
~SDL2WindowBackend() override;
|
||||
|
||||
// =========================================================================
|
||||
// IWindowBackend Implementation
|
||||
// =========================================================================
|
||||
|
||||
absl::Status Initialize(const WindowConfig& config) override;
|
||||
absl::Status Shutdown() override;
|
||||
bool IsInitialized() const override { return initialized_; }
|
||||
|
||||
bool PollEvent(WindowEvent& out_event) override;
|
||||
void ProcessNativeEvent(void* native_event) override;
|
||||
|
||||
WindowStatus GetStatus() const override;
|
||||
bool IsActive() const override { return active_; }
|
||||
void SetActive(bool active) override { active_ = active; }
|
||||
|
||||
void GetSize(int* width, int* height) const override;
|
||||
void SetSize(int width, int height) override;
|
||||
std::string GetTitle() const override;
|
||||
void SetTitle(const std::string& title) override;
|
||||
|
||||
bool InitializeRenderer(gfx::IRenderer* renderer) override;
|
||||
SDL_Window* GetNativeWindow() override { return window_.get(); }
|
||||
|
||||
absl::Status InitializeImGui(gfx::IRenderer* renderer) override;
|
||||
void ShutdownImGui() override;
|
||||
void NewImGuiFrame() override;
|
||||
|
||||
uint32_t GetAudioDevice() const override { return audio_device_; }
|
||||
std::shared_ptr<int16_t> GetAudioBuffer() const override {
|
||||
return audio_buffer_;
|
||||
}
|
||||
|
||||
std::string GetBackendName() const override { return "SDL2"; }
|
||||
int GetSDLVersion() const override { return 2; }
|
||||
|
||||
private:
|
||||
// Convert SDL2 event to platform-agnostic WindowEvent
|
||||
WindowEvent ConvertSDL2Event(const SDL_Event& sdl_event);
|
||||
|
||||
// Update modifier key state from SDL
|
||||
void UpdateModifierState();
|
||||
|
||||
std::unique_ptr<SDL_Window, util::SDL_Deleter> window_;
|
||||
bool initialized_ = false;
|
||||
bool active_ = true;
|
||||
bool is_resizing_ = false;
|
||||
bool imgui_initialized_ = false;
|
||||
|
||||
// Modifier key state
|
||||
bool key_shift_ = false;
|
||||
bool key_ctrl_ = false;
|
||||
bool key_alt_ = false;
|
||||
bool key_super_ = false;
|
||||
|
||||
// Legacy audio support
|
||||
SDL_AudioDeviceID audio_device_ = 0;
|
||||
std::shared_ptr<int16_t> audio_buffer_;
|
||||
};
|
||||
|
||||
} // namespace platform
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_PLATFORM_SDL2_WINDOW_BACKEND_H_
|
||||
456
src/app/platform/sdl3_window_backend.cc
Normal file
456
src/app/platform/sdl3_window_backend.cc
Normal file
@@ -0,0 +1,456 @@
|
||||
// sdl3_window_backend.cc - SDL3 Window Backend Implementation
|
||||
|
||||
// Only compile SDL3 backend when YAZE_USE_SDL3 is defined
|
||||
#ifdef YAZE_USE_SDL3
|
||||
|
||||
#include "app/platform/sdl3_window_backend.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "app/gfx/resource/arena.h"
|
||||
#include "app/gui/core/style.h"
|
||||
#include "app/platform/font_loader.h"
|
||||
#include "imgui/backends/imgui_impl_sdl3.h"
|
||||
#include "imgui/backends/imgui_impl_sdlrenderer3.h"
|
||||
#include "imgui/imgui.h"
|
||||
#include "util/log.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace platform {
|
||||
|
||||
// Global flag for window resize state (used by emulator to pause)
|
||||
extern bool g_window_is_resizing;
|
||||
|
||||
SDL3WindowBackend::~SDL3WindowBackend() {
|
||||
if (initialized_) {
|
||||
Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
absl::Status SDL3WindowBackend::Initialize(const WindowConfig& config) {
|
||||
if (initialized_) {
|
||||
LOG_WARN("SDL3WindowBackend", "Already initialized, shutting down first");
|
||||
RETURN_IF_ERROR(Shutdown());
|
||||
}
|
||||
|
||||
// Initialize SDL3 subsystems
|
||||
// Note: SDL3 removed SDL_INIT_TIMER (timer is always available)
|
||||
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_EVENTS)) {
|
||||
return absl::InternalError(
|
||||
absl::StrFormat("SDL_Init failed: %s", SDL_GetError()));
|
||||
}
|
||||
|
||||
// Determine window size
|
||||
int screen_width = config.width;
|
||||
int screen_height = config.height;
|
||||
|
||||
if (screen_width == 0 || screen_height == 0) {
|
||||
// Auto-detect from display
|
||||
// SDL3 uses SDL_GetPrimaryDisplay() and SDL_GetCurrentDisplayMode()
|
||||
SDL_DisplayID display_id = SDL_GetPrimaryDisplay();
|
||||
const SDL_DisplayMode* mode = SDL_GetCurrentDisplayMode(display_id);
|
||||
|
||||
if (mode) {
|
||||
screen_width = static_cast<int>(mode->w * config.display_scale);
|
||||
screen_height = static_cast<int>(mode->h * config.display_scale);
|
||||
} else {
|
||||
// Fallback to reasonable defaults
|
||||
screen_width = 1280;
|
||||
screen_height = 720;
|
||||
LOG_WARN("SDL3WindowBackend",
|
||||
"Failed to get display mode, using defaults: %dx%d",
|
||||
screen_width, screen_height);
|
||||
}
|
||||
}
|
||||
|
||||
// Build window flags
|
||||
// Note: SDL3 changed some flag names
|
||||
SDL_WindowFlags flags = 0;
|
||||
if (config.resizable) {
|
||||
flags |= SDL_WINDOW_RESIZABLE;
|
||||
}
|
||||
if (config.maximized) {
|
||||
flags |= SDL_WINDOW_MAXIMIZED;
|
||||
}
|
||||
if (config.fullscreen) {
|
||||
flags |= SDL_WINDOW_FULLSCREEN;
|
||||
}
|
||||
if (config.high_dpi) {
|
||||
flags |= SDL_WINDOW_HIGH_PIXEL_DENSITY;
|
||||
}
|
||||
|
||||
// Create window
|
||||
// Note: SDL3 uses SDL_CreateWindow with different signature
|
||||
SDL_Window* raw_window =
|
||||
SDL_CreateWindow(config.title.c_str(), screen_width, screen_height, flags);
|
||||
|
||||
if (!raw_window) {
|
||||
SDL_Quit();
|
||||
return absl::InternalError(
|
||||
absl::StrFormat("SDL_CreateWindow failed: %s", SDL_GetError()));
|
||||
}
|
||||
|
||||
window_ = std::unique_ptr<SDL_Window, SDL3WindowDeleter>(raw_window);
|
||||
|
||||
// Allocate legacy audio buffer for backwards compatibility
|
||||
const int audio_frequency = 48000;
|
||||
const size_t buffer_size = (audio_frequency / 50) * 2; // Stereo PAL
|
||||
audio_buffer_ = std::shared_ptr<int16_t>(new int16_t[buffer_size],
|
||||
std::default_delete<int16_t[]>());
|
||||
|
||||
LOG_INFO("SDL3WindowBackend",
|
||||
"Initialized: %dx%d, audio buffer: %zu samples", screen_width,
|
||||
screen_height, buffer_size);
|
||||
|
||||
initialized_ = true;
|
||||
active_ = true;
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status SDL3WindowBackend::Shutdown() {
|
||||
if (!initialized_) {
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
// Shutdown ImGui if initialized
|
||||
if (imgui_initialized_) {
|
||||
ShutdownImGui();
|
||||
}
|
||||
|
||||
// Shutdown graphics arena while renderer is still valid
|
||||
LOG_INFO("SDL3WindowBackend", "Shutting down graphics arena...");
|
||||
gfx::Arena::Get().Shutdown();
|
||||
|
||||
// Destroy window
|
||||
if (window_) {
|
||||
LOG_INFO("SDL3WindowBackend", "Destroying window...");
|
||||
window_.reset();
|
||||
}
|
||||
|
||||
// Quit SDL
|
||||
LOG_INFO("SDL3WindowBackend", "Shutting down SDL...");
|
||||
SDL_Quit();
|
||||
|
||||
initialized_ = false;
|
||||
LOG_INFO("SDL3WindowBackend", "Shutdown complete");
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
bool SDL3WindowBackend::PollEvent(WindowEvent& out_event) {
|
||||
SDL_Event sdl_event;
|
||||
if (SDL_PollEvent(&sdl_event)) {
|
||||
// Let ImGui process the event first
|
||||
if (imgui_initialized_) {
|
||||
ImGui_ImplSDL3_ProcessEvent(&sdl_event);
|
||||
}
|
||||
|
||||
// Convert to platform-agnostic event
|
||||
out_event = ConvertSDL3Event(sdl_event);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SDL3WindowBackend::ProcessNativeEvent(void* native_event) {
|
||||
if (native_event && imgui_initialized_) {
|
||||
ImGui_ImplSDL3_ProcessEvent(static_cast<SDL_Event*>(native_event));
|
||||
}
|
||||
}
|
||||
|
||||
WindowEvent SDL3WindowBackend::ConvertSDL3Event(const SDL_Event& sdl_event) {
|
||||
WindowEvent event;
|
||||
event.type = WindowEventType::None;
|
||||
|
||||
switch (sdl_event.type) {
|
||||
// =========================================================================
|
||||
// Application Events
|
||||
// =========================================================================
|
||||
case SDL_EVENT_QUIT:
|
||||
event.type = WindowEventType::Quit;
|
||||
active_ = false;
|
||||
break;
|
||||
|
||||
// =========================================================================
|
||||
// Keyboard Events
|
||||
// Note: SDL3 uses event.key.key instead of event.key.keysym.sym
|
||||
// =========================================================================
|
||||
case SDL_EVENT_KEY_DOWN:
|
||||
event.type = WindowEventType::KeyDown;
|
||||
event.key_code = sdl_event.key.key;
|
||||
event.scan_code = sdl_event.key.scancode;
|
||||
UpdateModifierState();
|
||||
event.key_shift = key_shift_;
|
||||
event.key_ctrl = key_ctrl_;
|
||||
event.key_alt = key_alt_;
|
||||
event.key_super = key_super_;
|
||||
break;
|
||||
|
||||
case SDL_EVENT_KEY_UP:
|
||||
event.type = WindowEventType::KeyUp;
|
||||
event.key_code = sdl_event.key.key;
|
||||
event.scan_code = sdl_event.key.scancode;
|
||||
UpdateModifierState();
|
||||
event.key_shift = key_shift_;
|
||||
event.key_ctrl = key_ctrl_;
|
||||
event.key_alt = key_alt_;
|
||||
event.key_super = key_super_;
|
||||
break;
|
||||
|
||||
// =========================================================================
|
||||
// Mouse Events
|
||||
// Note: SDL3 uses float coordinates
|
||||
// =========================================================================
|
||||
case SDL_EVENT_MOUSE_MOTION:
|
||||
event.type = WindowEventType::MouseMotion;
|
||||
event.mouse_x = sdl_event.motion.x;
|
||||
event.mouse_y = sdl_event.motion.y;
|
||||
break;
|
||||
|
||||
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
||||
event.type = WindowEventType::MouseButtonDown;
|
||||
event.mouse_x = sdl_event.button.x;
|
||||
event.mouse_y = sdl_event.button.y;
|
||||
event.mouse_button = sdl_event.button.button;
|
||||
break;
|
||||
|
||||
case SDL_EVENT_MOUSE_BUTTON_UP:
|
||||
event.type = WindowEventType::MouseButtonUp;
|
||||
event.mouse_x = sdl_event.button.x;
|
||||
event.mouse_y = sdl_event.button.y;
|
||||
event.mouse_button = sdl_event.button.button;
|
||||
break;
|
||||
|
||||
case SDL_EVENT_MOUSE_WHEEL:
|
||||
event.type = WindowEventType::MouseWheel;
|
||||
event.wheel_x = sdl_event.wheel.x;
|
||||
event.wheel_y = sdl_event.wheel.y;
|
||||
break;
|
||||
|
||||
// =========================================================================
|
||||
// Drop Events
|
||||
// =========================================================================
|
||||
case SDL_EVENT_DROP_FILE:
|
||||
event.type = WindowEventType::DropFile;
|
||||
if (sdl_event.drop.data) {
|
||||
event.dropped_file = sdl_event.drop.data;
|
||||
// Note: SDL3 drop.data is managed by SDL, don't free it
|
||||
}
|
||||
break;
|
||||
|
||||
// =========================================================================
|
||||
// Window Events - SDL3 Major Change
|
||||
// SDL3 no longer uses SDL_WINDOWEVENT with sub-types.
|
||||
// Each window event type is now a top-level event.
|
||||
// =========================================================================
|
||||
case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
|
||||
event.type = WindowEventType::Close;
|
||||
active_ = false;
|
||||
break;
|
||||
|
||||
case SDL_EVENT_WINDOW_RESIZED:
|
||||
event.type = WindowEventType::Resize;
|
||||
event.window_width = sdl_event.window.data1;
|
||||
event.window_height = sdl_event.window.data2;
|
||||
is_resizing_ = true;
|
||||
g_window_is_resizing = true;
|
||||
break;
|
||||
|
||||
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
|
||||
// This is the SDL3 equivalent of SDL_WINDOWEVENT_SIZE_CHANGED
|
||||
event.type = WindowEventType::Resize;
|
||||
event.window_width = sdl_event.window.data1;
|
||||
event.window_height = sdl_event.window.data2;
|
||||
is_resizing_ = true;
|
||||
g_window_is_resizing = true;
|
||||
break;
|
||||
|
||||
case SDL_EVENT_WINDOW_MINIMIZED:
|
||||
event.type = WindowEventType::Minimized;
|
||||
is_resizing_ = false;
|
||||
g_window_is_resizing = false;
|
||||
break;
|
||||
|
||||
case SDL_EVENT_WINDOW_MAXIMIZED:
|
||||
event.type = WindowEventType::Maximized;
|
||||
break;
|
||||
|
||||
case SDL_EVENT_WINDOW_RESTORED:
|
||||
event.type = WindowEventType::Restored;
|
||||
is_resizing_ = false;
|
||||
g_window_is_resizing = false;
|
||||
break;
|
||||
|
||||
case SDL_EVENT_WINDOW_SHOWN:
|
||||
event.type = WindowEventType::Shown;
|
||||
is_resizing_ = false;
|
||||
g_window_is_resizing = false;
|
||||
break;
|
||||
|
||||
case SDL_EVENT_WINDOW_HIDDEN:
|
||||
event.type = WindowEventType::Hidden;
|
||||
is_resizing_ = false;
|
||||
g_window_is_resizing = false;
|
||||
break;
|
||||
|
||||
case SDL_EVENT_WINDOW_EXPOSED:
|
||||
event.type = WindowEventType::Exposed;
|
||||
is_resizing_ = false;
|
||||
g_window_is_resizing = false;
|
||||
break;
|
||||
|
||||
case SDL_EVENT_WINDOW_FOCUS_GAINED:
|
||||
event.type = WindowEventType::FocusGained;
|
||||
break;
|
||||
|
||||
case SDL_EVENT_WINDOW_FOCUS_LOST:
|
||||
event.type = WindowEventType::FocusLost;
|
||||
break;
|
||||
}
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
void SDL3WindowBackend::UpdateModifierState() {
|
||||
// SDL3 uses SDL_GetModState which returns SDL_Keymod
|
||||
SDL_Keymod mod = SDL_GetModState();
|
||||
key_shift_ = (mod & SDL_KMOD_SHIFT) != 0;
|
||||
key_ctrl_ = (mod & SDL_KMOD_CTRL) != 0;
|
||||
key_alt_ = (mod & SDL_KMOD_ALT) != 0;
|
||||
key_super_ = (mod & SDL_KMOD_GUI) != 0;
|
||||
}
|
||||
|
||||
WindowStatus SDL3WindowBackend::GetStatus() const {
|
||||
WindowStatus status;
|
||||
status.is_active = active_;
|
||||
status.is_resizing = is_resizing_;
|
||||
|
||||
if (window_) {
|
||||
SDL_WindowFlags flags = SDL_GetWindowFlags(window_.get());
|
||||
status.is_minimized = (flags & SDL_WINDOW_MINIMIZED) != 0;
|
||||
status.is_maximized = (flags & SDL_WINDOW_MAXIMIZED) != 0;
|
||||
status.is_fullscreen = (flags & SDL_WINDOW_FULLSCREEN) != 0;
|
||||
status.is_focused = (flags & SDL_WINDOW_INPUT_FOCUS) != 0;
|
||||
|
||||
SDL_GetWindowSize(window_.get(), &status.width, &status.height);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
void SDL3WindowBackend::GetSize(int* width, int* height) const {
|
||||
if (window_) {
|
||||
SDL_GetWindowSize(window_.get(), width, height);
|
||||
} else {
|
||||
if (width) *width = 0;
|
||||
if (height) *height = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void SDL3WindowBackend::SetSize(int width, int height) {
|
||||
if (window_) {
|
||||
SDL_SetWindowSize(window_.get(), width, height);
|
||||
}
|
||||
}
|
||||
|
||||
std::string SDL3WindowBackend::GetTitle() const {
|
||||
if (window_) {
|
||||
const char* title = SDL_GetWindowTitle(window_.get());
|
||||
return title ? title : "";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
void SDL3WindowBackend::SetTitle(const std::string& title) {
|
||||
if (window_) {
|
||||
SDL_SetWindowTitle(window_.get(), title.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
bool SDL3WindowBackend::InitializeRenderer(gfx::IRenderer* renderer) {
|
||||
if (!window_ || !renderer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (renderer->GetBackendRenderer()) {
|
||||
// Already initialized
|
||||
return true;
|
||||
}
|
||||
|
||||
return renderer->Initialize(window_.get());
|
||||
}
|
||||
|
||||
absl::Status SDL3WindowBackend::InitializeImGui(gfx::IRenderer* renderer) {
|
||||
if (imgui_initialized_) {
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
if (!renderer) {
|
||||
return absl::InvalidArgumentError("Renderer is null");
|
||||
}
|
||||
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
||||
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
||||
|
||||
// Initialize ImGui backends for SDL3
|
||||
SDL_Renderer* sdl_renderer =
|
||||
static_cast<SDL_Renderer*>(renderer->GetBackendRenderer());
|
||||
|
||||
if (!sdl_renderer) {
|
||||
return absl::InternalError("Failed to get SDL renderer from IRenderer");
|
||||
}
|
||||
|
||||
// Note: SDL3 uses different ImGui backend functions
|
||||
if (!ImGui_ImplSDL3_InitForSDLRenderer(window_.get(), sdl_renderer)) {
|
||||
return absl::InternalError("ImGui_ImplSDL3_InitForSDLRenderer failed");
|
||||
}
|
||||
|
||||
if (!ImGui_ImplSDLRenderer3_Init(sdl_renderer)) {
|
||||
ImGui_ImplSDL3_Shutdown();
|
||||
return absl::InternalError("ImGui_ImplSDLRenderer3_Init failed");
|
||||
}
|
||||
|
||||
// Load fonts
|
||||
RETURN_IF_ERROR(LoadPackageFonts());
|
||||
|
||||
// Apply default style
|
||||
gui::ColorsYaze();
|
||||
|
||||
imgui_initialized_ = true;
|
||||
LOG_INFO("SDL3WindowBackend", "ImGui initialized successfully");
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
void SDL3WindowBackend::ShutdownImGui() {
|
||||
if (!imgui_initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO("SDL3WindowBackend", "Shutting down ImGui implementations...");
|
||||
ImGui_ImplSDLRenderer3_Shutdown();
|
||||
ImGui_ImplSDL3_Shutdown();
|
||||
|
||||
LOG_INFO("SDL3WindowBackend", "Destroying ImGui context...");
|
||||
ImGui::DestroyContext();
|
||||
|
||||
imgui_initialized_ = false;
|
||||
}
|
||||
|
||||
void SDL3WindowBackend::NewImGuiFrame() {
|
||||
if (!imgui_initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui_ImplSDLRenderer3_NewFrame();
|
||||
ImGui_ImplSDL3_NewFrame();
|
||||
}
|
||||
|
||||
} // namespace platform
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_USE_SDL3
|
||||
103
src/app/platform/sdl3_window_backend.h
Normal file
103
src/app/platform/sdl3_window_backend.h
Normal file
@@ -0,0 +1,103 @@
|
||||
// sdl3_window_backend.h - SDL3 Window Backend Implementation
|
||||
|
||||
#ifndef YAZE_APP_PLATFORM_SDL3_WINDOW_BACKEND_H_
|
||||
#define YAZE_APP_PLATFORM_SDL3_WINDOW_BACKEND_H_
|
||||
|
||||
// Only compile SDL3 backend when YAZE_USE_SDL3 is defined
|
||||
#ifdef YAZE_USE_SDL3
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/platform/iwindow.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace platform {
|
||||
|
||||
// Forward declaration for unique_ptr custom deleter
|
||||
struct SDL3WindowDeleter {
|
||||
void operator()(SDL_Window* p) const {
|
||||
if (p) SDL_DestroyWindow(p);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief SDL3 implementation of the window backend interface
|
||||
*
|
||||
* Handles the significant event handling changes in SDL3:
|
||||
* - Individual window events instead of SDL_WINDOWEVENT
|
||||
* - SDL_EVENT_* naming convention
|
||||
* - event.key.key instead of event.key.keysym.sym
|
||||
* - bool* keyboard state instead of Uint8*
|
||||
*/
|
||||
class SDL3WindowBackend : public IWindowBackend {
|
||||
public:
|
||||
SDL3WindowBackend() = default;
|
||||
~SDL3WindowBackend() override;
|
||||
|
||||
// =========================================================================
|
||||
// IWindowBackend Implementation
|
||||
// =========================================================================
|
||||
|
||||
absl::Status Initialize(const WindowConfig& config) override;
|
||||
absl::Status Shutdown() override;
|
||||
bool IsInitialized() const override { return initialized_; }
|
||||
|
||||
bool PollEvent(WindowEvent& out_event) override;
|
||||
void ProcessNativeEvent(void* native_event) override;
|
||||
|
||||
WindowStatus GetStatus() const override;
|
||||
bool IsActive() const override { return active_; }
|
||||
void SetActive(bool active) override { active_ = active; }
|
||||
|
||||
void GetSize(int* width, int* height) const override;
|
||||
void SetSize(int width, int height) override;
|
||||
std::string GetTitle() const override;
|
||||
void SetTitle(const std::string& title) override;
|
||||
|
||||
bool InitializeRenderer(gfx::IRenderer* renderer) override;
|
||||
SDL_Window* GetNativeWindow() override { return window_.get(); }
|
||||
|
||||
absl::Status InitializeImGui(gfx::IRenderer* renderer) override;
|
||||
void ShutdownImGui() override;
|
||||
void NewImGuiFrame() override;
|
||||
|
||||
uint32_t GetAudioDevice() const override { return 0; } // SDL3 uses streams
|
||||
std::shared_ptr<int16_t> GetAudioBuffer() const override {
|
||||
return audio_buffer_;
|
||||
}
|
||||
|
||||
std::string GetBackendName() const override { return "SDL3"; }
|
||||
int GetSDLVersion() const override { return 3; }
|
||||
|
||||
private:
|
||||
// Convert SDL3 event to platform-agnostic WindowEvent
|
||||
WindowEvent ConvertSDL3Event(const SDL_Event& sdl_event);
|
||||
|
||||
// Update modifier key state from SDL3
|
||||
void UpdateModifierState();
|
||||
|
||||
std::unique_ptr<SDL_Window, SDL3WindowDeleter> window_;
|
||||
bool initialized_ = false;
|
||||
bool active_ = true;
|
||||
bool is_resizing_ = false;
|
||||
bool imgui_initialized_ = false;
|
||||
|
||||
// Modifier key state
|
||||
bool key_shift_ = false;
|
||||
bool key_ctrl_ = false;
|
||||
bool key_alt_ = false;
|
||||
bool key_super_ = false;
|
||||
|
||||
// Legacy audio buffer for compatibility
|
||||
std::shared_ptr<int16_t> audio_buffer_;
|
||||
};
|
||||
|
||||
} // namespace platform
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_USE_SDL3
|
||||
#endif // YAZE_APP_PLATFORM_SDL3_WINDOW_BACKEND_H_
|
||||
510
src/app/platform/sdl_compat.h
Normal file
510
src/app/platform/sdl_compat.h
Normal file
@@ -0,0 +1,510 @@
|
||||
#ifndef YAZE_APP_PLATFORM_SDL_COMPAT_H_
|
||||
#define YAZE_APP_PLATFORM_SDL_COMPAT_H_
|
||||
|
||||
/**
|
||||
* @file sdl_compat.h
|
||||
* @brief SDL2/SDL3 compatibility layer
|
||||
*
|
||||
* This header provides cross-version compatibility between SDL2 and SDL3.
|
||||
* It defines type aliases, macros, and wrapper functions that allow
|
||||
* application code to work with both versions.
|
||||
*/
|
||||
|
||||
#ifdef YAZE_USE_SDL3
|
||||
#include <SDL3/SDL.h>
|
||||
#else
|
||||
#include <SDL.h>
|
||||
#endif
|
||||
|
||||
namespace yaze {
|
||||
namespace platform {
|
||||
|
||||
// ============================================================================
|
||||
// Type Aliases
|
||||
// ============================================================================
|
||||
|
||||
#ifdef YAZE_USE_SDL3
|
||||
// SDL3 uses bool* for keyboard state
|
||||
using KeyboardState = const bool*;
|
||||
#else
|
||||
// SDL2 uses Uint8* for keyboard state
|
||||
using KeyboardState = const Uint8*;
|
||||
#endif
|
||||
|
||||
// ============================================================================
|
||||
// Event Type Constants
|
||||
// ============================================================================
|
||||
|
||||
#ifdef YAZE_USE_SDL3
|
||||
constexpr auto kEventKeyDown = SDL_EVENT_KEY_DOWN;
|
||||
constexpr auto kEventKeyUp = SDL_EVENT_KEY_UP;
|
||||
constexpr auto kEventMouseMotion = SDL_EVENT_MOUSE_MOTION;
|
||||
constexpr auto kEventMouseButtonDown = SDL_EVENT_MOUSE_BUTTON_DOWN;
|
||||
constexpr auto kEventMouseButtonUp = SDL_EVENT_MOUSE_BUTTON_UP;
|
||||
constexpr auto kEventMouseWheel = SDL_EVENT_MOUSE_WHEEL;
|
||||
constexpr auto kEventQuit = SDL_EVENT_QUIT;
|
||||
constexpr auto kEventDropFile = SDL_EVENT_DROP_FILE;
|
||||
constexpr auto kEventWindowCloseRequested = SDL_EVENT_WINDOW_CLOSE_REQUESTED;
|
||||
constexpr auto kEventWindowResized = SDL_EVENT_WINDOW_RESIZED;
|
||||
constexpr auto kEventGamepadAdded = SDL_EVENT_GAMEPAD_ADDED;
|
||||
constexpr auto kEventGamepadRemoved = SDL_EVENT_GAMEPAD_REMOVED;
|
||||
#else
|
||||
constexpr auto kEventKeyDown = SDL_KEYDOWN;
|
||||
constexpr auto kEventKeyUp = SDL_KEYUP;
|
||||
constexpr auto kEventMouseMotion = SDL_MOUSEMOTION;
|
||||
constexpr auto kEventMouseButtonDown = SDL_MOUSEBUTTONDOWN;
|
||||
constexpr auto kEventMouseButtonUp = SDL_MOUSEBUTTONUP;
|
||||
constexpr auto kEventMouseWheel = SDL_MOUSEWHEEL;
|
||||
constexpr auto kEventQuit = SDL_QUIT;
|
||||
constexpr auto kEventDropFile = SDL_DROPFILE;
|
||||
// SDL2 uses SDL_WINDOWEVENT with sub-types, not individual events
|
||||
// These are handled specially in window code
|
||||
constexpr auto kEventWindowEvent = SDL_WINDOWEVENT;
|
||||
constexpr auto kEventControllerDeviceAdded = SDL_CONTROLLERDEVICEADDED;
|
||||
constexpr auto kEventControllerDeviceRemoved = SDL_CONTROLLERDEVICEREMOVED;
|
||||
#endif
|
||||
|
||||
// ============================================================================
|
||||
// Keyboard Helpers
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief Get keyboard state from SDL event
|
||||
* @param event The SDL event
|
||||
* @return The keycode from the event
|
||||
*/
|
||||
inline SDL_Keycode GetKeyFromEvent(const SDL_Event& event) {
|
||||
#ifdef YAZE_USE_SDL3
|
||||
return event.key.key;
|
||||
#else
|
||||
return event.key.keysym.sym;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if a key is pressed using the keyboard state
|
||||
* @param state The keyboard state from SDL_GetKeyboardState
|
||||
* @param scancode The scancode to check
|
||||
* @return True if the key is pressed
|
||||
*/
|
||||
inline bool IsKeyPressed(KeyboardState state, SDL_Scancode scancode) {
|
||||
#ifdef YAZE_USE_SDL3
|
||||
// SDL3 returns bool*
|
||||
return state[scancode];
|
||||
#else
|
||||
// SDL2 returns Uint8*, non-zero means pressed
|
||||
return state[scancode] != 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Gamepad/Controller Helpers
|
||||
// ============================================================================
|
||||
|
||||
#ifdef YAZE_USE_SDL3
|
||||
// SDL3 uses SDL_Gamepad instead of SDL_GameController
|
||||
using GamepadHandle = SDL_Gamepad*;
|
||||
|
||||
inline GamepadHandle OpenGamepad(int index) {
|
||||
SDL_JoystickID* joysticks = SDL_GetGamepads(nullptr);
|
||||
if (joysticks && index < 4) {
|
||||
SDL_JoystickID id = joysticks[index];
|
||||
SDL_free(joysticks);
|
||||
return SDL_OpenGamepad(id);
|
||||
}
|
||||
if (joysticks) SDL_free(joysticks);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
inline void CloseGamepad(GamepadHandle gamepad) {
|
||||
if (gamepad) SDL_CloseGamepad(gamepad);
|
||||
}
|
||||
|
||||
inline bool GetGamepadButton(GamepadHandle gamepad, SDL_GamepadButton button) {
|
||||
return SDL_GetGamepadButton(gamepad, button);
|
||||
}
|
||||
|
||||
inline int16_t GetGamepadAxis(GamepadHandle gamepad, SDL_GamepadAxis axis) {
|
||||
return SDL_GetGamepadAxis(gamepad, axis);
|
||||
}
|
||||
|
||||
inline bool IsGamepadConnected(int index) {
|
||||
int count = 0;
|
||||
SDL_JoystickID* joysticks = SDL_GetGamepads(&count);
|
||||
if (joysticks) {
|
||||
SDL_free(joysticks);
|
||||
}
|
||||
return index < count;
|
||||
}
|
||||
#else
|
||||
// SDL2 uses SDL_GameController
|
||||
using GamepadHandle = SDL_GameController*;
|
||||
|
||||
inline GamepadHandle OpenGamepad(int index) {
|
||||
if (SDL_IsGameController(index)) {
|
||||
return SDL_GameControllerOpen(index);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
inline void CloseGamepad(GamepadHandle gamepad) {
|
||||
if (gamepad) SDL_GameControllerClose(gamepad);
|
||||
}
|
||||
|
||||
inline bool GetGamepadButton(GamepadHandle gamepad,
|
||||
SDL_GameControllerButton button) {
|
||||
return SDL_GameControllerGetButton(gamepad, button) != 0;
|
||||
}
|
||||
|
||||
inline int16_t GetGamepadAxis(GamepadHandle gamepad,
|
||||
SDL_GameControllerAxis axis) {
|
||||
return SDL_GameControllerGetAxis(gamepad, axis);
|
||||
}
|
||||
|
||||
inline bool IsGamepadConnected(int index) {
|
||||
return SDL_IsGameController(index);
|
||||
}
|
||||
#endif
|
||||
|
||||
// ============================================================================
|
||||
// Button/Axis Type Aliases
|
||||
// ============================================================================
|
||||
|
||||
#ifdef YAZE_USE_SDL3
|
||||
using GamepadButton = SDL_GamepadButton;
|
||||
using GamepadAxis = SDL_GamepadAxis;
|
||||
|
||||
constexpr auto kGamepadButtonA = SDL_GAMEPAD_BUTTON_SOUTH;
|
||||
constexpr auto kGamepadButtonB = SDL_GAMEPAD_BUTTON_EAST;
|
||||
constexpr auto kGamepadButtonX = SDL_GAMEPAD_BUTTON_WEST;
|
||||
constexpr auto kGamepadButtonY = SDL_GAMEPAD_BUTTON_NORTH;
|
||||
constexpr auto kGamepadButtonBack = SDL_GAMEPAD_BUTTON_BACK;
|
||||
constexpr auto kGamepadButtonStart = SDL_GAMEPAD_BUTTON_START;
|
||||
constexpr auto kGamepadButtonLeftShoulder = SDL_GAMEPAD_BUTTON_LEFT_SHOULDER;
|
||||
constexpr auto kGamepadButtonRightShoulder = SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER;
|
||||
constexpr auto kGamepadButtonDpadUp = SDL_GAMEPAD_BUTTON_DPAD_UP;
|
||||
constexpr auto kGamepadButtonDpadDown = SDL_GAMEPAD_BUTTON_DPAD_DOWN;
|
||||
constexpr auto kGamepadButtonDpadLeft = SDL_GAMEPAD_BUTTON_DPAD_LEFT;
|
||||
constexpr auto kGamepadButtonDpadRight = SDL_GAMEPAD_BUTTON_DPAD_RIGHT;
|
||||
|
||||
constexpr auto kGamepadAxisLeftX = SDL_GAMEPAD_AXIS_LEFTX;
|
||||
constexpr auto kGamepadAxisLeftY = SDL_GAMEPAD_AXIS_LEFTY;
|
||||
#else
|
||||
using GamepadButton = SDL_GameControllerButton;
|
||||
using GamepadAxis = SDL_GameControllerAxis;
|
||||
|
||||
constexpr auto kGamepadButtonA = SDL_CONTROLLER_BUTTON_A;
|
||||
constexpr auto kGamepadButtonB = SDL_CONTROLLER_BUTTON_B;
|
||||
constexpr auto kGamepadButtonX = SDL_CONTROLLER_BUTTON_X;
|
||||
constexpr auto kGamepadButtonY = SDL_CONTROLLER_BUTTON_Y;
|
||||
constexpr auto kGamepadButtonBack = SDL_CONTROLLER_BUTTON_BACK;
|
||||
constexpr auto kGamepadButtonStart = SDL_CONTROLLER_BUTTON_START;
|
||||
constexpr auto kGamepadButtonLeftShoulder = SDL_CONTROLLER_BUTTON_LEFTSHOULDER;
|
||||
constexpr auto kGamepadButtonRightShoulder = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER;
|
||||
constexpr auto kGamepadButtonDpadUp = SDL_CONTROLLER_BUTTON_DPAD_UP;
|
||||
constexpr auto kGamepadButtonDpadDown = SDL_CONTROLLER_BUTTON_DPAD_DOWN;
|
||||
constexpr auto kGamepadButtonDpadLeft = SDL_CONTROLLER_BUTTON_DPAD_LEFT;
|
||||
constexpr auto kGamepadButtonDpadRight = SDL_CONTROLLER_BUTTON_DPAD_RIGHT;
|
||||
|
||||
constexpr auto kGamepadAxisLeftX = SDL_CONTROLLER_AXIS_LEFTX;
|
||||
constexpr auto kGamepadAxisLeftY = SDL_CONTROLLER_AXIS_LEFTY;
|
||||
#endif
|
||||
|
||||
// ============================================================================
|
||||
// Renderer Helpers
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief Create a renderer with default settings.
|
||||
*
|
||||
* SDL2: SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED)
|
||||
* SDL3: SDL_CreateRenderer(window, nullptr)
|
||||
*/
|
||||
inline SDL_Renderer* CreateRenderer(SDL_Window* window) {
|
||||
#ifdef YAZE_USE_SDL3
|
||||
return SDL_CreateRenderer(window, nullptr);
|
||||
#else
|
||||
return SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set vertical sync for the renderer.
|
||||
*
|
||||
* SDL2: VSync is set at renderer creation time via flags
|
||||
* SDL3: SDL_SetRenderVSync(renderer, interval)
|
||||
*/
|
||||
inline void SetRenderVSync(SDL_Renderer* renderer, int interval) {
|
||||
#ifdef YAZE_USE_SDL3
|
||||
SDL_SetRenderVSync(renderer, interval);
|
||||
#else
|
||||
// SDL2 sets vsync at creation time, this is a no-op
|
||||
(void)renderer;
|
||||
(void)interval;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Render a texture to the current render target.
|
||||
*
|
||||
* SDL2: SDL_RenderCopy(renderer, texture, srcrect, dstrect)
|
||||
* SDL3: SDL_RenderTexture(renderer, texture, srcrect, dstrect)
|
||||
*
|
||||
* Note: This version handles the int to float conversion for SDL3.
|
||||
*/
|
||||
inline bool RenderTexture(SDL_Renderer* renderer, SDL_Texture* texture,
|
||||
const SDL_Rect* srcrect, const SDL_Rect* dstrect) {
|
||||
#ifdef YAZE_USE_SDL3
|
||||
SDL_FRect src_frect, dst_frect;
|
||||
SDL_FRect* src_ptr = nullptr;
|
||||
SDL_FRect* dst_ptr = nullptr;
|
||||
|
||||
if (srcrect) {
|
||||
src_frect.x = static_cast<float>(srcrect->x);
|
||||
src_frect.y = static_cast<float>(srcrect->y);
|
||||
src_frect.w = static_cast<float>(srcrect->w);
|
||||
src_frect.h = static_cast<float>(srcrect->h);
|
||||
src_ptr = &src_frect;
|
||||
}
|
||||
|
||||
if (dstrect) {
|
||||
dst_frect.x = static_cast<float>(dstrect->x);
|
||||
dst_frect.y = static_cast<float>(dstrect->y);
|
||||
dst_frect.w = static_cast<float>(dstrect->w);
|
||||
dst_frect.h = static_cast<float>(dstrect->h);
|
||||
dst_ptr = &dst_frect;
|
||||
}
|
||||
|
||||
return SDL_RenderTexture(renderer, texture, src_ptr, dst_ptr);
|
||||
#else
|
||||
return SDL_RenderCopy(renderer, texture, srcrect, dstrect) == 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Surface Helpers
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief Free/destroy a surface.
|
||||
*
|
||||
* SDL2: SDL_FreeSurface(surface)
|
||||
* SDL3: SDL_DestroySurface(surface)
|
||||
*/
|
||||
inline void FreeSurface(SDL_Surface* surface) {
|
||||
if (!surface) return;
|
||||
#ifdef YAZE_USE_SDL3
|
||||
SDL_DestroySurface(surface);
|
||||
#else
|
||||
SDL_FreeSurface(surface);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert a surface to a specific pixel format.
|
||||
*
|
||||
* SDL2: SDL_ConvertSurfaceFormat(surface, format, flags)
|
||||
* SDL3: SDL_ConvertSurface(surface, format)
|
||||
*/
|
||||
inline SDL_Surface* ConvertSurfaceFormat(SDL_Surface* surface, uint32_t format,
|
||||
uint32_t flags = 0) {
|
||||
if (!surface) return nullptr;
|
||||
#ifdef YAZE_USE_SDL3
|
||||
(void)flags; // SDL3 removed flags parameter
|
||||
return SDL_ConvertSurface(surface, format);
|
||||
#else
|
||||
return SDL_ConvertSurfaceFormat(surface, format, flags);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get bits per pixel from a surface.
|
||||
*
|
||||
* SDL2: surface->format->BitsPerPixel
|
||||
* SDL3: SDL_GetPixelFormatDetails(surface->format)->bits_per_pixel
|
||||
*/
|
||||
inline int GetSurfaceBitsPerPixel(SDL_Surface* surface) {
|
||||
if (!surface) return 0;
|
||||
#ifdef YAZE_USE_SDL3
|
||||
const SDL_PixelFormatDetails* details =
|
||||
SDL_GetPixelFormatDetails(surface->format);
|
||||
return details ? details->bits_per_pixel : 0;
|
||||
#else
|
||||
return surface->format ? surface->format->BitsPerPixel : 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get bytes per pixel from a surface.
|
||||
*
|
||||
* SDL2: surface->format->BytesPerPixel
|
||||
* SDL3: SDL_GetPixelFormatDetails(surface->format)->bytes_per_pixel
|
||||
*/
|
||||
inline int GetSurfaceBytesPerPixel(SDL_Surface* surface) {
|
||||
if (!surface) return 0;
|
||||
#ifdef YAZE_USE_SDL3
|
||||
const SDL_PixelFormatDetails* details =
|
||||
SDL_GetPixelFormatDetails(surface->format);
|
||||
return details ? details->bytes_per_pixel : 0;
|
||||
#else
|
||||
return surface->format ? surface->format->BytesPerPixel : 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Window Event Compatibility Macros
|
||||
// These macros allow code to handle window events consistently across SDL2/SDL3
|
||||
// ============================================================================
|
||||
|
||||
#ifdef YAZE_USE_SDL3
|
||||
|
||||
// SDL3 has individual window events at the top level
|
||||
#define YAZE_SDL_QUIT SDL_EVENT_QUIT
|
||||
#define YAZE_SDL_WINDOWEVENT 0 // Placeholder - SDL3 has no combined event
|
||||
|
||||
// SDL3 window events are individual event types
|
||||
#define YAZE_SDL_WINDOW_CLOSE SDL_EVENT_WINDOW_CLOSE_REQUESTED
|
||||
#define YAZE_SDL_WINDOW_RESIZED SDL_EVENT_WINDOW_RESIZED
|
||||
#define YAZE_SDL_WINDOW_SIZE_CHANGED SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED
|
||||
#define YAZE_SDL_WINDOW_MINIMIZED SDL_EVENT_WINDOW_MINIMIZED
|
||||
#define YAZE_SDL_WINDOW_MAXIMIZED SDL_EVENT_WINDOW_MAXIMIZED
|
||||
#define YAZE_SDL_WINDOW_RESTORED SDL_EVENT_WINDOW_RESTORED
|
||||
#define YAZE_SDL_WINDOW_SHOWN SDL_EVENT_WINDOW_SHOWN
|
||||
#define YAZE_SDL_WINDOW_HIDDEN SDL_EVENT_WINDOW_HIDDEN
|
||||
#define YAZE_SDL_WINDOW_EXPOSED SDL_EVENT_WINDOW_EXPOSED
|
||||
#define YAZE_SDL_WINDOW_FOCUS_GAINED SDL_EVENT_WINDOW_FOCUS_GAINED
|
||||
#define YAZE_SDL_WINDOW_FOCUS_LOST SDL_EVENT_WINDOW_FOCUS_LOST
|
||||
|
||||
// SDL3 has no nested window events
|
||||
#define YAZE_SDL_HAS_INDIVIDUAL_WINDOW_EVENTS 1
|
||||
|
||||
#else // SDL2
|
||||
|
||||
// SDL2 event types
|
||||
#define YAZE_SDL_QUIT SDL_QUIT
|
||||
#define YAZE_SDL_WINDOWEVENT SDL_WINDOWEVENT
|
||||
|
||||
// SDL2 window events are nested under SDL_WINDOWEVENT
|
||||
#define YAZE_SDL_WINDOW_CLOSE SDL_WINDOWEVENT_CLOSE
|
||||
#define YAZE_SDL_WINDOW_RESIZED SDL_WINDOWEVENT_RESIZED
|
||||
#define YAZE_SDL_WINDOW_SIZE_CHANGED SDL_WINDOWEVENT_SIZE_CHANGED
|
||||
#define YAZE_SDL_WINDOW_MINIMIZED SDL_WINDOWEVENT_MINIMIZED
|
||||
#define YAZE_SDL_WINDOW_MAXIMIZED SDL_WINDOWEVENT_MAXIMIZED
|
||||
#define YAZE_SDL_WINDOW_RESTORED SDL_WINDOWEVENT_RESTORED
|
||||
#define YAZE_SDL_WINDOW_SHOWN SDL_WINDOWEVENT_SHOWN
|
||||
#define YAZE_SDL_WINDOW_HIDDEN SDL_WINDOWEVENT_HIDDEN
|
||||
#define YAZE_SDL_WINDOW_EXPOSED SDL_WINDOWEVENT_EXPOSED
|
||||
#define YAZE_SDL_WINDOW_FOCUS_GAINED SDL_WINDOWEVENT_FOCUS_GAINED
|
||||
#define YAZE_SDL_WINDOW_FOCUS_LOST SDL_WINDOWEVENT_FOCUS_LOST
|
||||
|
||||
// SDL2 uses nested window events
|
||||
#define YAZE_SDL_HAS_INDIVIDUAL_WINDOW_EVENTS 0
|
||||
|
||||
#endif // YAZE_USE_SDL3
|
||||
|
||||
// ============================================================================
|
||||
// Window Event Helper Functions
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief Check if an event is a window close event
|
||||
* Works correctly for both SDL2 (nested) and SDL3 (individual) events
|
||||
*/
|
||||
inline bool IsWindowCloseEvent(const SDL_Event& event) {
|
||||
#ifdef YAZE_USE_SDL3
|
||||
return event.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED;
|
||||
#else
|
||||
return event.type == SDL_WINDOWEVENT &&
|
||||
event.window.event == SDL_WINDOWEVENT_CLOSE;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if an event is a window resize event
|
||||
*/
|
||||
inline bool IsWindowResizeEvent(const SDL_Event& event) {
|
||||
#ifdef YAZE_USE_SDL3
|
||||
return event.type == SDL_EVENT_WINDOW_RESIZED ||
|
||||
event.type == SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED;
|
||||
#else
|
||||
return event.type == SDL_WINDOWEVENT &&
|
||||
(event.window.event == SDL_WINDOWEVENT_RESIZED ||
|
||||
event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if an event is a window minimize event
|
||||
*/
|
||||
inline bool IsWindowMinimizedEvent(const SDL_Event& event) {
|
||||
#ifdef YAZE_USE_SDL3
|
||||
return event.type == SDL_EVENT_WINDOW_MINIMIZED;
|
||||
#else
|
||||
return event.type == SDL_WINDOWEVENT &&
|
||||
event.window.event == SDL_WINDOWEVENT_MINIMIZED;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if an event is a window restore event
|
||||
*/
|
||||
inline bool IsWindowRestoredEvent(const SDL_Event& event) {
|
||||
#ifdef YAZE_USE_SDL3
|
||||
return event.type == SDL_EVENT_WINDOW_RESTORED;
|
||||
#else
|
||||
return event.type == SDL_WINDOWEVENT &&
|
||||
event.window.event == SDL_WINDOWEVENT_RESTORED;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get window width from resize event data
|
||||
*/
|
||||
inline int GetWindowEventWidth(const SDL_Event& event) {
|
||||
return event.window.data1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get window height from resize event data
|
||||
*/
|
||||
inline int GetWindowEventHeight(const SDL_Event& event) {
|
||||
return event.window.data2;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Initialization Helpers
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief Check if SDL initialization succeeded.
|
||||
*
|
||||
* SDL2: Returns 0 on success
|
||||
* SDL3: Returns true (non-zero) on success
|
||||
*/
|
||||
inline bool InitSucceeded(int result) {
|
||||
#ifdef YAZE_USE_SDL3
|
||||
// SDL3 returns bool (non-zero for success)
|
||||
return result != 0;
|
||||
#else
|
||||
// SDL2 returns 0 for success
|
||||
return result == 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get recommended init flags.
|
||||
*
|
||||
* SDL3 removed SDL_INIT_TIMER (timer is always available).
|
||||
*/
|
||||
inline uint32_t GetDefaultInitFlags() {
|
||||
#ifdef YAZE_USE_SDL3
|
||||
return SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_EVENTS;
|
||||
#else
|
||||
return SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER | SDL_INIT_EVENTS;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace platform
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_PLATFORM_SDL_COMPAT_H_
|
||||
@@ -1,7 +1,7 @@
|
||||
#ifndef YAZE_APP_CORE_TIMING_H
|
||||
#define YAZE_APP_CORE_TIMING_H
|
||||
|
||||
#include <SDL.h>
|
||||
#include "app/platform/sdl_compat.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#ifndef YAZE_CORE_WINDOW_H_
|
||||
#define YAZE_CORE_WINDOW_H_
|
||||
|
||||
#include <SDL.h>
|
||||
#include "app/platform/sdl_compat.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
|
||||
75
src/app/platform/window_backend_factory.cc
Normal file
75
src/app/platform/window_backend_factory.cc
Normal file
@@ -0,0 +1,75 @@
|
||||
// window_backend_factory.cc - Window Backend Factory Implementation
|
||||
|
||||
#include "app/platform/iwindow.h"
|
||||
|
||||
#include "app/platform/sdl2_window_backend.h"
|
||||
#include "util/log.h"
|
||||
|
||||
#ifdef YAZE_USE_SDL3
|
||||
#include "app/platform/sdl3_window_backend.h"
|
||||
#endif
|
||||
|
||||
namespace yaze {
|
||||
namespace platform {
|
||||
|
||||
std::unique_ptr<IWindowBackend> WindowBackendFactory::Create(
|
||||
WindowBackendType type) {
|
||||
switch (type) {
|
||||
case WindowBackendType::SDL2:
|
||||
#ifndef YAZE_USE_SDL3
|
||||
return std::make_unique<SDL2WindowBackend>();
|
||||
#else
|
||||
LOG_WARN("WindowBackendFactory",
|
||||
"SDL2 backend requested but built with SDL3, using SDL3");
|
||||
return std::make_unique<SDL3WindowBackend>();
|
||||
#endif
|
||||
|
||||
case WindowBackendType::SDL3:
|
||||
#ifdef YAZE_USE_SDL3
|
||||
return std::make_unique<SDL3WindowBackend>();
|
||||
#else
|
||||
LOG_WARN("WindowBackendFactory",
|
||||
"SDL3 backend requested but not available, using SDL2");
|
||||
return std::make_unique<SDL2WindowBackend>();
|
||||
#endif
|
||||
|
||||
case WindowBackendType::Auto:
|
||||
default:
|
||||
return Create(GetDefaultType());
|
||||
}
|
||||
}
|
||||
|
||||
WindowBackendType WindowBackendFactory::GetDefaultType() {
|
||||
#ifdef YAZE_USE_SDL3
|
||||
return WindowBackendType::SDL3;
|
||||
#else
|
||||
return WindowBackendType::SDL2;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool WindowBackendFactory::IsAvailable(WindowBackendType type) {
|
||||
switch (type) {
|
||||
case WindowBackendType::SDL2:
|
||||
#ifdef YAZE_USE_SDL3
|
||||
return false; // Built with SDL3, SDL2 not available
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
|
||||
case WindowBackendType::SDL3:
|
||||
#ifdef YAZE_USE_SDL3
|
||||
return true;
|
||||
#else
|
||||
return false; // SDL3 not built in
|
||||
#endif
|
||||
|
||||
case WindowBackendType::Auto:
|
||||
return true; // Auto always available
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace platform
|
||||
} // namespace yaze
|
||||
Reference in New Issue
Block a user