refactor(platform): 重构平台模块,实现多后端支持

重构平台模块,引入IWindow和IInput接口,支持SDL2和GLFW等多后端。主要变更包括:
1. 新增平台模块接口和SDL2后端实现
2. 移除旧版Window和Input类,替换为接口化设计
3. 添加后端注册机制,支持动态加载不同平台后端
4. 统一输入系统API,定义标准键盘、鼠标和手柄按键枚举
5. 更新构建系统,支持通过配置选择不同后端

同时调整相关代码以适配新接口,包括渲染器、场景管理和应用类等
This commit is contained in:
ChestnutYueyue 2026-02-15 00:22:24 +08:00
parent 387ea62853
commit 9439e200d7
23 changed files with 2018 additions and 1959 deletions

View File

@ -2,11 +2,12 @@
#include <extra2d/core/types.h>
#include <extra2d/graphics/render_backend.h>
#include <extra2d/platform/window.h>
#include <extra2d/platform/iwindow.h>
#include <string>
namespace extra2d {
class Input;
class IInput;
class SceneManager;
class TimerManager;
class EventQueue;
@ -14,21 +15,29 @@ class EventDispatcher;
class Camera;
class ViewportAdapter;
/**
* @brief
*/
enum class PlatformType { Auto = 0, PC, Switch };
/**
* @brief
*/
struct AppConfig {
std::string title = "Easy2D Application";
int width = 800;
int height = 600;
bool fullscreen = false;
bool resizable = true;
bool vsync = true;
int fpsLimit = 0;
BackendType renderBackend = BackendType::OpenGL;
int msaaSamples = 0;
PlatformType platform = PlatformType::Auto;
bool enableCursors = true;
bool enableDpiScale = false;
std::string title = "Extra2D Application";
int width = 1280;
int height = 720;
bool fullscreen = false;
bool resizable = true;
bool vsync = true;
int fpsLimit = 0;
BackendType renderBackend = BackendType::OpenGL;
int msaaSamples = 0;
PlatformType platform = PlatformType::Auto;
bool enableCursors = true;
bool enableDpiScale = false;
std::string backend = "sdl2";
};
/**
@ -36,69 +45,85 @@ struct AppConfig {
*/
class Application {
public:
static Application &get();
static Application& get();
Application(const Application &) = delete;
Application &operator=(const Application &) = delete;
Application(const Application&) = delete;
Application& operator=(const Application&) = delete;
bool init(const AppConfig &config);
void shutdown();
void run();
void quit();
/**
* @brief 使
*/
bool init();
void pause();
void resume();
bool isPaused() const { return paused_; }
bool isRunning() const { return running_; }
/**
* @brief 使
*/
bool init(const AppConfig& config);
Window &window() { return *window_; }
RenderBackend &renderer() { return *renderer_; }
Input &input();
SceneManager &scenes();
TimerManager &timers();
EventQueue &eventQueue();
EventDispatcher &eventDispatcher();
Camera &camera();
ViewportAdapter &viewportAdapter();
/**
* @brief
* @param path .json .ini
*/
bool init(const std::string& path);
void enterScene(Ptr<class Scene> scene);
void shutdown();
void run();
void quit();
float deltaTime() const { return deltaTime_; }
float totalTime() const { return totalTime_; }
int fps() const { return currentFps_; }
void pause();
void resume();
bool isPaused() const { return paused_; }
bool isRunning() const { return running_; }
const AppConfig &getConfig() const { return config_; }
IWindow& window() { return *window_; }
RenderBackend& renderer() { return *renderer_; }
IInput& input();
SceneManager& scenes();
TimerManager& timers();
EventQueue& eventQueue();
EventDispatcher& eventDispatcher();
Camera& camera();
ViewportAdapter& viewportAdapter();
void enterScene(Ptr<class Scene> scene);
float deltaTime() const { return deltaTime_; }
float totalTime() const { return totalTime_; }
int fps() const { return currentFps_; }
const AppConfig& getConfig() const { return config_; }
private:
Application() = default;
~Application();
Application() = default;
~Application();
void mainLoop();
void update();
void render();
bool initImpl();
void mainLoop();
void update();
void render();
AppConfig config_;
AppConfig config_;
UniquePtr<Window> window_;
UniquePtr<RenderBackend> renderer_;
UniquePtr<SceneManager> sceneManager_;
UniquePtr<TimerManager> timerManager_;
UniquePtr<EventQueue> eventQueue_;
UniquePtr<EventDispatcher> eventDispatcher_;
UniquePtr<Camera> camera_;
UniquePtr<ViewportAdapter> viewportAdapter_;
UniquePtr<IWindow> window_;
UniquePtr<RenderBackend> renderer_;
UniquePtr<SceneManager> sceneManager_;
UniquePtr<TimerManager> timerManager_;
UniquePtr<EventQueue> eventQueue_;
UniquePtr<EventDispatcher> eventDispatcher_;
UniquePtr<Camera> camera_;
UniquePtr<ViewportAdapter> viewportAdapter_;
bool initialized_ = false;
bool running_ = false;
bool paused_ = false;
bool shouldQuit_ = false;
bool initialized_ = false;
bool running_ = false;
bool paused_ = false;
bool shouldQuit_ = false;
float deltaTime_ = 0.0f;
float totalTime_ = 0.0f;
double lastFrameTime_ = 0.0;
int frameCount_ = 0;
float fpsTimer_ = 0.0f;
int currentFps_ = 0;
float deltaTime_ = 0.0f;
float totalTime_ = 0.0f;
double lastFrameTime_ = 0.0;
int frameCount_ = 0;
float fpsTimer_ = 0.0f;
int currentFps_ = 0;
};
} // namespace extra2d

View File

@ -9,8 +9,10 @@
#include <extra2d/core/types.h>
// Platform
#include <extra2d/platform/input.h>
#include <extra2d/platform/window.h>
#include <extra2d/platform/iinput.h>
#include <extra2d/platform/iwindow.h>
#include <extra2d/platform/keys.h>
#include <extra2d/platform/platform_module.h>
// Graphics
#include <extra2d/graphics/camera.h>

View File

@ -10,7 +10,7 @@
namespace extra2d {
class Window;
class IWindow;
// ============================================================================
// OpenGL 渲染器实现
@ -21,7 +21,7 @@ public:
~GLRenderer() override;
// RenderBackend 接口实现
bool init(Window *window) override;
bool init(IWindow* window) override;
void shutdown() override;
void beginFrame(const Color &clearColor) override;
@ -88,7 +88,7 @@ private:
float r, g, b, a;
};
Window *window_;
IWindow* window_;
GLSpriteBatch spriteBatch_;
GLShader shapeShader_;

View File

@ -8,7 +8,7 @@
namespace extra2d {
// 前向声明
class Window;
class IWindow;
class Texture;
class FontAtlas;
class Shader;
@ -44,7 +44,7 @@ public:
// ------------------------------------------------------------------------
// 生命周期
// ------------------------------------------------------------------------
virtual bool init(Window *window) = 0;
virtual bool init(IWindow* window) = 0;
virtual void shutdown() = 0;
// ------------------------------------------------------------------------

View File

@ -0,0 +1,175 @@
#pragma once
#include <extra2d/platform/keys.h>
#include <extra2d/core/math_types.h>
namespace extra2d {
/**
* @brief
*/
struct TouchPoint {
int id = 0;
Vec2 position;
Vec2 delta;
bool pressed = false;
bool released = false;
};
/**
* @brief
*
*/
class IInput {
public:
virtual ~IInput() = default;
/**
* @brief
*/
virtual void init() = 0;
/**
* @brief
*/
virtual void shutdown() = 0;
/**
* @brief
*/
virtual void update() = 0;
// ========== 键盘 ==========
/**
* @brief
*/
virtual bool down(Key key) const = 0;
/**
* @brief
*/
virtual bool pressed(Key key) const = 0;
/**
* @brief
*/
virtual bool released(Key key) const = 0;
// ========== 鼠标 ==========
/**
* @brief
*/
virtual bool down(Mouse btn) const = 0;
/**
* @brief
*/
virtual bool pressed(Mouse btn) const = 0;
/**
* @brief
*/
virtual bool released(Mouse btn) const = 0;
/**
* @brief
*/
virtual Vec2 mouse() const = 0;
/**
* @brief
*/
virtual Vec2 mouseDelta() const = 0;
/**
* @brief
*/
virtual float scroll() const = 0;
/**
* @brief
*/
virtual float scrollDelta() const = 0;
/**
* @brief
*/
virtual void setMouse(const Vec2& pos) = 0;
// ========== 手柄 ==========
/**
* @brief
*/
virtual bool gamepad() const = 0;
/**
* @brief
*/
virtual bool down(Gamepad btn) const = 0;
/**
* @brief
*/
virtual bool pressed(Gamepad btn) const = 0;
/**
* @brief
*/
virtual bool released(Gamepad btn) const = 0;
/**
* @brief
*/
virtual Vec2 leftStick() const = 0;
/**
* @brief
*/
virtual Vec2 rightStick() const = 0;
/**
* @brief
*/
virtual float leftTrigger() const = 0;
/**
* @brief
*/
virtual float rightTrigger() const = 0;
/**
* @brief
* @param left [0, 1]
* @param right [0, 1]
*/
virtual void vibrate(float left, float right) = 0;
// ========== 触摸 ==========
/**
* @brief
*/
virtual bool touching() const = 0;
/**
* @brief
*/
virtual int touchCount() const = 0;
/**
* @brief
* @param index
*/
virtual Vec2 touch(int index) const = 0;
/**
* @brief
* @param index
*/
virtual TouchPoint touchPoint(int index) const = 0;
};
} // namespace extra2d

View File

@ -1,173 +0,0 @@
#pragma once
#include <array>
#include <extra2d/core/math_types.h>
#include <extra2d/core/types.h>
#include <extra2d/event/input_codes.h>
#include <SDL.h>
namespace extra2d {
class ViewportAdapter;
// ============================================================================
// 鼠标按钮枚举
// ============================================================================
enum class MouseButton {
Left = 0,
Right = 1,
Middle = 2,
Button4 = 3,
Button5 = 4,
Button6 = 5,
Button7 = 6,
Button8 = 7,
Count = 8
};
// ============================================================================
// Input 类 - 跨平台输入管理
// 支持: 键盘、鼠标、手柄、触摸屏
// ============================================================================
class Input {
public:
Input();
~Input();
// 初始化
void init();
void shutdown();
// 每帧更新
void update();
// ------------------------------------------------------------------------
// 键盘输入
// ------------------------------------------------------------------------
bool isKeyDown(int keyCode) const;
bool isKeyPressed(int keyCode) const;
bool isKeyReleased(int keyCode) const;
// ------------------------------------------------------------------------
// 手柄按钮
// ------------------------------------------------------------------------
bool isButtonDown(int button) const;
bool isButtonPressed(int button) const;
bool isButtonReleased(int button) const;
// 摇杆
Vec2 getLeftStick() const;
Vec2 getRightStick() const;
// ------------------------------------------------------------------------
// 鼠标输入
// ------------------------------------------------------------------------
bool isMouseDown(MouseButton button) const;
bool isMousePressed(MouseButton button) const;
bool isMouseReleased(MouseButton button) const;
Vec2 getMousePosition() const;
Vec2 getMouseDelta() const;
float getMouseScroll() const { return mouseScroll_; }
float getMouseScrollDelta() const { return mouseScroll_ - prevMouseScroll_; }
void setMousePosition(const Vec2 &position);
void setMouseVisible(bool visible);
void setMouseLocked(bool locked);
// ------------------------------------------------------------------------
// 触摸屏 (Switch 原生支持PC 端模拟或禁用)
// ------------------------------------------------------------------------
bool isTouching() const { return touching_; }
Vec2 getTouchPosition() const { return touchPosition_; }
int getTouchCount() const { return touchCount_; }
// ------------------------------------------------------------------------
// 视口适配器
// ------------------------------------------------------------------------
/**
* @brief
* @param adapter
*/
void setViewportAdapter(ViewportAdapter* adapter);
/**
* @brief
* @return
*/
Vec2 getMousePosLogic() const;
/**
* @brief
* @return
*/
Vec2 getTouchPosLogic() const;
/**
* @brief
* @return
*/
Vec2 getMouseDeltaLogic() const;
// ------------------------------------------------------------------------
// 便捷方法
// ------------------------------------------------------------------------
bool isAnyKeyDown() const;
bool isAnyMouseDown() const;
private:
static constexpr int MAX_BUTTONS = SDL_CONTROLLER_BUTTON_MAX;
static constexpr int MAX_KEYS = SDL_NUM_SCANCODES;
SDL_GameController *controller_;
// 键盘状态 (PC 端使用)
std::array<bool, MAX_KEYS> keysDown_;
std::array<bool, MAX_KEYS> prevKeysDown_;
// 手柄按钮状态
std::array<bool, MAX_BUTTONS> buttonsDown_;
std::array<bool, MAX_BUTTONS> prevButtonsDown_;
// 摇杆状态
float leftStickX_;
float leftStickY_;
float rightStickX_;
float rightStickY_;
// 鼠标状态 (PC 端使用)
Vec2 mousePosition_;
Vec2 prevMousePosition_;
float mouseScroll_;
float prevMouseScroll_;
std::array<bool, 8> mouseButtonsDown_;
std::array<bool, 8> prevMouseButtonsDown_;
// 触摸屏状态 (Switch 原生)
bool touching_;
bool prevTouching_;
Vec2 touchPosition_;
Vec2 prevTouchPosition_;
int touchCount_;
// 视口适配器
ViewportAdapter* viewportAdapter_;
// 映射键盘 keyCode 到 SDL GameController 按钮 (Switch 兼容模式)
SDL_GameControllerButton mapKeyToButton(int keyCode) const;
// 更新键盘状态
void updateKeyboard();
// 更新鼠标状态
void updateMouse();
// 更新手柄状态
void updateGamepad();
// 更新触摸屏状态
void updateTouch();
};
} // namespace extra2d

View File

@ -0,0 +1,218 @@
#pragma once
#include <extra2d/core/types.h>
#include <extra2d/core/math_types.h>
#include <functional>
#include <string>
namespace extra2d {
class IInput;
/**
* @brief
*/
struct WindowConfig {
std::string title = "Extra2D Application";
int width = 1280;
int height = 720;
bool fullscreen = false;
bool fullscreenDesktop = true;
bool resizable = true;
bool vsync = true;
int msaaSamples = 0;
bool centerWindow = true;
bool visible = true;
bool decorated = true;
};
/**
* @brief
*/
enum class Cursor {
Arrow,
IBeam,
Crosshair,
Hand,
HResize,
VResize,
Hidden
};
/**
* @brief
*
*/
class IWindow {
public:
virtual ~IWindow() = default;
/**
* @brief
* @param cfg
* @return
*/
virtual bool create(const WindowConfig& cfg) = 0;
/**
* @brief
*/
virtual void destroy() = 0;
/**
* @brief
*/
virtual void poll() = 0;
/**
* @brief
*/
virtual void swap() = 0;
/**
* @brief
*/
virtual bool shouldClose() const = 0;
/**
* @brief
*/
virtual void close() = 0;
/**
* @brief
*/
virtual void setTitle(const std::string& title) = 0;
/**
* @brief
*/
virtual void setSize(int w, int h) = 0;
/**
* @brief
*/
virtual void setPos(int x, int y) = 0;
/**
* @brief
*/
virtual void setFullscreen(bool fs) = 0;
/**
* @brief
*/
virtual void setVSync(bool vsync) = 0;
/**
* @brief
*/
virtual void setVisible(bool visible) = 0;
/**
* @brief
*/
virtual int width() const = 0;
/**
* @brief
*/
virtual int height() const = 0;
/**
* @brief
*/
virtual Size size() const = 0;
/**
* @brief
*/
virtual Vec2 pos() const = 0;
/**
* @brief
*/
virtual bool fullscreen() const = 0;
/**
* @brief
*/
virtual bool vsync() const = 0;
/**
* @brief
*/
virtual bool focused() const = 0;
/**
* @brief
*/
virtual bool minimized() const = 0;
/**
* @brief X
*/
virtual float scaleX() const = 0;
/**
* @brief Y
*/
virtual float scaleY() const = 0;
/**
* @brief
*/
virtual void setCursor(Cursor cursor) = 0;
/**
* @brief /
*/
virtual void showCursor(bool show) = 0;
/**
* @brief /
*/
virtual void lockCursor(bool lock) = 0;
/**
* @brief
*/
virtual IInput* input() const = 0;
/**
* @brief
*/
using ResizeCb = std::function<void(int, int)>;
/**
* @brief
*/
using CloseCb = std::function<void()>;
/**
* @brief
*/
using FocusCb = std::function<void(bool)>;
/**
* @brief
*/
virtual void onResize(ResizeCb cb) = 0;
/**
* @brief
*/
virtual void onClose(CloseCb cb) = 0;
/**
* @brief
*/
virtual void onFocus(FocusCb cb) = 0;
/**
* @brief
*/
virtual void* native() const = 0;
};
} // namespace extra2d

View File

@ -0,0 +1,72 @@
#pragma once
namespace extra2d {
/**
* @brief
*/
enum class Key : int {
None = 0,
A, B, C, D, E, F, G, H, I, J, K, L, M,
N, O, P, Q, R, S, T, U, V, W, X, Y, Z,
Num0, Num1, Num2, Num3, Num4,
Num5, Num6, Num7, Num8, Num9,
F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12,
Space, Enter, Escape, Tab, Backspace,
Insert, Delete, Home, End, PageUp, PageDown,
Up, Down, Left, Right,
LShift, RShift, LCtrl, RCtrl, LAlt, RAlt,
CapsLock, NumLock, ScrollLock,
Count
};
/**
* @brief
*/
enum class Mouse : int {
Left = 0,
Right,
Middle,
X1,
X2,
Count
};
/**
* @brief
*/
enum class Gamepad : int {
A = 0,
B,
X,
Y,
LB,
RB,
LT,
RT,
Back,
Start,
Guide,
LStick,
RStick,
DUp,
DDown,
DLeft,
DRight,
Count
};
/**
* @brief
*/
enum class GamepadAxis : int {
LeftX = 0,
LeftY,
RightX,
RightY,
LeftTrigger,
RightTrigger,
Count
};
} // namespace extra2d

View File

@ -0,0 +1,97 @@
#pragma once
#include <extra2d/core/types.h>
#include <extra2d/platform/iwindow.h>
#include <extra2d/platform/iinput.h>
#include <functional>
#include <unordered_map>
#include <vector>
#include <string>
namespace extra2d {
/**
* @brief
*/
struct PlatformModuleConfig {
std::string backend = "sdl2";
bool gamepad = true;
bool touch = true;
float deadzone = 0.15f;
};
/**
* @brief
*
*/
class BackendFactory {
public:
using WindowFn = std::function<UniquePtr<IWindow>()>;
using InputFn = std::function<UniquePtr<IInput>()>;
/**
* @brief
* @param name
* @param win
* @param in
*/
static void reg(const std::string& name, WindowFn win, InputFn in);
/**
* @brief
* @param name
* @return nullptr
*/
static UniquePtr<IWindow> createWindow(const std::string& name);
/**
* @brief
* @param name
* @return nullptr
*/
static UniquePtr<IInput> createInput(const std::string& name);
/**
* @brief
*/
static std::vector<std::string> backends();
/**
* @brief
*/
static bool has(const std::string& name);
private:
struct BackendEntry {
WindowFn windowFn;
InputFn inputFn;
};
static std::unordered_map<std::string, BackendEntry>& registry();
};
/**
* @brief
* 使
*
* @example
* E2D_REG_BACKEND(sdl2, SDL2Window, SDL2Input)
*/
#define E2D_REG_BACKEND(name, WinClass, InClass) \
namespace { \
static struct E2D_BACKEND_REG_##name { \
E2D_BACKEND_REG_##name() { \
::extra2d::BackendFactory::reg( \
#name, \
[]() -> ::extra2d::UniquePtr<::extra2d::IWindow> { \
return ::extra2d::makeUnique<WinClass>(); \
}, \
[]() -> ::extra2d::UniquePtr<::extra2d::IInput> { \
return ::extra2d::makeUnique<InClass>(); \
} \
); \
} \
} e2d_backend_reg_##name; \
}
} // namespace extra2d

