From 9439e200d7ad4e915be4bfaaef72726775038a29 Mon Sep 17 00:00:00 2001 From: ChestnutYueyue <952134128@qq.com> Date: Sun, 15 Feb 2026 00:22:24 +0800 Subject: [PATCH] =?UTF-8?q?refactor(platform):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E5=B9=B3=E5=8F=B0=E6=A8=A1=E5=9D=97=EF=BC=8C=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E5=A4=9A=E5=90=8E=E7=AB=AF=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重构平台模块,引入IWindow和IInput接口,支持SDL2和GLFW等多后端。主要变更包括: 1. 新增平台模块接口和SDL2后端实现 2. 移除旧版Window和Input类,替换为接口化设计 3. 添加后端注册机制,支持动态加载不同平台后端 4. 统一输入系统API,定义标准键盘、鼠标和手柄按键枚举 5. 更新构建系统,支持通过配置选择不同后端 同时调整相关代码以适配新接口,包括渲染器、场景管理和应用类等 --- Extra2D/include/extra2d/app/application.h | 151 ++-- Extra2D/include/extra2d/extra2d.h | 6 +- .../extra2d/graphics/opengl/gl_renderer.h | 6 +- .../include/extra2d/graphics/render_backend.h | 4 +- Extra2D/include/extra2d/platform/iinput.h | 175 +++++ Extra2D/include/extra2d/platform/input.h | 173 ----- Extra2D/include/extra2d/platform/iwindow.h | 218 ++++++ Extra2D/include/extra2d/platform/keys.h | 72 ++ .../extra2d/platform/platform_module.h | 97 +++ Extra2D/include/extra2d/platform/window.h | 156 ---- Extra2D/src/app/application.cpp | 685 ++++++++---------- Extra2D/src/graphics/opengl/gl_renderer.cpp | 4 +- .../platform/backends/sdl2/sdl2_backend.cpp | 9 + .../src/platform/backends/sdl2/sdl2_input.cpp | 386 ++++++++++ .../src/platform/backends/sdl2/sdl2_input.h | 82 +++ .../platform/backends/sdl2/sdl2_window.cpp | 368 ++++++++++ .../src/platform/backends/sdl2/sdl2_window.h | 99 +++ Extra2D/src/platform/input.cpp | 665 ----------------- Extra2D/src/platform/platform_module.cpp | 44 ++ Extra2D/src/platform/window.cpp | 509 ------------- Extra2D/src/scene/scene_manager.cpp | 18 +- xmake.lua | 18 +- xmake/engine.lua | 32 +- 23 files changed, 2018 insertions(+), 1959 deletions(-) create mode 100644 Extra2D/include/extra2d/platform/iinput.h delete mode 100644 Extra2D/include/extra2d/platform/input.h create mode 100644 Extra2D/include/extra2d/platform/iwindow.h create mode 100644 Extra2D/include/extra2d/platform/keys.h create mode 100644 Extra2D/include/extra2d/platform/platform_module.h delete mode 100644 Extra2D/include/extra2d/platform/window.h create mode 100644 Extra2D/src/platform/backends/sdl2/sdl2_backend.cpp create mode 100644 Extra2D/src/platform/backends/sdl2/sdl2_input.cpp create mode 100644 Extra2D/src/platform/backends/sdl2/sdl2_input.h create mode 100644 Extra2D/src/platform/backends/sdl2/sdl2_window.cpp create mode 100644 Extra2D/src/platform/backends/sdl2/sdl2_window.h delete mode 100644 Extra2D/src/platform/input.cpp create mode 100644 Extra2D/src/platform/platform_module.cpp delete mode 100644 Extra2D/src/platform/window.cpp diff --git a/Extra2D/include/extra2d/app/application.h b/Extra2D/include/extra2d/app/application.h index 69bdefd..17ab0ce 100644 --- a/Extra2D/include/extra2d/app/application.h +++ b/Extra2D/include/extra2d/app/application.h @@ -2,11 +2,12 @@ #include #include -#include +#include +#include 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 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 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_; - UniquePtr renderer_; - UniquePtr sceneManager_; - UniquePtr timerManager_; - UniquePtr eventQueue_; - UniquePtr eventDispatcher_; - UniquePtr camera_; - UniquePtr viewportAdapter_; + UniquePtr window_; + UniquePtr renderer_; + UniquePtr sceneManager_; + UniquePtr timerManager_; + UniquePtr eventQueue_; + UniquePtr eventDispatcher_; + UniquePtr camera_; + UniquePtr 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 diff --git a/Extra2D/include/extra2d/extra2d.h b/Extra2D/include/extra2d/extra2d.h index eff041d..466c6a4 100644 --- a/Extra2D/include/extra2d/extra2d.h +++ b/Extra2D/include/extra2d/extra2d.h @@ -9,8 +9,10 @@ #include // Platform -#include -#include +#include +#include +#include +#include // Graphics #include diff --git a/Extra2D/include/extra2d/graphics/opengl/gl_renderer.h b/Extra2D/include/extra2d/graphics/opengl/gl_renderer.h index a2bea93..f977e68 100644 --- a/Extra2D/include/extra2d/graphics/opengl/gl_renderer.h +++ b/Extra2D/include/extra2d/graphics/opengl/gl_renderer.h @@ -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_; diff --git a/Extra2D/include/extra2d/graphics/render_backend.h b/Extra2D/include/extra2d/graphics/render_backend.h index 83dbe5d..895f439 100644 --- a/Extra2D/include/extra2d/graphics/render_backend.h +++ b/Extra2D/include/extra2d/graphics/render_backend.h @@ -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; // ------------------------------------------------------------------------ diff --git a/Extra2D/include/extra2d/platform/iinput.h b/Extra2D/include/extra2d/platform/iinput.h new file mode 100644 index 0000000..08d852e --- /dev/null +++ b/Extra2D/include/extra2d/platform/iinput.h @@ -0,0 +1,175 @@ +#pragma once + +#include +#include + +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 diff --git a/Extra2D/include/extra2d/platform/input.h b/Extra2D/include/extra2d/platform/input.h deleted file mode 100644 index d3c4a9b..0000000 --- a/Extra2D/include/extra2d/platform/input.h +++ /dev/null @@ -1,173 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include - -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 keysDown_; - std::array prevKeysDown_; - - // 手柄按钮状态 - std::array buttonsDown_; - std::array prevButtonsDown_; - - // 摇杆状态 - float leftStickX_; - float leftStickY_; - float rightStickX_; - float rightStickY_; - - // 鼠标状态 (PC 端使用) - Vec2 mousePosition_; - Vec2 prevMousePosition_; - float mouseScroll_; - float prevMouseScroll_; - std::array mouseButtonsDown_; - std::array 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 diff --git a/Extra2D/include/extra2d/platform/iwindow.h b/Extra2D/include/extra2d/platform/iwindow.h new file mode 100644 index 0000000..1fd6c2b --- /dev/null +++ b/Extra2D/include/extra2d/platform/iwindow.h @@ -0,0 +1,218 @@ +#pragma once + +#include +#include +#include +#include + +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; + + /** + * @brief 窗口关闭回调 + */ + using CloseCb = std::function; + + /** + * @brief 窗口焦点改变回调 + */ + using FocusCb = std::function; + + /** + * @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 diff --git a/Extra2D/include/extra2d/platform/keys.h b/Extra2D/include/extra2d/platform/keys.h new file mode 100644 index 0000000..7fafae1 --- /dev/null +++ b/Extra2D/include/extra2d/platform/keys.h @@ -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 diff --git a/Extra2D/include/extra2d/platform/platform_module.h b/Extra2D/include/extra2d/platform/platform_module.h new file mode 100644 index 0000000..98ffaa6 --- /dev/null +++ b/Extra2D/include/extra2d/platform/platform_module.h @@ -0,0 +1,97 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +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()>; + using InputFn = std::function()>; + + /** + * @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 createWindow(const std::string& name); + + /** + * @brief 创建输入实例 + * @param name 后端名称 + * @return 输入实例,如果后端不存在返回 nullptr + */ + static UniquePtr createInput(const std::string& name); + + /** + * @brief 获取所有已注册的后端名称 + */ + static std::vector backends(); + + /** + * @brief 检查后端是否存在 + */ + static bool has(const std::string& name); + +private: + struct BackendEntry { + WindowFn windowFn; + InputFn inputFn; + }; + + static std::unordered_map& 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(); \ + }, \ + []() -> ::extra2d::UniquePtr<::extra2d::IInput> { \ + return ::extra2d::makeUnique(); \ + } \ + ); \ + } \ + } e2d_backend_reg_##name; \ + } + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/platform/window.h b/Extra2D/include/extra2d/platform/window.h deleted file mode 100644 index 4824235..0000000 --- a/Extra2D/include/extra2d/platform/window.h +++ /dev/null @@ -1,156 +0,0 @@ -#pragma once -#include -#include -#include - -#include - -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(width_), static_cast(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; - using FocusCallback = std::function; - using CloseCallback = std::function; - - 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_; - - ResizeCallback resizeCallback_; - FocusCallback focusCallback_; - CloseCallback closeCallback_; - - bool initSDL(const WindowConfig &config); - void deinitSDL(); - void initCursors(); - void deinitCursors(); - void updateContentScale(); -}; - -} // namespace extra2d diff --git a/Extra2D/src/app/application.cpp b/Extra2D/src/app/application.cpp index 6376eee..c0c90ca 100644 --- a/Extra2D/src/app/application.cpp +++ b/Extra2D/src/app/application.cpp @@ -5,8 +5,8 @@ #include #include #include -#include -#include +#include +#include #include #include #include @@ -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(ts.tv_sec) + - static_cast(ts.tv_nsec) / 1000000000.0; + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return static_cast(ts.tv_sec) + + static_cast(ts.tv_nsec) / 1000000000.0; #else - using namespace std::chrono; - auto now = steady_clock::now(); - auto duration = now.time_since_epoch(); - return duration_cast>(duration).count(); + using namespace std::chrono; + auto now = steady_clock::now(); + auto duration = now.time_since_epoch(); + return duration_cast>(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 初始化成功返回true,失败返回false - * - * 初始化窗口、渲染器、场景管理器、定时器管理器、事件系统、相机和视口适配器等核心组件 - */ -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(); - 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(); - timerManager_ = makeUnique(); - eventQueue_ = makeUnique(); - eventDispatcher_ = makeUnique(); - camera_ = makeUnique(0, static_cast(window_->getWidth()), - static_cast(window_->getHeight()), 0); - - viewportAdapter_ = makeUnique(); - ViewportConfig vpConfig; - vpConfig.logicWidth = static_cast(config.width); - vpConfig.logicHeight = static_cast(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(); + timerManager_ = makeUnique(); + eventQueue_ = makeUnique(); + eventDispatcher_ = makeUnique(); + camera_ = makeUnique(0, static_cast(window_->width()), + static_cast(window_->height()), 0); + + viewportAdapter_ = makeUnique(); + ViewportConfig vpConfig; + vpConfig.logicWidth = static_cast(config_.width); + vpConfig.logicHeight = static_cast(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(width), + static_cast(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(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(config_.fpsLimit); + if (frameTime < target) { + auto sleepSeconds = target - frameTime; + std::this_thread::sleep_for(std::chrono::duration(sleepSeconds)); + } + } +} + +void Application::update() { + if (timerManager_) { + timerManager_->update(deltaTime_); } if (sceneManager_) { - auto currentScene = sceneManager_->getCurrentScene(); - if (currentScene) { - currentScene->setViewportSize(static_cast(width), - static_cast(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(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(config_.fpsLimit); - if (frameTime < target) { - auto sleepSeconds = target - frameTime; - std::this_thread::sleep_for(std::chrono::duration(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(vp.origin.x), static_cast(vp.origin.y), - static_cast(vp.size.width), static_cast(vp.size.height)); - } else { - renderer_->setViewport(0, 0, window_->getWidth(), window_->getHeight()); - } + if (viewportAdapter_) { + const auto& vp = viewportAdapter_->getViewport(); + renderer_->setViewport( + static_cast(vp.origin.x), static_cast(vp.origin.y), + static_cast(vp.size.width), static_cast(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) { - if (sceneManager_ && scene) { - scene->setViewportSize(static_cast(window_->getWidth()), - static_cast(window_->getHeight())); - sceneManager_->enterScene(scene); - } + if (sceneManager_ && scene) { + scene->setViewportSize(static_cast(window_->width()), + static_cast(window_->height())); + sceneManager_->enterScene(scene); + } } } // namespace extra2d diff --git a/Extra2D/src/graphics/opengl/gl_renderer.cpp b/Extra2D/src/graphics/opengl/gl_renderer.cpp index aa5e263..f108e51 100644 --- a/Extra2D/src/graphics/opengl/gl_renderer.cpp +++ b/Extra2D/src/graphics/opengl/gl_renderer.cpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #include @@ -85,7 +85,7 @@ GLRenderer::~GLRenderer() { shutdown(); } * @param window 窗口指针 * @return 初始化成功返回true,失败返回false */ -bool GLRenderer::init(Window *window) { +bool GLRenderer::init(IWindow* window) { window_ = window; // Switch: GL 上下文已通过 SDL2 + EGL 初始化,无需 glewInit() diff --git a/Extra2D/src/platform/backends/sdl2/sdl2_backend.cpp b/Extra2D/src/platform/backends/sdl2/sdl2_backend.cpp new file mode 100644 index 0000000..770c2c1 --- /dev/null +++ b/Extra2D/src/platform/backends/sdl2/sdl2_backend.cpp @@ -0,0 +1,9 @@ +#include "sdl2_window.h" +#include "sdl2_input.h" +#include + +namespace extra2d { + +E2D_REG_BACKEND(sdl2, SDL2Window, SDL2Input) + +} // namespace extra2d diff --git a/Extra2D/src/platform/backends/sdl2/sdl2_input.cpp b/Extra2D/src/platform/backends/sdl2/sdl2_input.cpp new file mode 100644 index 0000000..8699f49 --- /dev/null +++ b/Extra2D/src/platform/backends/sdl2/sdl2_input.cpp @@ -0,0 +1,386 @@ +#include "sdl2_input.h" +#include +#include + +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(key); + return idx < keyCurrent_.size() ? keyCurrent_[idx] : false; +} + +bool SDL2Input::pressed(Key key) const { + size_t idx = static_cast(key); + return idx < keyCurrent_.size() ? (keyCurrent_[idx] && !keyPrevious_[idx]) : false; +} + +bool SDL2Input::released(Key key) const { + size_t idx = static_cast(key); + return idx < keyCurrent_.size() ? (!keyCurrent_[idx] && keyPrevious_[idx]) : false; +} + +bool SDL2Input::down(Mouse btn) const { + size_t idx = static_cast(btn); + return idx < mouseCurrent_.size() ? mouseCurrent_[idx] : false; +} + +bool SDL2Input::pressed(Mouse btn) const { + size_t idx = static_cast(btn); + return idx < mouseCurrent_.size() ? (mouseCurrent_[idx] && !mousePrevious_[idx]) : false; +} + +bool SDL2Input::released(Mouse btn) const { + size_t idx = static_cast(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(pos.x), static_cast(pos.y)); +} + +bool SDL2Input::gamepad() const { + return gamepad_ != nullptr; +} + +bool SDL2Input::down(Gamepad btn) const { + size_t idx = static_cast(btn); + return idx < gamepadCurrent_.size() ? gamepadCurrent_[idx] : false; +} + +bool SDL2Input::pressed(Gamepad btn) const { + size_t idx = static_cast(btn); + return idx < gamepadCurrent_.size() ? (gamepadCurrent_[idx] && !gamepadPrevious_[idx]) : false; +} + +bool SDL2Input::released(Gamepad btn) const { + size_t idx = static_cast(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(std::clamp(left, 0.0f, 1.0f) * 65535); + Uint16 highFreq = static_cast(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(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(x), static_cast(y)); + mouseDelta_ = newPos - mousePos_; + mousePos_ = newPos; + + mouseCurrent_[static_cast(Mouse::Left)] = (state & SDL_BUTTON_LMASK) != 0; + mouseCurrent_[static_cast(Mouse::Right)] = (state & SDL_BUTTON_RMASK) != 0; + mouseCurrent_[static_cast(Mouse::Middle)] = (state & SDL_BUTTON_MMASK) != 0; + mouseCurrent_[static_cast(Mouse::X1)] = (state & SDL_BUTTON_X1MASK) != 0; + mouseCurrent_[static_cast(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(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(Gamepad::A)] = getButton(SDL_CONTROLLER_BUTTON_A); + gamepadCurrent_[static_cast(Gamepad::B)] = getButton(SDL_CONTROLLER_BUTTON_B); + gamepadCurrent_[static_cast(Gamepad::X)] = getButton(SDL_CONTROLLER_BUTTON_X); + gamepadCurrent_[static_cast(Gamepad::Y)] = getButton(SDL_CONTROLLER_BUTTON_Y); + gamepadCurrent_[static_cast(Gamepad::LB)] = getButton(SDL_CONTROLLER_BUTTON_LEFTSHOULDER); + gamepadCurrent_[static_cast(Gamepad::RB)] = getButton(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER); + gamepadCurrent_[static_cast(Gamepad::Back)] = getButton(SDL_CONTROLLER_BUTTON_BACK); + gamepadCurrent_[static_cast(Gamepad::Start)] = getButton(SDL_CONTROLLER_BUTTON_START); + gamepadCurrent_[static_cast(Gamepad::Guide)] = getButton(SDL_CONTROLLER_BUTTON_GUIDE); + gamepadCurrent_[static_cast(Gamepad::LStick)] = getButton(SDL_CONTROLLER_BUTTON_LEFTSTICK); + gamepadCurrent_[static_cast(Gamepad::RStick)] = getButton(SDL_CONTROLLER_BUTTON_RIGHTSTICK); + gamepadCurrent_[static_cast(Gamepad::DUp)] = getButton(SDL_CONTROLLER_BUTTON_DPAD_UP); + gamepadCurrent_[static_cast(Gamepad::DDown)] = getButton(SDL_CONTROLLER_BUTTON_DPAD_DOWN); + gamepadCurrent_[static_cast(Gamepad::DLeft)] = getButton(SDL_CONTROLLER_BUTTON_DPAD_LEFT); + gamepadCurrent_[static_cast(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 diff --git a/Extra2D/src/platform/backends/sdl2/sdl2_input.h b/Extra2D/src/platform/backends/sdl2/sdl2_input.h new file mode 100644 index 0000000..6c7a617 --- /dev/null +++ b/Extra2D/src/platform/backends/sdl2/sdl2_input.h @@ -0,0 +1,82 @@ +#pragma once + +#include +#include +#include + +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(Key::Count)> keyCurrent_{}; + std::array(Key::Count)> keyPrevious_{}; + + std::array(Mouse::Count)> mouseCurrent_{}; + std::array(Mouse::Count)> mousePrevious_{}; + + Vec2 mousePos_; + Vec2 mouseDelta_; + float scroll_ = 0.0f; + float scrollDelta_ = 0.0f; + + SDL_GameController* gamepad_ = nullptr; + int gamepadIndex_ = -1; + std::array(Gamepad::Count)> gamepadCurrent_{}; + std::array(Gamepad::Count)> gamepadPrevious_{}; + Vec2 leftStick_; + Vec2 rightStick_; + float leftTrigger_ = 0.0f; + float rightTrigger_ = 0.0f; + float deadzone_ = 0.15f; +}; + +} // namespace extra2d diff --git a/Extra2D/src/platform/backends/sdl2/sdl2_window.cpp b/Extra2D/src/platform/backends/sdl2/sdl2_window.cpp new file mode 100644 index 0000000..6c382fc --- /dev/null +++ b/Extra2D/src/platform/backends/sdl2/sdl2_window.cpp @@ -0,0 +1,368 @@ +#include "sdl2_window.h" +#include "sdl2_input.h" +#include + +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(); + 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(width_), static_cast(height_)); +} + +Vec2 SDL2Window::pos() const { + int x, y; + if (sdlWindow_) { + SDL_GetWindowPosition(sdlWindow_, &x, &y); + } else { + x = y = 0; + } + return Vec2(static_cast(x), static_cast(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(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(dw) / width_ : 1.0f; + scaleY_ = dh > 0 ? static_cast(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 diff --git a/Extra2D/src/platform/backends/sdl2/sdl2_window.h b/Extra2D/src/platform/backends/sdl2/sdl2_window.h new file mode 100644 index 0000000..3bc6853 --- /dev/null +++ b/Extra2D/src/platform/backends/sdl2/sdl2_window.h @@ -0,0 +1,99 @@ +#pragma once + +#include +#include + +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 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 diff --git a/Extra2D/src/platform/input.cpp b/Extra2D/src/platform/input.cpp deleted file mode 100644 index 48972b1..0000000 --- a/Extra2D/src/platform/input.cpp +++ /dev/null @@ -1,665 +0,0 @@ -#include -#include -#include -#include - -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(mouseX), static_cast(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(mouseX), static_cast(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(i)) != 0; - } - - leftStickX_ = static_cast(SDL_GameControllerGetAxis( - controller_, SDL_CONTROLLER_AXIS_LEFTX)) / 32767.0f; - leftStickY_ = static_cast(SDL_GameControllerGetAxis( - controller_, SDL_CONTROLLER_AXIS_LEFTY)) / 32767.0f; - rightStickX_ = static_cast(SDL_GameControllerGetAxis( - controller_, SDL_CONTROLLER_AXIS_RIGHTX)) / 32767.0f; - rightStickY_ = static_cast(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 按键按下返回true,否则返回false - */ -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 按键刚按下返回true,否则返回false - */ -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 按键刚释放返回true,否则返回false - */ -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 按钮按下返回true,否则返回false - */ -bool Input::isButtonDown(int button) const { - if (button < 0 || button >= MAX_BUTTONS) - return false; - return buttonsDown_[button]; -} - -/** - * @brief 检查手柄按钮是否刚被按下 - * - * @param button 手柄按钮索引 - * @return 按钮刚按下返回true,否则返回false - */ -bool Input::isButtonPressed(int button) const { - if (button < 0 || button >= MAX_BUTTONS) - return false; - return buttonsDown_[button] && !prevButtonsDown_[button]; -} - -/** - * @brief 检查手柄按钮是否刚被释放 - * - * @param button 手柄按钮索引 - * @return 按钮刚释放返回true,否则返回false - */ -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 按钮按下返回true,否则返回false - */ -bool Input::isMouseDown(MouseButton button) const { - int index = static_cast(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 按钮刚按下返回true,否则返回false - */ -bool Input::isMousePressed(MouseButton button) const { - int index = static_cast(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 按钮刚释放返回true,否则返回false - */ -bool Input::isMouseReleased(MouseButton button) const { - int index = static_cast(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(position.x), - static_cast(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 有按键按下返回true,否则返回false - */ -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 有按钮按下返回true,否则返回false - */ -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 diff --git a/Extra2D/src/platform/platform_module.cpp b/Extra2D/src/platform/platform_module.cpp new file mode 100644 index 0000000..93a704c --- /dev/null +++ b/Extra2D/src/platform/platform_module.cpp @@ -0,0 +1,44 @@ +#include + +namespace extra2d { + +std::unordered_map& BackendFactory::registry() { + static std::unordered_map reg; + return reg; +} + +void BackendFactory::reg(const std::string& name, WindowFn win, InputFn in) { + registry()[name] = {win, in}; +} + +UniquePtr 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 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 BackendFactory::backends() { + std::vector 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 diff --git a/Extra2D/src/platform/window.cpp b/Extra2D/src/platform/window.cpp deleted file mode 100644 index dbbc221..0000000 --- a/Extra2D/src/platform/window.cpp +++ /dev/null @@ -1,509 +0,0 @@ -#include -#include -#include -#include - -#include - -#include - -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 创建成功返回true,失败返回false - */ -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_->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 初始化成功返回true,失败返回false - */ -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(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(glGetString(GL_VERSION))); - E2D_LOG_INFO("OpenGL Renderer: {}", - reinterpret_cast(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 如果窗口应该关闭返回true,否则返回false - */ -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启用VSync,false禁用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(x), static_cast(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 如果窗口处于最小化状态返回true,否则返回false - */ -bool Window::isMinimized() const { - if (sdlWindow_) { - Uint32 flags = SDL_GetWindowFlags(sdlWindow_); - return (flags & SDL_WINDOW_MINIMIZED) != 0; - } - return false; -} - -/** - * @brief 检查窗口是否最大化 - * - * @return 如果窗口处于最大化状态返回true,否则返回false - */ -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(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 diff --git a/Extra2D/src/scene/scene_manager.cpp b/Extra2D/src/scene/scene_manager.cpp index 1c1cbb8..184e5af 100644 --- a/Extra2D/src/scene/scene_manager.cpp +++ b/Extra2D/src/scene/scene_manager.cpp @@ -2,7 +2,7 @@ #include #include #include -#include +#include #include #include @@ -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(MouseButton::Left), 0, worldPos); + Event evt = Event::createMouseButtonPress(static_cast(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(MouseButton::Left), 0, worldPos); + Event evt = Event::createMouseButtonRelease(static_cast(Mouse::Left), + 0, worldPos); dispatchToNode(target, evt); Event released; diff --git a/xmake.lua b/xmake.lua index e24dc8b..05ef2ef 100644 --- a/xmake.lua +++ b/xmake.lua @@ -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 -- ============================================== diff --git a/xmake/engine.lua b/xmake/engine.lua index cbb29e2..ad83a72 100644 --- a/xmake/engine.lua +++ b/xmake/engine.lua @@ -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