View File

@ -1,156 +0,0 @@
#pragma once
#include <extra2d/core/math_types.h>
#include <extra2d/core/types.h>
#include <functional>
#include <SDL.h>
namespace extra2d {
// 前向声明
class EventQueue;
class Input;
// ============================================================================
// 窗口配置
// ============================================================================
struct WindowConfig {
std::string title = "Extra2D Application";
int width = 1280;
int height = 720;
bool fullscreen = true;
bool resizable = false;
bool vsync = true;
int msaaSamples = 0;
bool centerWindow = true;
bool enableCursors = true;
bool enableDpiScale = true;
bool fullscreenDesktop =
true; // true: SDL_WINDOW_FULLSCREEN_DESKTOP, false: SDL_WINDOW_FULLSCREEN
};
// ============================================================================
// 鼠标光标形状枚举
// ============================================================================
enum class CursorShape {
Arrow,
IBeam,
Crosshair,
Hand,
HResize,
VResize,
ResizeAll,
ResizeNWSE,
ResizeNESW
};
// ============================================================================
// Window 类 - SDL2 Window + GLES 3.2 封装
// 支持平台: Nintendo Switch, Windows, Linux, macOS
// ============================================================================
class Window {
public:
Window();
~Window();
// 创建窗口
bool create(const WindowConfig &config);
void destroy();
// 窗口操作
void pollEvents();
void swapBuffers();
bool shouldClose() const;
void setShouldClose(bool close);
// 窗口属性
void setTitle(const std::string &title);
void setSize(int width, int height);
void setPos(int x, int y);
void setFullscreen(bool fullscreen);
void setVSync(bool enabled);
void setResizable(bool resizable);
// 获取窗口属性
int getWidth() const { return width_; }
int getHeight() const { return height_; }
Size getSize() const {
return Size(static_cast<float>(width_), static_cast<float>(height_));
}
Vec2 getPosition() const;
bool isFullscreen() const { return fullscreen_; }
bool isVSync() const { return vsync_; }
// DPI 缩放 (PC 端自动检测Switch 固定 1.0)
float getContentScaleX() const;
float getContentScaleY() const;
Vec2 getContentScale() const;
// 窗口状态
bool isFocused() const { return focused_; }
bool isMinimized() const;
bool isMaximized() const;
// 获取 SDL2 窗口和 GL 上下文
SDL_Window *getSDLWindow() const { return sdlWindow_; }
SDL_GLContext getGLContext() const { return glContext_; }
// 设置/获取用户数据
void setUserData(void *data) { userData_ = data; }
void *getUserData() const { return userData_; }
// 事件队列
void setEventQueue(EventQueue *queue) { eventQueue_ = queue; }
EventQueue *getEventQueue() const { return eventQueue_; }
// 获取输入管理器
Input *getInput() const { return input_.get(); }
// 光标操作 (PC 端有效Switch 上为空操作)
void setCursor(CursorShape shape);
void resetCursor();
void setMouseVisible(bool visible);
// 窗口回调
using ResizeCallback = std::function<void(int width, int height)>;
using FocusCallback = std::function<void(bool focused)>;
using CloseCallback = std::function<void()>;
void setResizeCallback(ResizeCallback callback) {
resizeCallback_ = callback;
}
void setFocusCallback(FocusCallback callback) { focusCallback_ = callback; }
void setCloseCallback(CloseCallback callback) { closeCallback_ = callback; }
private:
// SDL2 状态
SDL_Window *sdlWindow_;
SDL_GLContext glContext_;
SDL_Cursor *sdlCursors_[9]; // 光标缓存
SDL_Cursor *currentCursor_;
int width_;
int height_;
bool vsync_;
bool shouldClose_;
bool fullscreen_;
bool focused_;
float contentScaleX_;
float contentScaleY_;
bool enableDpiScale_;
void *userData_;
EventQueue *eventQueue_;
UniquePtr<Input> input_;
ResizeCallback resizeCallback_;
FocusCallback focusCallback_;
CloseCallback closeCallback_;
bool initSDL(const WindowConfig &config);
void deinitSDL();
void initCursors();
void deinitCursors();
void updateContentScale();
};
} // namespace extra2d

View File

@ -5,8 +5,8 @@
#include <extra2d/graphics/render_backend.h>
#include <extra2d/graphics/viewport_adapter.h>
#include <extra2d/graphics/vram_manager.h>
#include <extra2d/platform/input.h>
#include <extra2d/platform/window.h>
#include <extra2d/platform/iinput.h>
#include <extra2d/platform/platform_module.h>
#include <extra2d/scene/scene_manager.h>
#include <extra2d/utils/logger.h>
#include <extra2d/utils/timer.h>
@ -20,416 +20,359 @@
namespace extra2d {
/**
* @brief
* @return
*
* 使Switch平台使用clock_gettime
* 使std::chrono::steady_clock
*/
static double getTimeSeconds() {
#ifdef __SWITCH__
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return static_cast<double>(ts.tv_sec) +
static_cast<double>(ts.tv_nsec) / 1000000000.0;
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return static_cast<double>(ts.tv_sec) +
static_cast<double>(ts.tv_nsec) / 1000000000.0;
#else
using namespace std::chrono;
auto now = steady_clock::now();
auto duration = now.time_since_epoch();
return duration_cast<std::chrono::duration<double>>(duration).count();
using namespace std::chrono;
auto now = steady_clock::now();
auto duration = now.time_since_epoch();
return duration_cast<std::chrono::duration<double>>(duration).count();
#endif
}
/**
* @brief Application单例实例
* @return Application单例的引用
*/
Application &Application::get() {
static Application instance;
return instance;
Application& Application::get() {
static Application instance;
return instance;
}
/**
* @brief
*/
Application::~Application() { shutdown(); }
/**
* @brief
* @param config
* @return truefalse
*
*
*/
bool Application::init(const AppConfig &config) {
if (initialized_) {
E2D_LOG_WARN("Application already initialized");
return true;
}
bool Application::init() {
AppConfig cfg;
return init(cfg);
}
config_ = config;
bool Application::init(const AppConfig& config) {
if (initialized_) {
E2D_LOG_WARN("Application already initialized");
return true;
}
PlatformType platform = config_.platform;
if (platform == PlatformType::Auto) {
config_ = config;
return initImpl();
}
bool Application::init(const std::string& path) {
if (initialized_) {
E2D_LOG_WARN("Application already initialized");
return true;
}
E2D_LOG_INFO("Loading config from: {}", path);
AppConfig cfg;
if (path.find(".json") != std::string::npos) {
// TODO: 使用 nlohmann_json 加载配置
E2D_LOG_WARN("JSON config loading not yet implemented, using defaults");
} else if (path.find(".ini") != std::string::npos) {
// TODO: 实现 INI 配置加载
E2D_LOG_WARN("INI config loading not yet implemented, using defaults");
}
config_ = cfg;
return initImpl();
}
bool Application::initImpl() {
PlatformType platform = config_.platform;
if (platform == PlatformType::Auto) {
#ifdef __SWITCH__
platform = PlatformType::Switch;
platform = PlatformType::Switch;
#else
platform = PlatformType::PC;
platform = PlatformType::PC;
#endif
}
}
if (platform == PlatformType::Switch) {
if (platform == PlatformType::Switch) {
#ifdef __SWITCH__
Result rc;
rc = romfsInit();
if (R_SUCCEEDED(rc)) {
E2D_LOG_INFO("RomFS initialized successfully");
} else {
E2D_LOG_WARN("romfsInit failed: {:#08X}, will use regular filesystem",
rc);
}
Result rc;
rc = romfsInit();
if (R_SUCCEEDED(rc)) {
E2D_LOG_INFO("RomFS initialized successfully");
} else {
E2D_LOG_WARN("romfsInit failed: {:#08X}, will use regular filesystem", rc);
}
rc = socketInitializeDefault();
if (R_FAILED(rc)) {
E2D_LOG_WARN(
"socketInitializeDefault failed, nxlink will not be available");
}
rc = socketInitializeDefault();
if (R_FAILED(rc)) {
E2D_LOG_WARN("socketInitializeDefault failed, nxlink will not be available");
}
#endif
}
window_ = makeUnique<Window>();
WindowConfig winConfig;
winConfig.title = config.title;
winConfig.width = config.width;
winConfig.height = config.height;
if (platform == PlatformType::Switch) {
winConfig.fullscreen = true;
winConfig.fullscreenDesktop = false;
winConfig.resizable = false;
winConfig.enableCursors = false;
winConfig.enableDpiScale = false;
} else {
winConfig.fullscreen = config.fullscreen;
winConfig.resizable = config.resizable;
winConfig.enableCursors = config.enableCursors;
winConfig.enableDpiScale = config.enableDpiScale;
}
winConfig.vsync = config.vsync;
winConfig.msaaSamples = config.msaaSamples;
if (!window_->create(winConfig)) {
E2D_LOG_ERROR("Failed to create window");
return false;
}
renderer_ = RenderBackend::create(config.renderBackend);
if (!renderer_ || !renderer_->init(window_.get())) {
E2D_LOG_ERROR("Failed to initialize renderer");
window_->destroy();
return false;
}
sceneManager_ = makeUnique<SceneManager>();
timerManager_ = makeUnique<TimerManager>();
eventQueue_ = makeUnique<EventQueue>();
eventDispatcher_ = makeUnique<EventDispatcher>();
camera_ = makeUnique<Camera>(0, static_cast<float>(window_->getWidth()),
static_cast<float>(window_->getHeight()), 0);
viewportAdapter_ = makeUnique<ViewportAdapter>();
ViewportConfig vpConfig;
vpConfig.logicWidth = static_cast<float>(config.width);
vpConfig.logicHeight = static_cast<float>(config.height);
vpConfig.mode = ViewportMode::AspectRatio;
viewportAdapter_->setConfig(vpConfig);
camera_->setViewportAdapter(viewportAdapter_.get());
input().setViewportAdapter(viewportAdapter_.get());
viewportAdapter_->update(window_->getWidth(), window_->getHeight());
window_->setResizeCallback([this](int width, int height) {
if (viewportAdapter_) {
viewportAdapter_->update(width, height);
}
if (camera_) {
camera_->applyViewportAdapter();
std::string backend = config_.backend;
#ifdef __SWITCH__
backend = "switch";
#endif
if (!BackendFactory::has(backend)) {
E2D_LOG_ERROR("Backend '{}' not available", backend);
auto backends = BackendFactory::backends();
if (backends.empty()) {
E2D_LOG_ERROR("No backends registered!");
return false;
}
backend = backends[0];
E2D_LOG_WARN("Using fallback backend: {}", backend);
}
window_ = BackendFactory::createWindow(backend);
if (!window_) {
E2D_LOG_ERROR("Failed to create window for backend: {}", backend);
return false;
}
WindowConfig winConfig;
winConfig.title = config_.title;
winConfig.width = config_.width;
winConfig.height = config_.height;
if (platform == PlatformType::Switch) {
winConfig.fullscreen = true;
winConfig.fullscreenDesktop = false;
winConfig.resizable = false;
} else {
winConfig.fullscreen = config_.fullscreen;
winConfig.resizable = config_.resizable;
}
winConfig.vsync = config_.vsync;
winConfig.msaaSamples = config_.msaaSamples;
if (!window_->create(winConfig)) {
E2D_LOG_ERROR("Failed to create window");
return false;
}
renderer_ = RenderBackend::create(config_.renderBackend);
if (!renderer_ || !renderer_->init(window_.get())) {
E2D_LOG_ERROR("Failed to initialize renderer");
window_->destroy();
return false;
}
sceneManager_ = makeUnique<SceneManager>();
timerManager_ = makeUnique<TimerManager>();
eventQueue_ = makeUnique<EventQueue>();
eventDispatcher_ = makeUnique<EventDispatcher>();
camera_ = makeUnique<Camera>(0, static_cast<float>(window_->width()),
static_cast<float>(window_->height()), 0);
viewportAdapter_ = makeUnique<ViewportAdapter>();
ViewportConfig vpConfig;
vpConfig.logicWidth = static_cast<float>(config_.width);
vpConfig.logicHeight = static_cast<float>(config_.height);
vpConfig.mode = ViewportMode::AspectRatio;
viewportAdapter_->setConfig(vpConfig);
camera_->setViewportAdapter(viewportAdapter_.get());
viewportAdapter_->update(window_->width(), window_->height());
window_->onResize([this](int width, int height) {
if (viewportAdapter_) {
viewportAdapter_->update(width, height);
}
if (camera_) {
camera_->applyViewportAdapter();
}
if (sceneManager_) {
auto currentScene = sceneManager_->getCurrentScene();
if (currentScene) {
currentScene->setViewportSize(static_cast<float>(width),
static_cast<float>(height));
}
}
});
initialized_ = true;
running_ = true;
E2D_LOG_INFO("Application initialized (backend: {})", backend);
return true;
}
void Application::shutdown() {
if (!initialized_)
return;
E2D_LOG_INFO("Shutting down application...");
VRAMMgr::get().printStats();
if (sceneManager_) {
sceneManager_->end();
}
sceneManager_.reset();
viewportAdapter_.reset();
camera_.reset();
timerManager_.reset();
eventQueue_.reset();
eventDispatcher_.reset();
if (renderer_) {
renderer_->shutdown();
renderer_.reset();
}
if (window_) {
window_->destroy();
window_.reset();
}
PlatformType platform = config_.platform;
if (platform == PlatformType::Auto) {
#ifdef __SWITCH__
platform = PlatformType::Switch;
#else
platform = PlatformType::PC;
#endif
}
if (platform == PlatformType::Switch) {
#ifdef __SWITCH__
romfsExit();
socketExit();
#endif
}
initialized_ = false;
running_ = false;
E2D_LOG_INFO("Application shutdown complete");
}
void Application::run() {
if (!initialized_) {
E2D_LOG_ERROR("Application not initialized");
return;
}
lastFrameTime_ = getTimeSeconds();
while (running_ && !window_->shouldClose()) {
mainLoop();
}
}
void Application::quit() {
shouldQuit_ = true;
running_ = false;
}
void Application::pause() {
if (!paused_) {
paused_ = true;
E2D_LOG_INFO("Application paused");
}
}
void Application::resume() {
if (paused_) {
paused_ = false;
lastFrameTime_ = getTimeSeconds();
E2D_LOG_INFO("Application resumed");
}
}
void Application::mainLoop() {
double currentTime = getTimeSeconds();
deltaTime_ = static_cast<float>(currentTime - lastFrameTime_);
lastFrameTime_ = currentTime;
totalTime_ += deltaTime_;
frameCount_++;
fpsTimer_ += deltaTime_;
if (fpsTimer_ >= 1.0f) {
currentFps_ = frameCount_;
frameCount_ = 0;
fpsTimer_ -= 1.0f;
}
window_->poll();
if (eventDispatcher_ && eventQueue_) {
eventDispatcher_->processQueue(*eventQueue_);
}
if (!paused_) {
update();
}
render();
if (!config_.vsync && config_.fpsLimit > 0) {
double frameEndTime = getTimeSeconds();
double frameTime = frameEndTime - currentTime;
double target = 1.0 / static_cast<double>(config_.fpsLimit);
if (frameTime < target) {
auto sleepSeconds = target - frameTime;
std::this_thread::sleep_for(std::chrono::duration<double>(sleepSeconds));
}
}
}
void Application::update() {
if (timerManager_) {
timerManager_->update(deltaTime_);
}
if (sceneManager_) {
auto currentScene = sceneManager_->getCurrentScene();
if (currentScene) {
currentScene->setViewportSize(static_cast<float>(width),
static_cast<float>(height));
}
sceneManager_->update(deltaTime_);
}
});
initialized_ = true;
running_ = true;
E2D_LOG_INFO("Application initialized successfully");
return true;
}
/**
* @brief
*
*
*/
void Application::shutdown() {
if (!initialized_)
return;
E2D_LOG_INFO("Shutting down application...");
VRAMMgr::get().printStats();
if (sceneManager_) {
sceneManager_->end();
}
sceneManager_.reset();
viewportAdapter_.reset();
camera_.reset();
timerManager_.reset();
eventQueue_.reset();
eventDispatcher_.reset();
if (renderer_) {
renderer_->shutdown();
renderer_.reset();
}
if (window_) {
window_->destroy();
window_.reset();
}
PlatformType platform = config_.platform;
if (platform == PlatformType::Auto) {
#ifdef __SWITCH__
platform = PlatformType::Switch;
#else
platform = PlatformType::PC;
#endif
}
if (platform == PlatformType::Switch) {
#ifdef __SWITCH__
romfsExit();
socketExit();
#endif
}
initialized_ = false;
running_ = false;
E2D_LOG_INFO("Application shutdown complete");
}
/**
* @brief
*
* 退
*/
void Application::run() {
if (!initialized_) {
E2D_LOG_ERROR("Application not initialized");
return;
}
lastFrameTime_ = getTimeSeconds();
#ifdef __SWITCH__
while (running_ && !window_->shouldClose()) {
mainLoop();
}
#else
while (running_ && !window_->shouldClose()) {
mainLoop();
}
#endif
}
/**
* @brief 退
*
* 退退
*/
void Application::quit() {
shouldQuit_ = true;
running_ = false;
}
/**
* @brief
*
*
*/
void Application::pause() {
if (!paused_) {
paused_ = true;
E2D_LOG_INFO("Application paused");
}
}
/**
* @brief
*
* deltaTime跳跃
*/
void Application::resume() {
if (paused_) {
paused_ = false;
lastFrameTime_ = getTimeSeconds();
E2D_LOG_INFO("Application resumed");
}
}
/**
* @brief
*
*
*/
void Application::mainLoop() {
double currentTime = getTimeSeconds();
deltaTime_ = static_cast<float>(currentTime - lastFrameTime_);
lastFrameTime_ = currentTime;
totalTime_ += deltaTime_;
frameCount_++;
fpsTimer_ += deltaTime_;
if (fpsTimer_ >= 1.0f) {
currentFps_ = frameCount_;
frameCount_ = 0;
fpsTimer_ -= 1.0f;
}
window_->pollEvents();
if (eventDispatcher_ && eventQueue_) {
eventDispatcher_->processQueue(*eventQueue_);
}
if (!paused_) {
update();
}
render();
if (!config_.vsync && config_.fpsLimit > 0) {
double frameEndTime = getTimeSeconds();
double frameTime = frameEndTime - currentTime;
double target = 1.0 / static_cast<double>(config_.fpsLimit);
if (frameTime < target) {
auto sleepSeconds = target - frameTime;
std::this_thread::sleep_for(std::chrono::duration<double>(sleepSeconds));
}
}
}
/**
* @brief
*
*
*/
void Application::update() {
if (timerManager_) {
timerManager_->update(deltaTime_);
}
if (sceneManager_) {
sceneManager_->update(deltaTime_);
}
}
/**
* @brief
*
*
*/
void Application::render() {
if (!renderer_) {
E2D_LOG_ERROR("Render failed: renderer is null");
return;
}
if (!renderer_) {
E2D_LOG_ERROR("Render failed: renderer is null");
return;
}
if (viewportAdapter_) {
const auto &vp = viewportAdapter_->getViewport();
renderer_->setViewport(
static_cast<int>(vp.origin.x), static_cast<int>(vp.origin.y),
static_cast<int>(vp.size.width), static_cast<int>(vp.size.height));
} else {
renderer_->setViewport(0, 0, window_->getWidth(), window_->getHeight());
}
if (viewportAdapter_) {
const auto& vp = viewportAdapter_->getViewport();
renderer_->setViewport(
static_cast<int>(vp.origin.x), static_cast<int>(vp.origin.y),
static_cast<int>(vp.size.width), static_cast<int>(vp.size.height));
} else {
renderer_->setViewport(0, 0, window_->width(), window_->height());
}
if (sceneManager_) {
sceneManager_->render(*renderer_);
} else {
E2D_LOG_WARN("Render: sceneManager is null");
}
if (sceneManager_) {
sceneManager_->render(*renderer_);
} else {
E2D_LOG_WARN("Render: sceneManager is null");
}
window_->swapBuffers();
window_->swap();
}
/**
* @brief
* @return
*/
Input &Application::input() { return *window_->getInput(); }
IInput& Application::input() { return *window_->input(); }
/**
* @brief
* @return
*/
SceneManager &Application::scenes() { return *sceneManager_; }
SceneManager& Application::scenes() { return *sceneManager_; }
/**
* @brief
* @return
*/
TimerManager &Application::timers() { return *timerManager_; }
TimerManager& Application::timers() { return *timerManager_; }
/**
* @brief
* @return
*/
EventQueue &Application::eventQueue() { return *eventQueue_; }
EventQueue& Application::eventQueue() { return *eventQueue_; }
/**
* @brief
* @return
*/
EventDispatcher &Application::eventDispatcher() { return *eventDispatcher_; }
EventDispatcher& Application::eventDispatcher() { return *eventDispatcher_; }
/**
* @brief
* @return
*/
Camera &Application::camera() { return *camera_; }
Camera& Application::camera() { return *camera_; }
/**
* @brief
* @return
*/
ViewportAdapter &Application::viewportAdapter() { return *viewportAdapter_; }
ViewportAdapter& Application::viewportAdapter() { return *viewportAdapter_; }
/**
* @brief
* @param scene
*
*
*/
void Application::enterScene(Ptr<Scene> scene) {
if (sceneManager_ && scene) {
scene->setViewportSize(static_cast<float>(window_->getWidth()),
static_cast<float>(window_->getHeight()));
sceneManager_->enterScene(scene);
}
if (sceneManager_ && scene) {
scene->setViewportSize(static_cast<float>(window_->width()),
static_cast<float>(window_->height()));
sceneManager_->enterScene(scene);
}
}
} // namespace extra2d

View File

@ -7,7 +7,7 @@
#include <extra2d/graphics/opengl/gl_renderer.h>
#include <extra2d/graphics/opengl/gl_texture.h>
#include <extra2d/graphics/vram_manager.h>
#include <extra2d/platform/window.h>
#include <extra2d/platform/iwindow.h>
#include <extra2d/utils/logger.h>
#include <vector>
@ -85,7 +85,7 @@ GLRenderer::~GLRenderer() { shutdown(); }
* @param window
* @return truefalse
*/
bool GLRenderer::init(Window *window) {
bool GLRenderer::init(IWindow* window) {
window_ = window;
// Switch: GL 上下文已通过 SDL2 + EGL 初始化,无需 glewInit()

View File

@ -0,0 +1,9 @@
#include "sdl2_window.h"
#include "sdl2_input.h"
#include <extra2d/platform/platform_module.h>
namespace extra2d {
E2D_REG_BACKEND(sdl2, SDL2Window, SDL2Input)
} // namespace extra2d

View File

@ -0,0 +1,386 @@
#include "sdl2_input.h"
#include <extra2d/utils/logger.h>
#include <cmath>
namespace extra2d {
SDL2Input::SDL2Input() {
keyCurrent_.fill(false);
keyPrevious_.fill(false);
mouseCurrent_.fill(false);
mousePrevious_.fill(false);
gamepadCurrent_.fill(false);
gamepadPrevious_.fill(false);
}
SDL2Input::~SDL2Input() {
shutdown();
}
void SDL2Input::init() {
SDL_GameControllerEventState(SDL_ENABLE);
openGamepad();
E2D_LOG_DEBUG("SDL2 input initialized");
}
void SDL2Input::shutdown() {
closeGamepad();
}
void SDL2Input::update() {
keyPrevious_ = keyCurrent_;
mousePrevious_ = mouseCurrent_;
gamepadPrevious_ = gamepadCurrent_;
scrollDelta_ = 0.0f;
mouseDelta_ = Vec2::Zero();
updateKeyboard();
updateMouse();
updateGamepad();
}
bool SDL2Input::down(Key key) const {
size_t idx = static_cast<size_t>(key);
return idx < keyCurrent_.size() ? keyCurrent_[idx] : false;
}
bool SDL2Input::pressed(Key key) const {
size_t idx = static_cast<size_t>(key);
return idx < keyCurrent_.size() ? (keyCurrent_[idx] && !keyPrevious_[idx]) : false;
}
bool SDL2Input::released(Key key) const {
size_t idx = static_cast<size_t>(key);
return idx < keyCurrent_.size() ? (!keyCurrent_[idx] && keyPrevious_[idx]) : false;
}
bool SDL2Input::down(Mouse btn) const {
size_t idx = static_cast<size_t>(btn);
return idx < mouseCurrent_.size() ? mouseCurrent_[idx] : false;
}
bool SDL2Input::pressed(Mouse btn) const {
size_t idx = static_cast<size_t>(btn);
return idx < mouseCurrent_.size() ? (mouseCurrent_[idx] && !mousePrevious_[idx]) : false;
}
bool SDL2Input::released(Mouse btn) const {
size_t idx = static_cast<size_t>(btn);
return idx < mouseCurrent_.size() ? (!mouseCurrent_[idx] && mousePrevious_[idx]) : false;
}
Vec2 SDL2Input::mouse() const {
return mousePos_;
}
Vec2 SDL2Input::mouseDelta() const {
return mouseDelta_;
}
float SDL2Input::scroll() const {
return scroll_;
}
float SDL2Input::scrollDelta() const {
return scrollDelta_;
}
void SDL2Input::setMouse(const Vec2& pos) {
SDL_WarpMouseInWindow(nullptr, static_cast<int>(pos.x), static_cast<int>(pos.y));
}
bool SDL2Input::gamepad() const {
return gamepad_ != nullptr;
}
bool SDL2Input::down(Gamepad btn) const {
size_t idx = static_cast<size_t>(btn);
return idx < gamepadCurrent_.size() ? gamepadCurrent_[idx] : false;
}
bool SDL2Input::pressed(Gamepad btn) const {
size_t idx = static_cast<size_t>(btn);
return idx < gamepadCurrent_.size() ? (gamepadCurrent_[idx] && !gamepadPrevious_[idx]) : false;
}
bool SDL2Input::released(Gamepad btn) const {
size_t idx = static_cast<size_t>(btn);
return idx < gamepadCurrent_.size() ? (!gamepadCurrent_[idx] && gamepadPrevious_[idx]) : false;
}
Vec2 SDL2Input::leftStick() const {
return leftStick_;
}
Vec2 SDL2Input::rightStick() const {
return rightStick_;
}
float SDL2Input::leftTrigger() const {
return leftTrigger_;
}
float SDL2Input::rightTrigger() const {
return rightTrigger_;
}
void SDL2Input::vibrate(float left, float right) {
if (gamepad_) {
Uint16 lowFreq = static_cast<Uint16>(std::clamp(left, 0.0f, 1.0f) * 65535);
Uint16 highFreq = static_cast<Uint16>(std::clamp(right, 0.0f, 1.0f) * 65535);
SDL_GameControllerRumble(gamepad_, lowFreq, highFreq, 0);
}
}
bool SDL2Input::touching() const {
return false;
}
int SDL2Input::touchCount() const {
return 0;
}
Vec2 SDL2Input::touch(int index) const {
(void)index;
return Vec2::Zero();
}
TouchPoint SDL2Input::touchPoint(int index) const {
(void)index;
return TouchPoint{};
}
void SDL2Input::updateKeyboard() {
int numKeys = 0;
const Uint8* state = SDL_GetKeyboardState(&numKeys);
auto updateKey = [&](Key key, int sdlScancode) {
size_t idx = static_cast<size_t>(key);
if (idx < keyCurrent_.size() && sdlScancode < numKeys) {
keyCurrent_[idx] = state[sdlScancode] != 0;
}
};
updateKey(Key::A, SDL_SCANCODE_A);
updateKey(Key::B, SDL_SCANCODE_B);
updateKey(Key::C, SDL_SCANCODE_C);
updateKey(Key::D, SDL_SCANCODE_D);
updateKey(Key::E, SDL_SCANCODE_E);
updateKey(Key::F, SDL_SCANCODE_F);
updateKey(Key::G, SDL_SCANCODE_G);
updateKey(Key::H, SDL_SCANCODE_H);
updateKey(Key::I, SDL_SCANCODE_I);
updateKey(Key::J, SDL_SCANCODE_J);
updateKey(Key::K, SDL_SCANCODE_K);
updateKey(Key::L, SDL_SCANCODE_L);
updateKey(Key::M, SDL_SCANCODE_M);
updateKey(Key::N, SDL_SCANCODE_N);
updateKey(Key::O, SDL_SCANCODE_O);
updateKey(Key::P, SDL_SCANCODE_P);
updateKey(Key::Q, SDL_SCANCODE_Q);
updateKey(Key::R, SDL_SCANCODE_R);
updateKey(Key::S, SDL_SCANCODE_S);
updateKey(Key::T, SDL_SCANCODE_T);
updateKey(Key::U, SDL_SCANCODE_U);
updateKey(Key::V, SDL_SCANCODE_V);
updateKey(Key::W, SDL_SCANCODE_W);
updateKey(Key::X, SDL_SCANCODE_X);
updateKey(Key::Y, SDL_SCANCODE_Y);
updateKey(Key::Z, SDL_SCANCODE_Z);
updateKey(Key::Num0, SDL_SCANCODE_0);
updateKey(Key::Num1, SDL_SCANCODE_1);
updateKey(Key::Num2, SDL_SCANCODE_2);
updateKey(Key::Num3, SDL_SCANCODE_3);
updateKey(Key::Num4, SDL_SCANCODE_4);
updateKey(Key::Num5, SDL_SCANCODE_5);
updateKey(Key::Num6, SDL_SCANCODE_6);
updateKey(Key::Num7, SDL_SCANCODE_7);
updateKey(Key::Num8, SDL_SCANCODE_8);
updateKey(Key::Num9, SDL_SCANCODE_9);
updateKey(Key::F1, SDL_SCANCODE_F1);
updateKey(Key::F2, SDL_SCANCODE_F2);
updateKey(Key::F3, SDL_SCANCODE_F3);
updateKey(Key::F4, SDL_SCANCODE_F4);
updateKey(Key::F5, SDL_SCANCODE_F5);
updateKey(Key::F6, SDL_SCANCODE_F6);
updateKey(Key::F7, SDL_SCANCODE_F7);
updateKey(Key::F8, SDL_SCANCODE_F8);
updateKey(Key::F9, SDL_SCANCODE_F9);
updateKey(Key::F10, SDL_SCANCODE_F10);
updateKey(Key::F11, SDL_SCANCODE_F11);
updateKey(Key::F12, SDL_SCANCODE_F12);
updateKey(Key::Space, SDL_SCANCODE_SPACE);
updateKey(Key::Enter, SDL_SCANCODE_RETURN);
updateKey(Key::Escape, SDL_SCANCODE_ESCAPE);
updateKey(Key::Tab, SDL_SCANCODE_TAB);
updateKey(Key::Backspace, SDL_SCANCODE_BACKSPACE);
updateKey(Key::Insert, SDL_SCANCODE_INSERT);
updateKey(Key::Delete, SDL_SCANCODE_DELETE);
updateKey(Key::Home, SDL_SCANCODE_HOME);
updateKey(Key::End, SDL_SCANCODE_END);
updateKey(Key::PageUp, SDL_SCANCODE_PAGEUP);
updateKey(Key::PageDown, SDL_SCANCODE_PAGEDOWN);
updateKey(Key::Up, SDL_SCANCODE_UP);
updateKey(Key::Down, SDL_SCANCODE_DOWN);
updateKey(Key::Left, SDL_SCANCODE_LEFT);
updateKey(Key::Right, SDL_SCANCODE_RIGHT);
updateKey(Key::LShift, SDL_SCANCODE_LSHIFT);
updateKey(Key::RShift, SDL_SCANCODE_RSHIFT);
updateKey(Key::LCtrl, SDL_SCANCODE_LCTRL);
updateKey(Key::RCtrl, SDL_SCANCODE_RCTRL);
updateKey(Key::LAlt, SDL_SCANCODE_LALT);
updateKey(Key::RAlt, SDL_SCANCODE_RALT);
updateKey(Key::CapsLock, SDL_SCANCODE_CAPSLOCK);
updateKey(Key::NumLock, SDL_SCANCODE_NUMLOCKCLEAR);
updateKey(Key::ScrollLock, SDL_SCANCODE_SCROLLLOCK);
}
void SDL2Input::updateMouse() {
int x, y;
Uint32 state = SDL_GetMouseState(&x, &y);
Vec2 newPos(static_cast<float>(x), static_cast<float>(y));
mouseDelta_ = newPos - mousePos_;
mousePos_ = newPos;
mouseCurrent_[static_cast<size_t>(Mouse::Left)] = (state & SDL_BUTTON_LMASK) != 0;
mouseCurrent_[static_cast<size_t>(Mouse::Right)] = (state & SDL_BUTTON_RMASK) != 0;
mouseCurrent_[static_cast<size_t>(Mouse::Middle)] = (state & SDL_BUTTON_MMASK) != 0;
mouseCurrent_[static_cast<size_t>(Mouse::X1)] = (state & SDL_BUTTON_X1MASK) != 0;
mouseCurrent_[static_cast<size_t>(Mouse::X2)] = (state & SDL_BUTTON_X2MASK) != 0;
}
void SDL2Input::updateGamepad() {
if (!gamepad_) {
openGamepad();
if (!gamepad_) return;
}
auto applyDeadzone = [this](float value) -> float {
if (std::abs(value) < deadzone_) return 0.0f;
float sign = value >= 0 ? 1.0f : -1.0f;
return sign * (std::abs(value) - deadzone_) / (1.0f - deadzone_);
};
auto getAxis = [this](SDL_GameControllerAxis axis) -> float {
if (!gamepad_) return 0.0f;
Sint16 value = SDL_GameControllerGetAxis(gamepad_, axis);
return static_cast<float>(value) / 32767.0f;
};
auto getButton = [this](SDL_GameControllerButton btn) -> bool {
return gamepad_ ? SDL_GameControllerGetButton(gamepad_, btn) != 0 : false;
};
leftStick_.x = applyDeadzone(getAxis(SDL_CONTROLLER_AXIS_LEFTX));
leftStick_.y = applyDeadzone(getAxis(SDL_CONTROLLER_AXIS_LEFTY));
rightStick_.x = applyDeadzone(getAxis(SDL_CONTROLLER_AXIS_RIGHTX));
rightStick_.y = applyDeadzone(getAxis(SDL_CONTROLLER_AXIS_RIGHTY));
leftTrigger_ = getAxis(SDL_CONTROLLER_AXIS_TRIGGERLEFT);
rightTrigger_ = getAxis(SDL_CONTROLLER_AXIS_TRIGGERRIGHT);
gamepadCurrent_[static_cast<size_t>(Gamepad::A)] = getButton(SDL_CONTROLLER_BUTTON_A);
gamepadCurrent_[static_cast<size_t>(Gamepad::B)] = getButton(SDL_CONTROLLER_BUTTON_B);
gamepadCurrent_[static_cast<size_t>(Gamepad::X)] = getButton(SDL_CONTROLLER_BUTTON_X);
gamepadCurrent_[static_cast<size_t>(Gamepad::Y)] = getButton(SDL_CONTROLLER_BUTTON_Y);
gamepadCurrent_[static_cast<size_t>(Gamepad::LB)] = getButton(SDL_CONTROLLER_BUTTON_LEFTSHOULDER);
gamepadCurrent_[static_cast<size_t>(Gamepad::RB)] = getButton(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER);
gamepadCurrent_[static_cast<size_t>(Gamepad::Back)] = getButton(SDL_CONTROLLER_BUTTON_BACK);
gamepadCurrent_[static_cast<size_t>(Gamepad::Start)] = getButton(SDL_CONTROLLER_BUTTON_START);
gamepadCurrent_[static_cast<size_t>(Gamepad::Guide)] = getButton(SDL_CONTROLLER_BUTTON_GUIDE);
gamepadCurrent_[static_cast<size_t>(Gamepad::LStick)] = getButton(SDL_CONTROLLER_BUTTON_LEFTSTICK);
gamepadCurrent_[static_cast<size_t>(Gamepad::RStick)] = getButton(SDL_CONTROLLER_BUTTON_RIGHTSTICK);
gamepadCurrent_[static_cast<size_t>(Gamepad::DUp)] = getButton(SDL_CONTROLLER_BUTTON_DPAD_UP);
gamepadCurrent_[static_cast<size_t>(Gamepad::DDown)] = getButton(SDL_CONTROLLER_BUTTON_DPAD_DOWN);
gamepadCurrent_[static_cast<size_t>(Gamepad::DLeft)] = getButton(SDL_CONTROLLER_BUTTON_DPAD_LEFT);
gamepadCurrent_[static_cast<size_t>(Gamepad::DRight)] = getButton(SDL_CONTROLLER_BUTTON_DPAD_RIGHT);
}
void SDL2Input::openGamepad() {
for (int i = 0; i < SDL_NumJoysticks(); ++i) {
if (SDL_IsGameController(i)) {
gamepad_ = SDL_GameControllerOpen(i);
if (gamepad_) {
gamepadIndex_ = i;
E2D_LOG_INFO("Gamepad connected: {}", SDL_GameControllerName(gamepad_));
break;
}
}
}
}
void SDL2Input::closeGamepad() {
if (gamepad_) {
SDL_GameControllerClose(gamepad_);
gamepad_ = nullptr;
gamepadIndex_ = -1;
E2D_LOG_INFO("Gamepad disconnected");
}
}
int SDL2Input::keyToSDL(Key key) {
switch (key) {
case Key::A: return SDL_SCANCODE_A;
case Key::B: return SDL_SCANCODE_B;
case Key::C: return SDL_SCANCODE_C;
case Key::D: return SDL_SCANCODE_D;
case Key::E: return SDL_SCANCODE_E;
case Key::F: return SDL_SCANCODE_F;
case Key::G: return SDL_SCANCODE_G;
case Key::H: return SDL_SCANCODE_H;
case Key::I: return SDL_SCANCODE_I;
case Key::J: return SDL_SCANCODE_J;
case Key::K: return SDL_SCANCODE_K;
case Key::L: return SDL_SCANCODE_L;
case Key::M: return SDL_SCANCODE_M;
case Key::N: return SDL_SCANCODE_N;
case Key::O: return SDL_SCANCODE_O;
case Key::P: return SDL_SCANCODE_P;
case Key::Q: return SDL_SCANCODE_Q;
case Key::R: return SDL_SCANCODE_R;
case Key::S: return SDL_SCANCODE_S;
case Key::T: return SDL_SCANCODE_T;
case Key::U: return SDL_SCANCODE_U;
case Key::V: return SDL_SCANCODE_V;
case Key::W: return SDL_SCANCODE_W;
case Key::X: return SDL_SCANCODE_X;
case Key::Y: return SDL_SCANCODE_Y;
case Key::Z: return SDL_SCANCODE_Z;
default: return SDL_SCANCODE_UNKNOWN;
}
}
int SDL2Input::mouseToSDL(Mouse btn) {
switch (btn) {
case Mouse::Left: return SDL_BUTTON_LEFT;
case Mouse::Right: return SDL_BUTTON_RIGHT;
case Mouse::Middle: return SDL_BUTTON_MIDDLE;
case Mouse::X1: return SDL_BUTTON_X1;
case Mouse::X2: return SDL_BUTTON_X2;
default: return 0;
}
}
int SDL2Input::gamepadToSDL(Gamepad btn) {
switch (btn) {
case Gamepad::A: return SDL_CONTROLLER_BUTTON_A;
case Gamepad::B: return SDL_CONTROLLER_BUTTON_B;
case Gamepad::X: return SDL_CONTROLLER_BUTTON_X;
case Gamepad::Y: return SDL_CONTROLLER_BUTTON_Y;
case Gamepad::LB: return SDL_CONTROLLER_BUTTON_LEFTSHOULDER;
case Gamepad::RB: return SDL_CONTROLLER_BUTTON_RIGHTSHOULDER;
case Gamepad::Back: return SDL_CONTROLLER_BUTTON_BACK;
case Gamepad::Start: return SDL_CONTROLLER_BUTTON_START;
case Gamepad::Guide: return SDL_CONTROLLER_BUTTON_GUIDE;
case Gamepad::LStick: return SDL_CONTROLLER_BUTTON_LEFTSTICK;
case Gamepad::RStick: return SDL_CONTROLLER_BUTTON_RIGHTSTICK;
case Gamepad::DUp: return SDL_CONTROLLER_BUTTON_DPAD_UP;
case Gamepad::DDown: return SDL_CONTROLLER_BUTTON_DPAD_DOWN;
case Gamepad::DLeft: return SDL_CONTROLLER_BUTTON_DPAD_LEFT;
case Gamepad::DRight: return SDL_CONTROLLER_BUTTON_DPAD_RIGHT;
default: return SDL_CONTROLLER_BUTTON_INVALID;
}
}
} // namespace extra2d

View File

@ -0,0 +1,82 @@
#pragma once
#include <extra2d/platform/iinput.h>
#include <SDL.h>
#include <array>
namespace extra2d {
/**
* @brief SDL2
*/
class SDL2Input : public IInput {
public:
SDL2Input();
~SDL2Input() override;
void init() override;
void shutdown() override;
void update() override;
bool down(Key key) const override;
bool pressed(Key key) const override;
bool released(Key key) const override;
bool down(Mouse btn) const override;
bool pressed(Mouse btn) const override;
bool released(Mouse btn) const override;
Vec2 mouse() const override;
Vec2 mouseDelta() const override;
float scroll() const override;
float scrollDelta() const override;
void setMouse(const Vec2& pos) override;
bool gamepad() const override;
bool down(Gamepad btn) const override;
bool pressed(Gamepad btn) const override;
bool released(Gamepad btn) const override;
Vec2 leftStick() const override;
Vec2 rightStick() const override;
float leftTrigger() const override;
float rightTrigger() const override;
void vibrate(float left, float right) override;
bool touching() const override;
int touchCount() const override;
Vec2 touch(int index) const override;
TouchPoint touchPoint(int index) const override;
private:
void updateKeyboard();
void updateMouse();
void updateGamepad();
void openGamepad();
void closeGamepad();
static int keyToSDL(Key key);
static int mouseToSDL(Mouse btn);
static int gamepadToSDL(Gamepad btn);
std::array<bool, static_cast<size_t>(Key::Count)> keyCurrent_{};
std::array<bool, static_cast<size_t>(Key::Count)> keyPrevious_{};
std::array<bool, static_cast<size_t>(Mouse::Count)> mouseCurrent_{};
std::array<bool, static_cast<size_t>(Mouse::Count)> mousePrevious_{};
Vec2 mousePos_;
Vec2 mouseDelta_;
float scroll_ = 0.0f;
float scrollDelta_ = 0.0f;
SDL_GameController* gamepad_ = nullptr;
int gamepadIndex_ = -1;
std::array<bool, static_cast<size_t>(Gamepad::Count)> gamepadCurrent_{};
std::array<bool, static_cast<size_t>(Gamepad::Count)> gamepadPrevious_{};
Vec2 leftStick_;
Vec2 rightStick_;
float leftTrigger_ = 0.0f;
float rightTrigger_ = 0.0f;
float deadzone_ = 0.15f;
};
} // namespace extra2d

View File

@ -0,0 +1,368 @@
#include "sdl2_window.h"
#include "sdl2_input.h"
#include <extra2d/utils/logger.h>
namespace extra2d {
SDL2Window::SDL2Window() {
for (int i = 0; i < 7; ++i) {
sdlCursors_[i] = nullptr;
}
}
SDL2Window::~SDL2Window() {
destroy();
}
bool SDL2Window::create(const WindowConfig& cfg) {
if (!initSDL()) {
return false;
}
Uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN;
if (cfg.fullscreen) {
flags |= cfg.fullscreenDesktop ? SDL_WINDOW_FULLSCREEN_DESKTOP : SDL_WINDOW_FULLSCREEN;
}
if (cfg.resizable) {
flags |= SDL_WINDOW_RESIZABLE;
}
if (!cfg.decorated) {
flags |= SDL_WINDOW_BORDERLESS;
}
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
if (cfg.msaaSamples > 0) {
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, cfg.msaaSamples);
}
int x = SDL_WINDOWPOS_CENTERED;
int y = SDL_WINDOWPOS_CENTERED;
if (!cfg.centerWindow) {
x = SDL_WINDOWPOS_UNDEFINED;
y = SDL_WINDOWPOS_UNDEFINED;
}
sdlWindow_ = SDL_CreateWindow(
cfg.title.c_str(),
x, y,
cfg.width, cfg.height,
flags
);
if (!sdlWindow_) {
E2D_LOG_ERROR("Failed to create SDL window: {}", SDL_GetError());
deinitSDL();
return false;
}
glContext_ = SDL_GL_CreateContext(sdlWindow_);
if (!glContext_) {
E2D_LOG_ERROR("Failed to create OpenGL context: {}", SDL_GetError());
SDL_DestroyWindow(sdlWindow_);
sdlWindow_ = nullptr;
deinitSDL();
return false;
}
SDL_GL_SetSwapInterval(cfg.vsync ? 1 : 0);
SDL_GetWindowSize(sdlWindow_, &width_, &height_);
fullscreen_ = cfg.fullscreen;
vsync_ = cfg.vsync;
initCursors();
updateContentScale();
input_ = makeUnique<SDL2Input>();
input_->init();
E2D_LOG_INFO("SDL2 window created: {}x{}", width_, height_);
return true;
}
void SDL2Window::destroy() {
if (input_) {
input_->shutdown();
input_.reset();
}
deinitCursors();
if (glContext_) {
SDL_GL_DeleteContext(glContext_);
glContext_ = nullptr;
}
if (sdlWindow_) {
SDL_DestroyWindow(sdlWindow_);
sdlWindow_ = nullptr;
}
deinitSDL();
}
void SDL2Window::poll() {
if (!sdlWindow_) return;
if (input_) {
input_->update();
}
SDL_Event event;
while (SDL_PollEvent(&event)) {
handleEvent(event);
}
}
void SDL2Window::swap() {
if (sdlWindow_ && glContext_) {
SDL_GL_SwapWindow(sdlWindow_);
}
}
bool SDL2Window::shouldClose() const {
return shouldClose_;
}
void SDL2Window::close() {
shouldClose_ = true;
}
void SDL2Window::setTitle(const std::string& title) {
if (sdlWindow_) {
SDL_SetWindowTitle(sdlWindow_, title.c_str());
}
}
void SDL2Window::setSize(int w, int h) {
if (sdlWindow_) {
SDL_SetWindowSize(sdlWindow_, w, h);
width_ = w;
height_ = h;
}
}
void SDL2Window::setPos(int x, int y) {
if (sdlWindow_) {
SDL_SetWindowPosition(sdlWindow_, x, y);
}
}
void SDL2Window::setFullscreen(bool fs) {
if (sdlWindow_) {
SDL_SetWindowFullscreen(sdlWindow_, fs ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
fullscreen_ = fs;
}
}
void SDL2Window::setVSync(bool vsync) {
if (glContext_) {
SDL_GL_SetSwapInterval(vsync ? 1 : 0);
vsync_ = vsync;
}
}
void SDL2Window::setVisible(bool visible) {
if (sdlWindow_) {
if (visible) {
SDL_ShowWindow(sdlWindow_);
} else {
SDL_HideWindow(sdlWindow_);
}
}
}
int SDL2Window::width() const {
return width_;
}
int SDL2Window::height() const {
return height_;
}
Size SDL2Window::size() const {
return Size(static_cast<float>(width_), static_cast<float>(height_));
}
Vec2 SDL2Window::pos() const {
int x, y;
if (sdlWindow_) {
SDL_GetWindowPosition(sdlWindow_, &x, &y);
} else {
x = y = 0;
}
return Vec2(static_cast<float>(x), static_cast<float>(y));
}
bool SDL2Window::fullscreen() const {
return fullscreen_;
}
bool SDL2Window::vsync() const {
return vsync_;
}
bool SDL2Window::focused() const {
return focused_;
}
bool SDL2Window::minimized() const {
return minimized_;
}
float SDL2Window::scaleX() const {
return scaleX_;
}
float SDL2Window::scaleY() const {
return scaleY_;
}
void SDL2Window::setCursor(Cursor cursor) {
if (cursor == Cursor::Hidden) {
SDL_ShowCursor(SDL_DISABLE);
return;
}
SDL_ShowCursor(SDL_ENABLE);
int idx = static_cast<int>(cursor);
if (idx >= 0 && idx < 7 && sdlCursors_[idx]) {
SDL_SetCursor(sdlCursors_[idx]);
currentCursor_ = idx;
}
}
void SDL2Window::showCursor(bool show) {
SDL_ShowCursor(show ? SDL_ENABLE : SDL_DISABLE);
cursorVisible_ = show;
}
void SDL2Window::lockCursor(bool lock) {
if (sdlWindow_) {
SDL_SetRelativeMouseMode(lock ? SDL_TRUE : SDL_FALSE);
cursorLocked_ = lock;
}
}
IInput* SDL2Window::input() const {
return input_.get();
}
void SDL2Window::onResize(ResizeCb cb) {
resizeCb_ = cb;
}
void SDL2Window::onClose(CloseCb cb) {
closeCb_ = cb;
}
void SDL2Window::onFocus(FocusCb cb) {
focusCb_ = cb;
}
void* SDL2Window::native() const {
return sdlWindow_;
}
bool SDL2Window::initSDL() {
static int sdlInitCount = 0;
if (sdlInitCount == 0) {
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) != 0) {
E2D_LOG_ERROR("Failed to initialize SDL: {}", SDL_GetError());
return false;
}
sdlInitCount++;
}
return true;
}
void SDL2Window::deinitSDL() {
static int sdlInitCount = 1;
sdlInitCount--;
if (sdlInitCount == 0) {
SDL_Quit();
}
}
void SDL2Window::initCursors() {
sdlCursors_[0] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW);
sdlCursors_[1] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM);
sdlCursors_[2] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_CROSSHAIR);
sdlCursors_[3] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND);
sdlCursors_[4] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS);
sdlCursors_[5] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE);
sdlCursors_[6] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL);
}
void SDL2Window::deinitCursors() {
for (int i = 0; i < 7; ++i) {
if (sdlCursors_[i]) {
SDL_FreeCursor(sdlCursors_[i]);
sdlCursors_[i] = nullptr;
}
}
}
void SDL2Window::updateContentScale() {
if (sdlWindow_) {
SDL_GetWindowSize(sdlWindow_, &width_, &height_);
int dw, dh;
SDL_GL_GetDrawableSize(sdlWindow_, &dw, &dh);
scaleX_ = dw > 0 ? static_cast<float>(dw) / width_ : 1.0f;
scaleY_ = dh > 0 ? static_cast<float>(dh) / height_ : 1.0f;
}
}
void SDL2Window::handleEvent(const SDL_Event& event) {
switch (event.type) {
case SDL_QUIT:
shouldClose_ = true;
if (closeCb_) closeCb_();
break;
case SDL_WINDOWEVENT:
switch (event.window.event) {
case SDL_WINDOWEVENT_RESIZED:
case SDL_WINDOWEVENT_SIZE_CHANGED:
width_ = event.window.data1;
height_ = event.window.data2;
updateContentScale();
if (resizeCb_) resizeCb_(width_, height_);
break;
case SDL_WINDOWEVENT_FOCUS_GAINED:
focused_ = true;
if (focusCb_) focusCb_(true);
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
focused_ = false;
if (focusCb_) focusCb_(false);
break;
case SDL_WINDOWEVENT_MINIMIZED:
minimized_ = true;
break;
case SDL_WINDOWEVENT_RESTORED:
minimized_ = false;
break;
case SDL_WINDOWEVENT_CLOSE:
shouldClose_ = true;
if (closeCb_) closeCb_();
break;
}
break;
}
}
} // namespace extra2d

View File

@ -0,0 +1,99 @@
#pragma once
#include <extra2d/platform/iwindow.h>
#include <SDL.h>
namespace extra2d {
class SDL2Input;
/**
* @brief SDL2
*/
class SDL2Window : public IWindow {
public:
SDL2Window();
~SDL2Window() override;
bool create(const WindowConfig& cfg) override;
void destroy() override;
void poll() override;
void swap() override;
bool shouldClose() const override;
void close() override;
void setTitle(const std::string& title) override;
void setSize(int w, int h) override;
void setPos(int x, int y) override;
void setFullscreen(bool fs) override;
void setVSync(bool vsync) override;
void setVisible(bool visible) override;
int width() const override;
int height() const override;
Size size() const override;
Vec2 pos() const override;
bool fullscreen() const override;
bool vsync() const override;
bool focused() const override;
bool minimized() const override;
float scaleX() const override;
float scaleY() const override;
void setCursor(Cursor cursor) override;
void showCursor(bool show) override;
void lockCursor(bool lock) override;
IInput* input() const override;
void onResize(ResizeCb cb) override;
void onClose(CloseCb cb) override;
void onFocus(FocusCb cb) override;
void* native() const override;
/**
* @brief SDL
*/
SDL_Window* sdlWindow() const { return sdlWindow_; }
/**
* @brief OpenGL
*/
SDL_GLContext glContext() const { return glContext_; }
private:
bool initSDL();
void deinitSDL();
void initCursors();
void deinitCursors();
void updateContentScale();
void handleEvent(const SDL_Event& event);
SDL_Window* sdlWindow_ = nullptr;
SDL_GLContext glContext_ = nullptr;
SDL_Cursor* sdlCursors_[7] = {};
int currentCursor_ = 0;
UniquePtr<SDL2Input> input_;
int width_ = 1280;
int height_ = 720;
bool fullscreen_ = false;
bool vsync_ = true;
bool focused_ = true;
bool minimized_ = false;
bool shouldClose_ = false;
float scaleX_ = 1.0f;
float scaleY_ = 1.0f;
bool cursorVisible_ = true;
bool cursorLocked_ = false;
ResizeCb resizeCb_;
CloseCb closeCb_;
FocusCb focusCb_;
};
} // namespace extra2d

View File

@ -1,665 +0,0 @@
#include <extra2d/event/input_codes.h>
#include <extra2d/graphics/viewport_adapter.h>
#include <extra2d/platform/input.h>
#include <extra2d/utils/logger.h>
namespace extra2d {
/**
* @brief
*
*
* false
*/
Input::Input()
: controller_(nullptr),
leftStickX_(0.0f), leftStickY_(0.0f),
rightStickX_(0.0f), rightStickY_(0.0f),
mouseScroll_(0.0f), prevMouseScroll_(0.0f),
touching_(false), prevTouching_(false), touchCount_(0),
viewportAdapter_(nullptr) {
keysDown_.fill(false);
prevKeysDown_.fill(false);
buttonsDown_.fill(false);
prevButtonsDown_.fill(false);
mouseButtonsDown_.fill(false);
prevMouseButtonsDown_.fill(false);
}
/**
* @brief
*
* shutdown()
*/
Input::~Input() { shutdown(); }
/**
* @brief
*
* PC端获取初始鼠标位置
*/
void Input::init() {
for (int i = 0; i < SDL_NumJoysticks(); ++i) {
if (SDL_IsGameController(i)) {
controller_ = SDL_GameControllerOpen(i);
if (controller_) {
E2D_LOG_INFO("GameController opened: {}",
SDL_GameControllerName(controller_));
break;
}
}
}
if (!controller_) {
E2D_LOG_WARN("No game controller found");
}
#ifndef PLATFORM_SWITCH
int mouseX, mouseY;
SDL_GetMouseState(&mouseX, &mouseY);
mousePosition_ = Vec2(static_cast<float>(mouseX), static_cast<float>(mouseY));
prevMousePosition_ = mousePosition_;
#endif
}
/**
* @brief
*
*
*/
void Input::shutdown() {
if (controller_) {
SDL_GameControllerClose(controller_);
controller_ = nullptr;
}
}
/**
* @brief
*
*
*/
void Input::update() {
prevKeysDown_ = keysDown_;
prevButtonsDown_ = buttonsDown_;
prevMouseButtonsDown_ = mouseButtonsDown_;
prevMousePosition_ = mousePosition_;
prevMouseScroll_ = mouseScroll_;
prevTouching_ = touching_;
prevTouchPosition_ = touchPosition_;
updateKeyboard();
updateMouse();
updateGamepad();
updateTouch();
}
/**
* @brief
*
* SDL获取当前键盘状态并更新按键数组
*/
void Input::updateKeyboard() {
const Uint8* state = SDL_GetKeyboardState(nullptr);
for (int i = 0; i < MAX_KEYS; ++i) {
keysDown_[i] = state[i] != 0;
}
}
/**
* @brief
*
* Switch平台执行
*/
void Input::updateMouse() {
#ifndef PLATFORM_SWITCH
int mouseX, mouseY;
Uint32 buttonState = SDL_GetMouseState(&mouseX, &mouseY);
mousePosition_ = Vec2(static_cast<float>(mouseX), static_cast<float>(mouseY));
mouseButtonsDown_[0] = (buttonState & SDL_BUTTON(SDL_BUTTON_LEFT)) != 0;
mouseButtonsDown_[1] = (buttonState & SDL_BUTTON(SDL_BUTTON_RIGHT)) != 0;
mouseButtonsDown_[2] = (buttonState & SDL_BUTTON(SDL_BUTTON_MIDDLE)) != 0;
mouseButtonsDown_[3] = (buttonState & SDL_BUTTON(SDL_BUTTON_X1)) != 0;
mouseButtonsDown_[4] = (buttonState & SDL_BUTTON(SDL_BUTTON_X2)) != 0;
#endif
}
/**
* @brief
*
* -1.0~1.0
*/
void Input::updateGamepad() {
if (controller_) {
for (int i = 0; i < MAX_BUTTONS; ++i) {
buttonsDown_[i] =
SDL_GameControllerGetButton(
controller_, static_cast<SDL_GameControllerButton>(i)) != 0;
}
leftStickX_ = static_cast<float>(SDL_GameControllerGetAxis(
controller_, SDL_CONTROLLER_AXIS_LEFTX)) / 32767.0f;
leftStickY_ = static_cast<float>(SDL_GameControllerGetAxis(
controller_, SDL_CONTROLLER_AXIS_LEFTY)) / 32767.0f;
rightStickX_ = static_cast<float>(SDL_GameControllerGetAxis(
controller_, SDL_CONTROLLER_AXIS_RIGHTX)) / 32767.0f;
rightStickY_ = static_cast<float>(SDL_GameControllerGetAxis(
controller_, SDL_CONTROLLER_AXIS_RIGHTY)) / 32767.0f;
} else {
buttonsDown_.fill(false);
leftStickX_ = leftStickY_ = rightStickX_ = rightStickY_ = 0.0f;
}
}
/**
* @brief
*
*
* Switch平台使用原生触摸屏支持PC端支持可选触摸设备
*/
void Input::updateTouch() {
#ifdef PLATFORM_SWITCH
SDL_TouchID touchId = SDL_GetTouchDevice(0);
if (touchId != 0) {
touchCount_ = SDL_GetNumTouchFingers(touchId);
if (touchCount_ > 0) {
SDL_Finger *finger = SDL_GetTouchFinger(touchId, 0);
if (finger) {
touching_ = true;
touchPosition_ = Vec2(finger->x * 1280.0f, finger->y * 720.0f);
} else {
touching_ = false;
}
} else {
touching_ = false;
}
} else {
touchCount_ = 0;
touching_ = false;
}
#else
SDL_TouchID touchId = SDL_GetTouchDevice(0);
if (touchId != 0) {
touchCount_ = SDL_GetNumTouchFingers(touchId);
if (touchCount_ > 0) {
SDL_Finger *finger = SDL_GetTouchFinger(touchId, 0);
if (finger) {
touching_ = true;
int windowWidth, windowHeight;
SDL_Window* window = SDL_GL_GetCurrentWindow();
if (window) {
SDL_GetWindowSize(window, &windowWidth, &windowHeight);
touchPosition_ = Vec2(finger->x * windowWidth, finger->y * windowHeight);
} else {
touchPosition_ = Vec2(finger->x * 1280.0f, finger->y * 720.0f);
}
} else {
touching_ = false;
}
} else {
touching_ = false;
}
} else {
touchCount_ = 0;
touching_ = false;
}
#endif
}
// ============================================================================
// 键盘输入
// ============================================================================
/**
* @brief
*
* Switch平台模拟键盘输入
*
* @param keyCode
* @return SDL手柄按钮枚举值
*/
SDL_GameControllerButton Input::mapKeyToButton(int keyCode) const {
switch (keyCode) {
case Key::Up:
return SDL_CONTROLLER_BUTTON_DPAD_UP;
case Key::Down:
return SDL_CONTROLLER_BUTTON_DPAD_DOWN;
case Key::Left:
return SDL_CONTROLLER_BUTTON_DPAD_LEFT;
case Key::Right:
return SDL_CONTROLLER_BUTTON_DPAD_RIGHT;
case Key::W:
return SDL_CONTROLLER_BUTTON_DPAD_UP;
case Key::S:
return SDL_CONTROLLER_BUTTON_DPAD_DOWN;
case Key::A:
return SDL_CONTROLLER_BUTTON_DPAD_LEFT;
case Key::D:
return SDL_CONTROLLER_BUTTON_DPAD_RIGHT;
case Key::Z:
return SDL_CONTROLLER_BUTTON_B;
case Key::X:
return SDL_CONTROLLER_BUTTON_A;
case Key::C:
return SDL_CONTROLLER_BUTTON_Y;
case Key::V:
return SDL_CONTROLLER_BUTTON_X;
case Key::Space:
return SDL_CONTROLLER_BUTTON_A;
case Key::Enter:
return SDL_CONTROLLER_BUTTON_A;
case Key::Escape:
return SDL_CONTROLLER_BUTTON_START;
case Key::Q:
return SDL_CONTROLLER_BUTTON_LEFTSHOULDER;
case Key::E:
return SDL_CONTROLLER_BUTTON_RIGHTSHOULDER;
case Key::Tab:
return SDL_CONTROLLER_BUTTON_BACK;
case Key::Backspace:
return SDL_CONTROLLER_BUTTON_START;
default:
return SDL_CONTROLLER_BUTTON_INVALID;
}
}
/**
* @brief
*
*
* Switch平台映射到手柄按钮PC端直接读取键盘状态
*
* @param keyCode
* @return truefalse
*/
bool Input::isKeyDown(int keyCode) const {
#ifdef PLATFORM_SWITCH
SDL_GameControllerButton button = mapKeyToButton(keyCode);
if (button == SDL_CONTROLLER_BUTTON_INVALID)
return false;
return buttonsDown_[button];
#else
SDL_Scancode scancode = SDL_GetScancodeFromKey(keyCode);
if (scancode >= 0 && scancode < MAX_KEYS) {
return keysDown_[scancode];
}
return false;
#endif
}
/**
* @brief
*
*
*
* @param keyCode
* @return truefalse
*/
bool Input::isKeyPressed(int keyCode) const {
#ifdef PLATFORM_SWITCH
SDL_GameControllerButton button = mapKeyToButton(keyCode);
if (button == SDL_CONTROLLER_BUTTON_INVALID)
return false;
return buttonsDown_[button] && !prevButtonsDown_[button];
#else
SDL_Scancode scancode = SDL_GetScancodeFromKey(keyCode);
if (scancode >= 0 && scancode < MAX_KEYS) {
return keysDown_[scancode] && !prevKeysDown_[scancode];
}
return false;
#endif
}
/**
* @brief
*
*
*
* @param keyCode
* @return truefalse
*/
bool Input::isKeyReleased(int keyCode) const {
#ifdef PLATFORM_SWITCH
SDL_GameControllerButton button = mapKeyToButton(keyCode);
if (button == SDL_CONTROLLER_BUTTON_INVALID)
return false;
return !buttonsDown_[button] && prevButtonsDown_[button];
#else
SDL_Scancode scancode = SDL_GetScancodeFromKey(keyCode);
if (scancode >= 0 && scancode < MAX_KEYS) {
return !keysDown_[scancode] && prevKeysDown_[scancode];
}
return false;
#endif
}
// ============================================================================
// 手柄按钮
// ============================================================================
/**
* @brief
*
* @param button
* @return truefalse
*/
bool Input::isButtonDown(int button) const {
if (button < 0 || button >= MAX_BUTTONS)
return false;
return buttonsDown_[button];
}
/**
* @brief
*
* @param button
* @return truefalse
*/
bool Input::isButtonPressed(int button) const {
if (button < 0 || button >= MAX_BUTTONS)
return false;
return buttonsDown_[button] && !prevButtonsDown_[button];
}
/**
* @brief
*
* @param button
* @return truefalse
*/
bool Input::isButtonReleased(int button) const {
if (button < 0 || button >= MAX_BUTTONS)
return false;
return !buttonsDown_[button] && prevButtonsDown_[button];
}
/**
* @brief
*
* @return X和Y轴值的二维向量-1.0~1.0
*/
Vec2 Input::getLeftStick() const { return Vec2(leftStickX_, leftStickY_); }
/**
* @brief
*
* @return X和Y轴值的二维向量-1.0~1.0
*/
Vec2 Input::getRightStick() const { return Vec2(rightStickX_, rightStickY_); }
// ============================================================================
// 鼠标输入
// ============================================================================
/**
* @brief
*
* Switch平台左键映射到触摸A键
*
* @param button
* @return truefalse
*/
bool Input::isMouseDown(MouseButton button) const {
int index = static_cast<int>(button);
if (index < 0 || index >= 8)
return false;
#ifdef PLATFORM_SWITCH
if (button == MouseButton::Left) {
return touching_;
}
if (button == MouseButton::Right) {
return buttonsDown_[SDL_CONTROLLER_BUTTON_A];
}
return false;
#else
return mouseButtonsDown_[index];
#endif
}
/**
* @brief
*
* @param button
* @return truefalse
*/
bool Input::isMousePressed(MouseButton button) const {
int index = static_cast<int>(button);
if (index < 0 || index >= 8)
return false;
#ifdef PLATFORM_SWITCH
if (button == MouseButton::Left) {
return touching_ && !prevTouching_;
}
if (button == MouseButton::Right) {
return buttonsDown_[SDL_CONTROLLER_BUTTON_A] &&
!prevButtonsDown_[SDL_CONTROLLER_BUTTON_A];
}
return false;
#else
return mouseButtonsDown_[index] && !prevMouseButtonsDown_[index];
#endif
}
/**
* @brief
*
* @param button
* @return truefalse
*/
bool Input::isMouseReleased(MouseButton button) const {
int index = static_cast<int>(button);
if (index < 0 || index >= 8)
return false;
#ifdef PLATFORM_SWITCH
if (button == MouseButton::Left) {
return !touching_ && prevTouching_;
}
if (button == MouseButton::Right) {
return !buttonsDown_[SDL_CONTROLLER_BUTTON_A] &&
prevButtonsDown_[SDL_CONTROLLER_BUTTON_A];
}
return false;
#else
return !mouseButtonsDown_[index] && prevMouseButtonsDown_[index];
#endif
}
/**
* @brief
*
* Switch平台返回触摸位置PC端返回鼠标位置
*
* @return X和Y坐标的二维向量
*/
Vec2 Input::getMousePosition() const {
#ifdef PLATFORM_SWITCH
return touchPosition_;
#else
return mousePosition_;
#endif
}
/**
* @brief
*
*
*
* @return X和Y移动量的二维向量
*/
Vec2 Input::getMouseDelta() const {
#ifdef PLATFORM_SWITCH
if (touching_ && prevTouching_) {
return touchPosition_ - prevTouchPosition_;
}
return Vec2::Zero();
#else
return mousePosition_ - prevMousePosition_;
#endif
}
/**
* @brief
*
* PC端有效
*
* @param position
*/
void Input::setMousePosition(const Vec2 &position) {
#ifndef PLATFORM_SWITCH
SDL_WarpMouseInWindow(SDL_GL_GetCurrentWindow(),
static_cast<int>(position.x),
static_cast<int>(position.y));
#else
(void)position;
#endif
}
/**
* @brief
*
* PC端有效
*
* @param visible true显示光标false隐藏光标
*/
void Input::setMouseVisible(bool visible) {
#ifndef PLATFORM_SWITCH
SDL_ShowCursor(visible ? SDL_ENABLE : SDL_DISABLE);
#else
(void)visible;
#endif
}
/**
* @brief
*
*
*
* @param locked true锁定鼠标到窗口中心false解锁
*/
void Input::setMouseLocked(bool locked) {
#ifndef PLATFORM_SWITCH
SDL_SetRelativeMouseMode(locked ? SDL_TRUE : SDL_FALSE);
#else
(void)locked;
#endif
}
// ============================================================================
// 便捷方法
// ============================================================================
/**
* @brief
*
* Switch平台检查手柄按钮PC端检查键盘按键
*
* @return truefalse
*/
bool Input::isAnyKeyDown() const {
#ifdef PLATFORM_SWITCH
for (int i = 0; i < MAX_BUTTONS; ++i) {
if (buttonsDown_[i])
return true;
}
#else
for (int i = 0; i < MAX_KEYS; ++i) {
if (keysDown_[i])
return true;
}
#endif
return false;
}
/**
* @brief
*
* Switch平台检查触摸状态PC端检查鼠标按钮
*
* @return truefalse
*/
bool Input::isAnyMouseDown() const {
#ifdef PLATFORM_SWITCH
return touching_;
#else
for (int i = 0; i < 8; ++i) {
if (mouseButtonsDown_[i])
return true;
}
return false;
#endif
}
// ============================================================================
// 视口适配器
// ============================================================================
/**
* @brief
*
*
*
* @param adapter
*/
void Input::setViewportAdapter(ViewportAdapter* adapter) {
viewportAdapter_ = adapter;
}
/**
* @brief
*
*
*
* @return
*/
Vec2 Input::getMousePosLogic() const {
Vec2 screenPos = getMousePosition();
if (viewportAdapter_) {
return viewportAdapter_->screenToLogic(screenPos);
}
return screenPos;
}
/**
* @brief
*
*
*
* @return
*/
Vec2 Input::getTouchPosLogic() const {
Vec2 screenPos = getTouchPosition();
if (viewportAdapter_) {
return viewportAdapter_->screenToLogic(screenPos);
}
return screenPos;
}
/**
* @brief
*
*
*
* @return
*/
Vec2 Input::getMouseDeltaLogic() const {
Vec2 delta = getMouseDelta();
if (viewportAdapter_) {
float scale = viewportAdapter_->getUniformScale();
if (scale > 0.0f) {
return delta / scale;
}
}
return delta;
}
} // namespace extra2d

View File

@ -0,0 +1,44 @@
#include <extra2d/platform/platform_module.h>
namespace extra2d {
std::unordered_map<std::string, BackendFactory::BackendEntry>& BackendFactory::registry() {
static std::unordered_map<std::string, BackendEntry> reg;
return reg;
}
void BackendFactory::reg(const std::string& name, WindowFn win, InputFn in) {
registry()[name] = {win, in};
}
UniquePtr<IWindow> BackendFactory::createWindow(const std::string& name) {
auto& reg = registry();
auto it = reg.find(name);
if (it != reg.end() && it->second.windowFn) {
return it->second.windowFn();
}
return nullptr;
}
UniquePtr<IInput> BackendFactory::createInput(const std::string& name) {
auto& reg = registry();
auto it = reg.find(name);
if (it != reg.end() && it->second.inputFn) {
return it->second.inputFn();
}
return nullptr;
}
std::vector<std::string> BackendFactory::backends() {
std::vector<std::string> result;
for (const auto& pair : registry()) {
result.push_back(pair.first);
}
return result;
}
bool BackendFactory::has(const std::string& name) {
return registry().find(name) != registry().end();
}
} // namespace extra2d

View File

@ -1,509 +0,0 @@
#include <extra2d/event/event_queue.h>
#include <extra2d/platform/input.h>
#include <extra2d/platform/window.h>
#include <extra2d/utils/logger.h>
#include <SDL.h>
#include <glad/glad.h>
namespace extra2d {
/**
* @brief
*
* SDL窗口指针OpenGL上下文
* VSync状态等
*/
Window::Window()
: sdlWindow_(nullptr), glContext_(nullptr), currentCursor_(nullptr),
width_(1280), height_(720), vsync_(true), shouldClose_(false),
fullscreen_(true), focused_(true), contentScaleX_(1.0f),
contentScaleY_(1.0f), enableDpiScale_(true), userData_(nullptr),
eventQueue_(nullptr) {
for (int i = 0; i < 9; ++i) {
sdlCursors_[i] = nullptr;
}
}
/**
* @brief
*
* destroy()
*/
Window::~Window() { destroy(); }
/**
* @brief
*
* SDL窗口和OpenGL ES上下文
*
* @param config
* @return truefalse
*/
bool Window::create(const WindowConfig &config) {
if (sdlWindow_ != nullptr) {
E2D_LOG_WARN("Window already created");
return false;
}
width_ = config.width;
height_ = config.height;
vsync_ = config.vsync;
fullscreen_ = config.fullscreen;
enableDpiScale_ = config.enableDpiScale;
if (!initSDL(config)) {
E2D_LOG_ERROR("Failed to initialize SDL2");
return false;
}
input_ = makeUnique<Input>();
input_->init();
if (config.enableCursors) {
initCursors();
}
E2D_LOG_INFO("Window created: {}x{}", width_, height_);
return true;
}
/**
* @brief SDL库和OpenGL上下文
*
* SDL2全局初始化OpenGL ES 3.2
* OpenGL上下文并加载GLES函数指针
*
* @param config
* @return truefalse
*/
bool Window::initSDL(const WindowConfig &config) {
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER | SDL_INIT_AUDIO) !=
0) {
E2D_LOG_ERROR("SDL_Init failed: {}", SDL_GetError());
return false;
}
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
Uint32 windowFlags = SDL_WINDOW_OPENGL;
if (config.fullscreen) {
if (config.fullscreenDesktop) {
windowFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
} else {
windowFlags |= SDL_WINDOW_FULLSCREEN;
}
} else {
if (config.resizable) {
windowFlags |= SDL_WINDOW_RESIZABLE;
}
}
sdlWindow_ = SDL_CreateWindow(
config.title.c_str(),
config.centerWindow ? SDL_WINDOWPOS_CENTERED : SDL_WINDOWPOS_UNDEFINED,
config.centerWindow ? SDL_WINDOWPOS_CENTERED : SDL_WINDOWPOS_UNDEFINED,
width_, height_, windowFlags);
if (!sdlWindow_) {
E2D_LOG_ERROR("SDL_CreateWindow failed: {}", SDL_GetError());
SDL_Quit();
return false;
}
glContext_ = SDL_GL_CreateContext(sdlWindow_);
if (!glContext_) {
E2D_LOG_ERROR("SDL_GL_CreateContext failed: {}", SDL_GetError());
SDL_DestroyWindow(sdlWindow_);
sdlWindow_ = nullptr;
SDL_Quit();
return false;
}
if (SDL_GL_MakeCurrent(sdlWindow_, glContext_) != 0) {
E2D_LOG_ERROR("SDL_GL_MakeCurrent failed: {}", SDL_GetError());
SDL_GL_DeleteContext(glContext_);
glContext_ = nullptr;
SDL_DestroyWindow(sdlWindow_);
sdlWindow_ = nullptr;
SDL_Quit();
return false;
}
if (gladLoadGLES2Loader(
reinterpret_cast<GLADloadproc>(SDL_GL_GetProcAddress)) == 0) {
E2D_LOG_ERROR("gladLoadGLES2Loader failed");
SDL_GL_DeleteContext(glContext_);
glContext_ = nullptr;
SDL_DestroyWindow(sdlWindow_);
sdlWindow_ = nullptr;
SDL_Quit();
return false;
}
SDL_GL_SetSwapInterval(vsync_ ? 1 : 0);
if (config.enableDpiScale) {
updateContentScale();
}
E2D_LOG_INFO("SDL2 + GLES 3.2 initialized successfully");
E2D_LOG_INFO("OpenGL Version: {}",
reinterpret_cast<const char *>(glGetString(GL_VERSION)));
E2D_LOG_INFO("OpenGL Renderer: {}",
reinterpret_cast<const char *>(glGetString(GL_RENDERER)));
return true;
}
/**
* @brief SDL资源
*
* OpenGL上下文SDL窗口并退出SDL库
*/
void Window::deinitSDL() {
deinitCursors();
if (glContext_) {
SDL_GL_DeleteContext(glContext_);
glContext_ = nullptr;
}
if (sdlWindow_) {
SDL_DestroyWindow(sdlWindow_);
sdlWindow_ = nullptr;
}
SDL_Quit();
}
/**
* @brief
*
* deinitSDL()SDL相关资源
*/
void Window::destroy() {
if (sdlWindow_ != nullptr) {
input_.reset();
deinitSDL();
E2D_LOG_INFO("Window destroyed");
}
}
/**
* @brief
*
* SDL事件队列中的所有事件
*
*/
void Window::pollEvents() {
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_QUIT:
shouldClose_ = true;
if (closeCallback_) {
closeCallback_();
}
break;
case SDL_WINDOWEVENT:
switch (event.window.event) {
case SDL_WINDOWEVENT_RESIZED:
case SDL_WINDOWEVENT_SIZE_CHANGED:
width_ = event.window.data1;
height_ = event.window.data2;
updateContentScale();
if (resizeCallback_) {
resizeCallback_(width_, height_);
}
break;
case SDL_WINDOWEVENT_FOCUS_GAINED:
focused_ = true;
if (focusCallback_) {
focusCallback_(true);
}
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
focused_ = false;
if (focusCallback_) {
focusCallback_(false);
}
break;
}
break;
}
}
if (input_) {
input_->update();
}
}
/**
* @brief
*
*
*/
void Window::swapBuffers() {
if (sdlWindow_) {
SDL_GL_SwapWindow(sdlWindow_);
}
}
/**
* @brief
*
* @return truefalse
*/
bool Window::shouldClose() const { return shouldClose_; }
/**
* @brief
*
* @param close
*/
void Window::setShouldClose(bool close) { shouldClose_ = close; }
/**
* @brief
*
* @param title
*/
void Window::setTitle(const std::string &title) {
if (sdlWindow_) {
SDL_SetWindowTitle(sdlWindow_, title.c_str());
}
}
/**
* @brief
*
* @param width
* @param height
*/
void Window::setSize(int width, int height) {
if (sdlWindow_) {
SDL_SetWindowSize(sdlWindow_, width, height);
width_ = width;
height_ = height;
}
}
/**
* @brief
*
* @param x X坐标
* @param y Y坐标
*/
void Window::setPos(int x, int y) {
if (sdlWindow_) {
SDL_SetWindowPosition(sdlWindow_, x, y);
}
}
/**
* @brief
*
* @param fullscreen true为全屏模式false为窗口模式
*/
void Window::setFullscreen(bool fullscreen) {
if (sdlWindow_) {
Uint32 flags = fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0;
SDL_SetWindowFullscreen(sdlWindow_, flags);
fullscreen_ = fullscreen;
}
}
/**
* @brief
*
* @param enabled true启用VSyncfalse禁用VSync
*/
void Window::setVSync(bool enabled) {
vsync_ = enabled;
SDL_GL_SetSwapInterval(enabled ? 1 : 0);
}
/**
* @brief
*
* @param resizable true允许用户调整窗口大小false禁止调整
*/
void Window::setResizable(bool resizable) {
if (sdlWindow_) {
SDL_SetWindowResizable(sdlWindow_, resizable ? SDL_TRUE : SDL_FALSE);
}
}
/**
* @brief
*
* @return X和Y坐标的二维向量
*/
Vec2 Window::getPosition() const {
if (sdlWindow_) {
int x, y;
SDL_GetWindowPosition(sdlWindow_, &x, &y);
return Vec2(static_cast<float>(x), static_cast<float>(y));
}
return Vec2::Zero();
}
/**
* @brief X轴内容缩放比例
*
* DPI设置返回X轴的内容缩放比例DPI显示适配
*
* @return X轴缩放比例DPI缩放被禁用则返回1.0
*/
float Window::getContentScaleX() const {
return enableDpiScale_ ? contentScaleX_ : 1.0f;
}
/**
* @brief Y轴内容缩放比例
*
* DPI设置返回Y轴的内容缩放比例DPI显示适配
*
* @return Y轴缩放比例DPI缩放被禁用则返回1.0
*/
float Window::getContentScaleY() const {
return enableDpiScale_ ? contentScaleY_ : 1.0f;
}
/**
* @brief
*
* @return X和Y轴缩放比例的二维向量
*/
Vec2 Window::getContentScale() const {
return Vec2(getContentScaleX(), getContentScaleY());
}
/**
* @brief
*
* @return truefalse
*/
bool Window::isMinimized() const {
if (sdlWindow_) {
Uint32 flags = SDL_GetWindowFlags(sdlWindow_);
return (flags & SDL_WINDOW_MINIMIZED) != 0;
}
return false;
}
/**
* @brief
*
* @return truefalse
*/
bool Window::isMaximized() const {
if (sdlWindow_) {
Uint32 flags = SDL_GetWindowFlags(sdlWindow_);
return (flags & SDL_WINDOW_MAXIMIZED) != 0;
}
return true;
}
/**
* @brief
*
* 9
* 线
*/
void Window::initCursors() {
sdlCursors_[0] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW);
sdlCursors_[1] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM);
sdlCursors_[2] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_CROSSHAIR);
sdlCursors_[3] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND);
sdlCursors_[4] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE);
sdlCursors_[5] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS);
sdlCursors_[6] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL);
sdlCursors_[7] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE);
sdlCursors_[8] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW);
}
/**
* @brief
*
*
*/
void Window::deinitCursors() {
for (int i = 0; i < 9; ++i) {
if (sdlCursors_[i]) {
SDL_FreeCursor(sdlCursors_[i]);
sdlCursors_[i] = nullptr;
}
}
currentCursor_ = nullptr;
}
/**
* @brief
*
*
*
* @param shape
*/
void Window::setCursor(CursorShape shape) {
int index = static_cast<int>(shape);
if (index >= 0 && index < 9 && sdlCursors_[index]) {
SDL_SetCursor(sdlCursors_[index]);
currentCursor_ = sdlCursors_[index];
}
}
/**
* @brief
*
*
*/
void Window::resetCursor() {
SDL_SetCursor(SDL_GetDefaultCursor());
currentCursor_ = nullptr;
}
/**
* @brief
*
* @param visible true显示鼠标光标false隐藏鼠标光标
*/
void Window::setMouseVisible(bool visible) {
SDL_ShowCursor(visible ? SDL_ENABLE : SDL_DISABLE);
}
/**
* @brief
*
* DPI值更新内容缩放比例
* 96 DPI为基准计算缩放因子
*/
void Window::updateContentScale() {
if (sdlWindow_) {
int displayIndex = SDL_GetWindowDisplayIndex(sdlWindow_);
if (displayIndex >= 0) {
float ddpi, hdpi, vdpi;
if (SDL_GetDisplayDPI(displayIndex, &ddpi, &hdpi, &vdpi) == 0) {
contentScaleX_ = hdpi / 96.0f;
contentScaleY_ = vdpi / 96.0f;
}
}
}
}
} // namespace extra2d

View File

@ -2,7 +2,7 @@
#include <extra2d/app/application.h>
#include <extra2d/graphics/render_backend.h>
#include <extra2d/graphics/render_command.h>
#include <extra2d/platform/input.h>
#include <extra2d/platform/iinput.h>
#include <extra2d/scene/scene_manager.h>
#include <extra2d/utils/logger.h>
@ -389,7 +389,7 @@ void SceneManager::purgeCachedScenes() { namedScenes_.clear(); }
*/
void SceneManager::dispatchPointerEvents(Scene &scene) {
auto &input = Application::get().input();
Vec2 screenPos = input.getMousePosition();
Vec2 screenPos = input.mouse();
Vec2 worldPos = screenPos;
if (auto *camera = scene.getActiveCamera()) {
@ -426,17 +426,17 @@ void SceneManager::dispatchPointerEvents(Scene &scene) {
dispatchToNode(hoverTarget_, evt);
}
float scrollDelta = input.getMouseScrollDelta();
float scrollDelta = input.scrollDelta();
if (hoverTarget_ && scrollDelta != 0.0f) {
Event evt = Event::createMouseScroll(Vec2(0.0f, scrollDelta), worldPos);
dispatchToNode(hoverTarget_, evt);
}
if (input.isMousePressed(MouseButton::Left)) {
if (input.pressed(Mouse::Left)) {
captureTarget_ = hoverTarget_;
if (captureTarget_) {
Event evt = Event::createMouseButtonPress(
static_cast<int>(MouseButton::Left), 0, worldPos);
Event evt = Event::createMouseButtonPress(static_cast<int>(Mouse::Left),
0, worldPos);
dispatchToNode(captureTarget_, evt);
Event pressed;
@ -446,11 +446,11 @@ void SceneManager::dispatchPointerEvents(Scene &scene) {
}
}
if (input.isMouseReleased(MouseButton::Left)) {
if (input.released(Mouse::Left)) {
Node *target = captureTarget_ ? captureTarget_ : hoverTarget_;
if (target) {
Event evt = Event::createMouseButtonRelease(
static_cast<int>(MouseButton::Left), 0, worldPos);
Event evt = Event::createMouseButtonRelease(static_cast<int>(Mouse::Left),
0, worldPos);
dispatchToNode(target, evt);
Event released;

View File

@ -26,6 +26,13 @@ option("debug_logs")
set_description("Enable debug logging")
option_end()
option("backend")
set_default("sdl2")
set_showmenu(true)
set_values("sdl2", "glfw")
set_description("Platform backend (sdl2, glfw)")
option_end()
-- ==============================================
-- 平台检测与配置
-- ==============================================
@ -66,7 +73,16 @@ end
-- ==============================================
if target_plat == "mingw" then
add_requires("glm", "libsdl2")
local backend = get_config("backend") or "sdl2"
add_requires("glm")
add_requires("nlohmann_json")
if backend == "sdl2" then
add_requires("libsdl2")
elseif backend == "glfw" then
add_requires("glfw")
end
end
-- ==============================================

View File

@ -8,21 +8,40 @@ local function get_current_plat()
return get_config("plat") or os.host()
end
-- 获取后端配置
local function get_backend()
return get_config("backend") or "sdl2"
end
-- 定义 Extra2D 引擎库目标
function define_extra2d_engine()
target("extra2d")
set_kind("static")
-- 引擎源文件
-- 引擎核心源文件
add_files("Extra2D/src/**.cpp")
add_files("Extra2D/src/glad/glad.c")
-- 平台后端源文件
local plat = get_current_plat()
local backend = get_backend()
if plat == "switch" then
add_files("Extra2D/src/platform/backends/switch/*.cpp")
add_defines("E2D_BACKEND_SWITCH")
elseif backend == "sdl2" then
add_files("Extra2D/src/platform/backends/sdl2/*.cpp")
add_defines("E2D_BACKEND_SDL2")
elseif backend == "glfw" then
add_files("Extra2D/src/platform/backends/glfw/*.cpp")
add_defines("E2D_BACKEND_GLFW")
end
-- 头文件路径
add_includedirs("Extra2D/include", {public = true})
add_includedirs("Extra2D/include/extra2d/platform", {public = true})
-- 平台配置
local plat = get_current_plat()
if plat == "switch" then
local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro"
add_includedirs(devkitPro .. "/portlibs/switch/include", {public = true})
@ -30,7 +49,14 @@ function define_extra2d_engine()
add_syslinks("SDL2", "GLESv2", "EGL", "glapi", "drm_nouveau",
{public = true})
elseif plat == "mingw" then
add_packages("glm", "libsdl2", {public = true})
add_packages("glm", "nlohmann_json", {public = true})
if backend == "sdl2" then
add_packages("libsdl2", {public = true})
elseif backend == "glfw" then
add_packages("glfw", {public = true})
end
add_syslinks("opengl32", "glu32", "winmm", "imm32", "version", "setupapi", {public = true})
end