refactor(platform): 重构平台模块,实现多后端支持
重构平台模块,引入IWindow和IInput接口,支持SDL2和GLFW等多后端。主要变更包括: 1. 新增平台模块接口和SDL2后端实现 2. 移除旧版Window和Input类,替换为接口化设计 3. 添加后端注册机制,支持动态加载不同平台后端 4. 统一输入系统API,定义标准键盘、鼠标和手柄按键枚举 5. 更新构建系统,支持通过配置选择不同后端 同时调整相关代码以适配新接口,包括渲染器、场景管理和应用类等
This commit is contained in:
parent
387ea62853
commit
9439e200d7
|
|
@ -2,11 +2,12 @@
|
||||||
|
|
||||||
#include <extra2d/core/types.h>
|
#include <extra2d/core/types.h>
|
||||||
#include <extra2d/graphics/render_backend.h>
|
#include <extra2d/graphics/render_backend.h>
|
||||||
#include <extra2d/platform/window.h>
|
#include <extra2d/platform/iwindow.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
class Input;
|
class IInput;
|
||||||
class SceneManager;
|
class SceneManager;
|
||||||
class TimerManager;
|
class TimerManager;
|
||||||
class EventQueue;
|
class EventQueue;
|
||||||
|
|
@ -14,21 +15,29 @@ class EventDispatcher;
|
||||||
class Camera;
|
class Camera;
|
||||||
class ViewportAdapter;
|
class ViewportAdapter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 平台类型
|
||||||
|
*/
|
||||||
enum class PlatformType { Auto = 0, PC, Switch };
|
enum class PlatformType { Auto = 0, PC, Switch };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 应用配置
|
||||||
|
*/
|
||||||
struct AppConfig {
|
struct AppConfig {
|
||||||
std::string title = "Easy2D Application";
|
std::string title = "Extra2D Application";
|
||||||
int width = 800;
|
int width = 1280;
|
||||||
int height = 600;
|
int height = 720;
|
||||||
bool fullscreen = false;
|
bool fullscreen = false;
|
||||||
bool resizable = true;
|
bool resizable = true;
|
||||||
bool vsync = true;
|
bool vsync = true;
|
||||||
int fpsLimit = 0;
|
int fpsLimit = 0;
|
||||||
BackendType renderBackend = BackendType::OpenGL;
|
BackendType renderBackend = BackendType::OpenGL;
|
||||||
int msaaSamples = 0;
|
int msaaSamples = 0;
|
||||||
PlatformType platform = PlatformType::Auto;
|
PlatformType platform = PlatformType::Auto;
|
||||||
bool enableCursors = true;
|
bool enableCursors = true;
|
||||||
bool enableDpiScale = false;
|
bool enableDpiScale = false;
|
||||||
|
|
||||||
|
std::string backend = "sdl2";
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -36,69 +45,85 @@ struct AppConfig {
|
||||||
*/
|
*/
|
||||||
class Application {
|
class Application {
|
||||||
public:
|
public:
|
||||||
static Application &get();
|
static Application& get();
|
||||||
|
|
||||||
Application(const Application &) = delete;
|
Application(const Application&) = delete;
|
||||||
Application &operator=(const Application &) = delete;
|
Application& operator=(const Application&) = delete;
|
||||||
|
|
||||||
bool init(const AppConfig &config);
|
/**
|
||||||
void shutdown();
|
* @brief 使用默认配置初始化
|
||||||
void run();
|
*/
|
||||||
void quit();
|
bool init();
|
||||||
|
|
||||||
void pause();
|
/**
|
||||||
void resume();
|
* @brief 使用配置结构初始化
|
||||||
bool isPaused() const { return paused_; }
|
*/
|
||||||
bool isRunning() const { return running_; }
|
bool init(const AppConfig& config);
|
||||||
|
|
||||||
Window &window() { return *window_; }
|
/**
|
||||||
RenderBackend &renderer() { return *renderer_; }
|
* @brief 从配置文件初始化
|
||||||
Input &input();
|
* @param path 配置文件路径(支持 .json 和 .ini)
|
||||||
SceneManager &scenes();
|
*/
|
||||||
TimerManager &timers();
|
bool init(const std::string& path);
|
||||||
EventQueue &eventQueue();
|
|
||||||
EventDispatcher &eventDispatcher();
|
|
||||||
Camera &camera();
|
|
||||||
ViewportAdapter &viewportAdapter();
|
|
||||||
|
|
||||||
void enterScene(Ptr<class Scene> scene);
|
void shutdown();
|
||||||
|
void run();
|
||||||
|
void quit();
|
||||||
|
|
||||||
float deltaTime() const { return deltaTime_; }
|
void pause();
|
||||||
float totalTime() const { return totalTime_; }
|
void resume();
|
||||||
int fps() const { return currentFps_; }
|
bool isPaused() const { return paused_; }
|
||||||
|
bool isRunning() const { return running_; }
|
||||||
|
|
||||||
const AppConfig &getConfig() const { return config_; }
|
IWindow& window() { return *window_; }
|
||||||
|
RenderBackend& renderer() { return *renderer_; }
|
||||||
|
IInput& input();
|
||||||
|
SceneManager& scenes();
|
||||||
|
TimerManager& timers();
|
||||||
|
EventQueue& eventQueue();
|
||||||
|
EventDispatcher& eventDispatcher();
|
||||||
|
Camera& camera();
|
||||||
|
ViewportAdapter& viewportAdapter();
|
||||||
|
|
||||||
|
void enterScene(Ptr<class Scene> scene);
|
||||||
|
|
||||||
|
float deltaTime() const { return deltaTime_; }
|
||||||
|
float totalTime() const { return totalTime_; }
|
||||||
|
int fps() const { return currentFps_; }
|
||||||
|
|
||||||
|
const AppConfig& getConfig() const { return config_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Application() = default;
|
Application() = default;
|
||||||
~Application();
|
~Application();
|
||||||
|
|
||||||
void mainLoop();
|
bool initImpl();
|
||||||
void update();
|
void mainLoop();
|
||||||
void render();
|
void update();
|
||||||
|
void render();
|
||||||
|
|
||||||
AppConfig config_;
|
AppConfig config_;
|
||||||
|
|
||||||
UniquePtr<Window> window_;
|
UniquePtr<IWindow> window_;
|
||||||
UniquePtr<RenderBackend> renderer_;
|
UniquePtr<RenderBackend> renderer_;
|
||||||
UniquePtr<SceneManager> sceneManager_;
|
UniquePtr<SceneManager> sceneManager_;
|
||||||
UniquePtr<TimerManager> timerManager_;
|
UniquePtr<TimerManager> timerManager_;
|
||||||
UniquePtr<EventQueue> eventQueue_;
|
UniquePtr<EventQueue> eventQueue_;
|
||||||
UniquePtr<EventDispatcher> eventDispatcher_;
|
UniquePtr<EventDispatcher> eventDispatcher_;
|
||||||
UniquePtr<Camera> camera_;
|
UniquePtr<Camera> camera_;
|
||||||
UniquePtr<ViewportAdapter> viewportAdapter_;
|
UniquePtr<ViewportAdapter> viewportAdapter_;
|
||||||
|
|
||||||
bool initialized_ = false;
|
bool initialized_ = false;
|
||||||
bool running_ = false;
|
bool running_ = false;
|
||||||
bool paused_ = false;
|
bool paused_ = false;
|
||||||
bool shouldQuit_ = false;
|
bool shouldQuit_ = false;
|
||||||
|
|
||||||
float deltaTime_ = 0.0f;
|
float deltaTime_ = 0.0f;
|
||||||
float totalTime_ = 0.0f;
|
float totalTime_ = 0.0f;
|
||||||
double lastFrameTime_ = 0.0;
|
double lastFrameTime_ = 0.0;
|
||||||
int frameCount_ = 0;
|
int frameCount_ = 0;
|
||||||
float fpsTimer_ = 0.0f;
|
float fpsTimer_ = 0.0f;
|
||||||
int currentFps_ = 0;
|
int currentFps_ = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,10 @@
|
||||||
#include <extra2d/core/types.h>
|
#include <extra2d/core/types.h>
|
||||||
|
|
||||||
// Platform
|
// Platform
|
||||||
#include <extra2d/platform/input.h>
|
#include <extra2d/platform/iinput.h>
|
||||||
#include <extra2d/platform/window.h>
|
#include <extra2d/platform/iwindow.h>
|
||||||
|
#include <extra2d/platform/keys.h>
|
||||||
|
#include <extra2d/platform/platform_module.h>
|
||||||
|
|
||||||
// Graphics
|
// Graphics
|
||||||
#include <extra2d/graphics/camera.h>
|
#include <extra2d/graphics/camera.h>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
class Window;
|
class IWindow;
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// OpenGL 渲染器实现
|
// OpenGL 渲染器实现
|
||||||
|
|
@ -21,7 +21,7 @@ public:
|
||||||
~GLRenderer() override;
|
~GLRenderer() override;
|
||||||
|
|
||||||
// RenderBackend 接口实现
|
// RenderBackend 接口实现
|
||||||
bool init(Window *window) override;
|
bool init(IWindow* window) override;
|
||||||
void shutdown() override;
|
void shutdown() override;
|
||||||
|
|
||||||
void beginFrame(const Color &clearColor) override;
|
void beginFrame(const Color &clearColor) override;
|
||||||
|
|
@ -88,7 +88,7 @@ private:
|
||||||
float r, g, b, a;
|
float r, g, b, a;
|
||||||
};
|
};
|
||||||
|
|
||||||
Window *window_;
|
IWindow* window_;
|
||||||
GLSpriteBatch spriteBatch_;
|
GLSpriteBatch spriteBatch_;
|
||||||
GLShader shapeShader_;
|
GLShader shapeShader_;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
// 前向声明
|
// 前向声明
|
||||||
class Window;
|
class IWindow;
|
||||||
class Texture;
|
class Texture;
|
||||||
class FontAtlas;
|
class FontAtlas;
|
||||||
class Shader;
|
class Shader;
|
||||||
|
|
@ -44,7 +44,7 @@ public:
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// 生命周期
|
// 生命周期
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
virtual bool init(Window *window) = 0;
|
virtual bool init(IWindow* window) = 0;
|
||||||
virtual void shutdown() = 0;
|
virtual void shutdown() = 0;
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,175 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/platform/keys.h>
|
||||||
|
#include <extra2d/core/math_types.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 触摸点信息
|
||||||
|
*/
|
||||||
|
struct TouchPoint {
|
||||||
|
int id = 0;
|
||||||
|
Vec2 position;
|
||||||
|
Vec2 delta;
|
||||||
|
bool pressed = false;
|
||||||
|
bool released = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 输入抽象接口
|
||||||
|
* 所有平台输入后端必须实现此接口
|
||||||
|
*/
|
||||||
|
class IInput {
|
||||||
|
public:
|
||||||
|
virtual ~IInput() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化输入系统
|
||||||
|
*/
|
||||||
|
virtual void init() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 关闭输入系统
|
||||||
|
*/
|
||||||
|
virtual void shutdown() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 每帧更新输入状态
|
||||||
|
*/
|
||||||
|
virtual void update() = 0;
|
||||||
|
|
||||||
|
// ========== 键盘 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检测按键是否按下(持续状态)
|
||||||
|
*/
|
||||||
|
virtual bool down(Key key) const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检测按键是否刚按下(仅当前帧)
|
||||||
|
*/
|
||||||
|
virtual bool pressed(Key key) const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检测按键是否刚释放(仅当前帧)
|
||||||
|
*/
|
||||||
|
virtual bool released(Key key) const = 0;
|
||||||
|
|
||||||
|
// ========== 鼠标 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检测鼠标按钮是否按下
|
||||||
|
*/
|
||||||
|
virtual bool down(Mouse btn) const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检测鼠标按钮是否刚按下
|
||||||
|
*/
|
||||||
|
virtual bool pressed(Mouse btn) const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检测鼠标按钮是否刚释放
|
||||||
|
*/
|
||||||
|
virtual bool released(Mouse btn) const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取鼠标位置
|
||||||
|
*/
|
||||||
|
virtual Vec2 mouse() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取鼠标移动增量
|
||||||
|
*/
|
||||||
|
virtual Vec2 mouseDelta() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取滚轮值
|
||||||
|
*/
|
||||||
|
virtual float scroll() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取滚轮增量
|
||||||
|
*/
|
||||||
|
virtual float scrollDelta() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置鼠标位置
|
||||||
|
*/
|
||||||
|
virtual void setMouse(const Vec2& pos) = 0;
|
||||||
|
|
||||||
|
// ========== 手柄 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检测手柄是否连接
|
||||||
|
*/
|
||||||
|
virtual bool gamepad() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检测手柄按钮是否按下
|
||||||
|
*/
|
||||||
|
virtual bool down(Gamepad btn) const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检测手柄按钮是否刚按下
|
||||||
|
*/
|
||||||
|
virtual bool pressed(Gamepad btn) const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检测手柄按钮是否刚释放
|
||||||
|
*/
|
||||||
|
virtual bool released(Gamepad btn) const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取左摇杆值
|
||||||
|
*/
|
||||||
|
virtual Vec2 leftStick() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取右摇杆值
|
||||||
|
*/
|
||||||
|
virtual Vec2 rightStick() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取左扳机值
|
||||||
|
*/
|
||||||
|
virtual float leftTrigger() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取右扳机值
|
||||||
|
*/
|
||||||
|
virtual float rightTrigger() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置手柄振动
|
||||||
|
* @param left 左马达强度 [0, 1]
|
||||||
|
* @param right 右马达强度 [0, 1]
|
||||||
|
*/
|
||||||
|
virtual void vibrate(float left, float right) = 0;
|
||||||
|
|
||||||
|
// ========== 触摸 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检测是否有触摸
|
||||||
|
*/
|
||||||
|
virtual bool touching() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取触摸点数量
|
||||||
|
*/
|
||||||
|
virtual int touchCount() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取触摸点位置
|
||||||
|
* @param index 触摸点索引
|
||||||
|
*/
|
||||||
|
virtual Vec2 touch(int index) const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取触摸点信息
|
||||||
|
* @param index 触摸点索引
|
||||||
|
*/
|
||||||
|
virtual TouchPoint touchPoint(int index) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -1,173 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <extra2d/core/math_types.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <extra2d/event/input_codes.h>
|
|
||||||
|
|
||||||
#include <SDL.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
class ViewportAdapter;
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 鼠标按钮枚举
|
|
||||||
// ============================================================================
|
|
||||||
enum class MouseButton {
|
|
||||||
Left = 0,
|
|
||||||
Right = 1,
|
|
||||||
Middle = 2,
|
|
||||||
Button4 = 3,
|
|
||||||
Button5 = 4,
|
|
||||||
Button6 = 5,
|
|
||||||
Button7 = 6,
|
|
||||||
Button8 = 7,
|
|
||||||
Count = 8
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Input 类 - 跨平台输入管理
|
|
||||||
// 支持: 键盘、鼠标、手柄、触摸屏
|
|
||||||
// ============================================================================
|
|
||||||
class Input {
|
|
||||||
public:
|
|
||||||
Input();
|
|
||||||
~Input();
|
|
||||||
|
|
||||||
// 初始化
|
|
||||||
void init();
|
|
||||||
void shutdown();
|
|
||||||
|
|
||||||
// 每帧更新
|
|
||||||
void update();
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 键盘输入
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
bool isKeyDown(int keyCode) const;
|
|
||||||
bool isKeyPressed(int keyCode) const;
|
|
||||||
bool isKeyReleased(int keyCode) const;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 手柄按钮
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
bool isButtonDown(int button) const;
|
|
||||||
bool isButtonPressed(int button) const;
|
|
||||||
bool isButtonReleased(int button) const;
|
|
||||||
|
|
||||||
// 摇杆
|
|
||||||
Vec2 getLeftStick() const;
|
|
||||||
Vec2 getRightStick() const;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 鼠标输入
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
bool isMouseDown(MouseButton button) const;
|
|
||||||
bool isMousePressed(MouseButton button) const;
|
|
||||||
bool isMouseReleased(MouseButton button) const;
|
|
||||||
|
|
||||||
Vec2 getMousePosition() const;
|
|
||||||
Vec2 getMouseDelta() const;
|
|
||||||
float getMouseScroll() const { return mouseScroll_; }
|
|
||||||
float getMouseScrollDelta() const { return mouseScroll_ - prevMouseScroll_; }
|
|
||||||
|
|
||||||
void setMousePosition(const Vec2 &position);
|
|
||||||
void setMouseVisible(bool visible);
|
|
||||||
void setMouseLocked(bool locked);
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 触摸屏 (Switch 原生支持,PC 端模拟或禁用)
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
bool isTouching() const { return touching_; }
|
|
||||||
Vec2 getTouchPosition() const { return touchPosition_; }
|
|
||||||
int getTouchCount() const { return touchCount_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 视口适配器
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* @brief 设置视口适配器
|
|
||||||
* @param adapter 视口适配器指针
|
|
||||||
*/
|
|
||||||
void setViewportAdapter(ViewportAdapter* adapter);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取逻辑坐标下的鼠标位置
|
|
||||||
* @return 逻辑坐标
|
|
||||||
*/
|
|
||||||
Vec2 getMousePosLogic() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取逻辑坐标下的触摸位置
|
|
||||||
* @return 逻辑坐标
|
|
||||||
*/
|
|
||||||
Vec2 getTouchPosLogic() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取逻辑坐标下的鼠标增量
|
|
||||||
* @return 逻辑坐标增量
|
|
||||||
*/
|
|
||||||
Vec2 getMouseDeltaLogic() const;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 便捷方法
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
bool isAnyKeyDown() const;
|
|
||||||
bool isAnyMouseDown() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
static constexpr int MAX_BUTTONS = SDL_CONTROLLER_BUTTON_MAX;
|
|
||||||
static constexpr int MAX_KEYS = SDL_NUM_SCANCODES;
|
|
||||||
|
|
||||||
SDL_GameController *controller_;
|
|
||||||
|
|
||||||
// 键盘状态 (PC 端使用)
|
|
||||||
std::array<bool, MAX_KEYS> keysDown_;
|
|
||||||
std::array<bool, MAX_KEYS> prevKeysDown_;
|
|
||||||
|
|
||||||
// 手柄按钮状态
|
|
||||||
std::array<bool, MAX_BUTTONS> buttonsDown_;
|
|
||||||
std::array<bool, MAX_BUTTONS> prevButtonsDown_;
|
|
||||||
|
|
||||||
// 摇杆状态
|
|
||||||
float leftStickX_;
|
|
||||||
float leftStickY_;
|
|
||||||
float rightStickX_;
|
|
||||||
float rightStickY_;
|
|
||||||
|
|
||||||
// 鼠标状态 (PC 端使用)
|
|
||||||
Vec2 mousePosition_;
|
|
||||||
Vec2 prevMousePosition_;
|
|
||||||
float mouseScroll_;
|
|
||||||
float prevMouseScroll_;
|
|
||||||
std::array<bool, 8> mouseButtonsDown_;
|
|
||||||
std::array<bool, 8> prevMouseButtonsDown_;
|
|
||||||
|
|
||||||
// 触摸屏状态 (Switch 原生)
|
|
||||||
bool touching_;
|
|
||||||
bool prevTouching_;
|
|
||||||
Vec2 touchPosition_;
|
|
||||||
Vec2 prevTouchPosition_;
|
|
||||||
int touchCount_;
|
|
||||||
|
|
||||||
// 视口适配器
|
|
||||||
ViewportAdapter* viewportAdapter_;
|
|
||||||
|
|
||||||
// 映射键盘 keyCode 到 SDL GameController 按钮 (Switch 兼容模式)
|
|
||||||
SDL_GameControllerButton mapKeyToButton(int keyCode) const;
|
|
||||||
|
|
||||||
// 更新键盘状态
|
|
||||||
void updateKeyboard();
|
|
||||||
|
|
||||||
// 更新鼠标状态
|
|
||||||
void updateMouse();
|
|
||||||
|
|
||||||
// 更新手柄状态
|
|
||||||
void updateGamepad();
|
|
||||||
|
|
||||||
// 更新触摸屏状态
|
|
||||||
void updateTouch();
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -0,0 +1,218 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/core/math_types.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
class IInput;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 窗口配置数据
|
||||||
|
*/
|
||||||
|
struct WindowConfig {
|
||||||
|
std::string title = "Extra2D Application";
|
||||||
|
int width = 1280;
|
||||||
|
int height = 720;
|
||||||
|
bool fullscreen = false;
|
||||||
|
bool fullscreenDesktop = true;
|
||||||
|
bool resizable = true;
|
||||||
|
bool vsync = true;
|
||||||
|
int msaaSamples = 0;
|
||||||
|
bool centerWindow = true;
|
||||||
|
bool visible = true;
|
||||||
|
bool decorated = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 光标形状
|
||||||
|
*/
|
||||||
|
enum class Cursor {
|
||||||
|
Arrow,
|
||||||
|
IBeam,
|
||||||
|
Crosshair,
|
||||||
|
Hand,
|
||||||
|
HResize,
|
||||||
|
VResize,
|
||||||
|
Hidden
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 窗口抽象接口
|
||||||
|
* 所有平台窗口后端必须实现此接口
|
||||||
|
*/
|
||||||
|
class IWindow {
|
||||||
|
public:
|
||||||
|
virtual ~IWindow() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建窗口
|
||||||
|
* @param cfg 窗口配置
|
||||||
|
* @return 创建是否成功
|
||||||
|
*/
|
||||||
|
virtual bool create(const WindowConfig& cfg) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 销毁窗口
|
||||||
|
*/
|
||||||
|
virtual void destroy() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 轮询事件
|
||||||
|
*/
|
||||||
|
virtual void poll() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 交换缓冲区
|
||||||
|
*/
|
||||||
|
virtual void swap() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 窗口是否应该关闭
|
||||||
|
*/
|
||||||
|
virtual bool shouldClose() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置窗口关闭标志
|
||||||
|
*/
|
||||||
|
virtual void close() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置窗口标题
|
||||||
|
*/
|
||||||
|
virtual void setTitle(const std::string& title) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置窗口大小
|
||||||
|
*/
|
||||||
|
virtual void setSize(int w, int h) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置窗口位置
|
||||||
|
*/
|
||||||
|
virtual void setPos(int x, int y) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置全屏模式
|
||||||
|
*/
|
||||||
|
virtual void setFullscreen(bool fs) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置垂直同步
|
||||||
|
*/
|
||||||
|
virtual void setVSync(bool vsync) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置窗口可见性
|
||||||
|
*/
|
||||||
|
virtual void setVisible(bool visible) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取窗口宽度
|
||||||
|
*/
|
||||||
|
virtual int width() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取窗口高度
|
||||||
|
*/
|
||||||
|
virtual int height() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取窗口大小
|
||||||
|
*/
|
||||||
|
virtual Size size() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取窗口位置
|
||||||
|
*/
|
||||||
|
virtual Vec2 pos() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 是否全屏
|
||||||
|
*/
|
||||||
|
virtual bool fullscreen() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 是否启用垂直同步
|
||||||
|
*/
|
||||||
|
virtual bool vsync() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 窗口是否获得焦点
|
||||||
|
*/
|
||||||
|
virtual bool focused() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 窗口是否最小化
|
||||||
|
*/
|
||||||
|
virtual bool minimized() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取内容缩放X
|
||||||
|
*/
|
||||||
|
virtual float scaleX() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取内容缩放Y
|
||||||
|
*/
|
||||||
|
virtual float scaleY() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置光标形状
|
||||||
|
*/
|
||||||
|
virtual void setCursor(Cursor cursor) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 显示/隐藏光标
|
||||||
|
*/
|
||||||
|
virtual void showCursor(bool show) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 锁定/解锁光标
|
||||||
|
*/
|
||||||
|
virtual void lockCursor(bool lock) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取输入接口
|
||||||
|
*/
|
||||||
|
virtual IInput* input() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 窗口大小改变回调
|
||||||
|
*/
|
||||||
|
using ResizeCb = std::function<void(int, int)>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 窗口关闭回调
|
||||||
|
*/
|
||||||
|
using CloseCb = std::function<void()>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 窗口焦点改变回调
|
||||||
|
*/
|
||||||
|
using FocusCb = std::function<void(bool)>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置大小改变回调
|
||||||
|
*/
|
||||||
|
virtual void onResize(ResizeCb cb) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置关闭回调
|
||||||
|
*/
|
||||||
|
virtual void onClose(CloseCb cb) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置焦点改变回调
|
||||||
|
*/
|
||||||
|
virtual void onFocus(FocusCb cb) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取原生窗口句柄
|
||||||
|
*/
|
||||||
|
virtual void* native() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -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
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/platform/iwindow.h>
|
||||||
|
#include <extra2d/platform/iinput.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 平台模块配置
|
||||||
|
*/
|
||||||
|
struct PlatformModuleConfig {
|
||||||
|
std::string backend = "sdl2";
|
||||||
|
bool gamepad = true;
|
||||||
|
bool touch = true;
|
||||||
|
float deadzone = 0.15f;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 平台后端工厂
|
||||||
|
* 用于注册和创建平台后端
|
||||||
|
*/
|
||||||
|
class BackendFactory {
|
||||||
|
public:
|
||||||
|
using WindowFn = std::function<UniquePtr<IWindow>()>;
|
||||||
|
using InputFn = std::function<UniquePtr<IInput>()>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 注册平台后端
|
||||||
|
* @param name 后端名称
|
||||||
|
* @param win 窗口创建函数
|
||||||
|
* @param in 输入创建函数
|
||||||
|
*/
|
||||||
|
static void reg(const std::string& name, WindowFn win, InputFn in);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建窗口实例
|
||||||
|
* @param name 后端名称
|
||||||
|
* @return 窗口实例,如果后端不存在返回 nullptr
|
||||||
|
*/
|
||||||
|
static UniquePtr<IWindow> createWindow(const std::string& name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建输入实例
|
||||||
|
* @param name 后端名称
|
||||||
|
* @return 输入实例,如果后端不存在返回 nullptr
|
||||||
|
*/
|
||||||
|
static UniquePtr<IInput> createInput(const std::string& name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取所有已注册的后端名称
|
||||||
|
*/
|
||||||
|
static std::vector<std::string> backends();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查后端是否存在
|
||||||
|
*/
|
||||||
|
static bool has(const std::string& name);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct BackendEntry {
|
||||||
|
WindowFn windowFn;
|
||||||
|
InputFn inputFn;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::unordered_map<std::string, BackendEntry>& registry();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 平台后端注册宏
|
||||||
|
* 在全局作用域使用此宏注册平台后端
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* E2D_REG_BACKEND(sdl2, SDL2Window, SDL2Input)
|
||||||
|
*/
|
||||||
|
#define E2D_REG_BACKEND(name, WinClass, InClass) \
|
||||||
|
namespace { \
|
||||||
|
static struct E2D_BACKEND_REG_##name { \
|
||||||
|
E2D_BACKEND_REG_##name() { \
|
||||||
|
::extra2d::BackendFactory::reg( \
|
||||||
|
#name, \
|
||||||
|
[]() -> ::extra2d::UniquePtr<::extra2d::IWindow> { \
|
||||||
|
return ::extra2d::makeUnique<WinClass>(); \
|
||||||
|
}, \
|
||||||
|
[]() -> ::extra2d::UniquePtr<::extra2d::IInput> { \
|
||||||
|
return ::extra2d::makeUnique<InClass>(); \
|
||||||
|
} \
|
||||||
|
); \
|
||||||
|
} \
|
||||||
|
} e2d_backend_reg_##name; \
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -1,156 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include <extra2d/core/math_types.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
#include <SDL.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// 前向声明
|
|
||||||
class EventQueue;
|
|
||||||
class Input;
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 窗口配置
|
|
||||||
// ============================================================================
|
|
||||||
struct WindowConfig {
|
|
||||||
std::string title = "Extra2D Application";
|
|
||||||
int width = 1280;
|
|
||||||
int height = 720;
|
|
||||||
bool fullscreen = true;
|
|
||||||
bool resizable = false;
|
|
||||||
bool vsync = true;
|
|
||||||
int msaaSamples = 0;
|
|
||||||
bool centerWindow = true;
|
|
||||||
bool enableCursors = true;
|
|
||||||
bool enableDpiScale = true;
|
|
||||||
bool fullscreenDesktop =
|
|
||||||
true; // true: SDL_WINDOW_FULLSCREEN_DESKTOP, false: SDL_WINDOW_FULLSCREEN
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 鼠标光标形状枚举
|
|
||||||
// ============================================================================
|
|
||||||
enum class CursorShape {
|
|
||||||
Arrow,
|
|
||||||
IBeam,
|
|
||||||
Crosshair,
|
|
||||||
Hand,
|
|
||||||
HResize,
|
|
||||||
VResize,
|
|
||||||
ResizeAll,
|
|
||||||
ResizeNWSE,
|
|
||||||
ResizeNESW
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Window 类 - SDL2 Window + GLES 3.2 封装
|
|
||||||
// 支持平台: Nintendo Switch, Windows, Linux, macOS
|
|
||||||
// ============================================================================
|
|
||||||
class Window {
|
|
||||||
public:
|
|
||||||
Window();
|
|
||||||
~Window();
|
|
||||||
|
|
||||||
// 创建窗口
|
|
||||||
bool create(const WindowConfig &config);
|
|
||||||
void destroy();
|
|
||||||
|
|
||||||
// 窗口操作
|
|
||||||
void pollEvents();
|
|
||||||
void swapBuffers();
|
|
||||||
bool shouldClose() const;
|
|
||||||
void setShouldClose(bool close);
|
|
||||||
|
|
||||||
// 窗口属性
|
|
||||||
void setTitle(const std::string &title);
|
|
||||||
void setSize(int width, int height);
|
|
||||||
void setPos(int x, int y);
|
|
||||||
void setFullscreen(bool fullscreen);
|
|
||||||
void setVSync(bool enabled);
|
|
||||||
void setResizable(bool resizable);
|
|
||||||
|
|
||||||
// 获取窗口属性
|
|
||||||
int getWidth() const { return width_; }
|
|
||||||
int getHeight() const { return height_; }
|
|
||||||
Size getSize() const {
|
|
||||||
return Size(static_cast<float>(width_), static_cast<float>(height_));
|
|
||||||
}
|
|
||||||
Vec2 getPosition() const;
|
|
||||||
bool isFullscreen() const { return fullscreen_; }
|
|
||||||
bool isVSync() const { return vsync_; }
|
|
||||||
|
|
||||||
// DPI 缩放 (PC 端自动检测,Switch 固定 1.0)
|
|
||||||
float getContentScaleX() const;
|
|
||||||
float getContentScaleY() const;
|
|
||||||
Vec2 getContentScale() const;
|
|
||||||
|
|
||||||
// 窗口状态
|
|
||||||
bool isFocused() const { return focused_; }
|
|
||||||
bool isMinimized() const;
|
|
||||||
bool isMaximized() const;
|
|
||||||
|
|
||||||
// 获取 SDL2 窗口和 GL 上下文
|
|
||||||
SDL_Window *getSDLWindow() const { return sdlWindow_; }
|
|
||||||
SDL_GLContext getGLContext() const { return glContext_; }
|
|
||||||
|
|
||||||
// 设置/获取用户数据
|
|
||||||
void setUserData(void *data) { userData_ = data; }
|
|
||||||
void *getUserData() const { return userData_; }
|
|
||||||
|
|
||||||
// 事件队列
|
|
||||||
void setEventQueue(EventQueue *queue) { eventQueue_ = queue; }
|
|
||||||
EventQueue *getEventQueue() const { return eventQueue_; }
|
|
||||||
|
|
||||||
// 获取输入管理器
|
|
||||||
Input *getInput() const { return input_.get(); }
|
|
||||||
|
|
||||||
// 光标操作 (PC 端有效,Switch 上为空操作)
|
|
||||||
void setCursor(CursorShape shape);
|
|
||||||
void resetCursor();
|
|
||||||
void setMouseVisible(bool visible);
|
|
||||||
|
|
||||||
// 窗口回调
|
|
||||||
using ResizeCallback = std::function<void(int width, int height)>;
|
|
||||||
using FocusCallback = std::function<void(bool focused)>;
|
|
||||||
using CloseCallback = std::function<void()>;
|
|
||||||
|
|
||||||
void setResizeCallback(ResizeCallback callback) {
|
|
||||||
resizeCallback_ = callback;
|
|
||||||
}
|
|
||||||
void setFocusCallback(FocusCallback callback) { focusCallback_ = callback; }
|
|
||||||
void setCloseCallback(CloseCallback callback) { closeCallback_ = callback; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
// SDL2 状态
|
|
||||||
SDL_Window *sdlWindow_;
|
|
||||||
SDL_GLContext glContext_;
|
|
||||||
SDL_Cursor *sdlCursors_[9]; // 光标缓存
|
|
||||||
SDL_Cursor *currentCursor_;
|
|
||||||
|
|
||||||
int width_;
|
|
||||||
int height_;
|
|
||||||
bool vsync_;
|
|
||||||
bool shouldClose_;
|
|
||||||
bool fullscreen_;
|
|
||||||
bool focused_;
|
|
||||||
float contentScaleX_;
|
|
||||||
float contentScaleY_;
|
|
||||||
bool enableDpiScale_;
|
|
||||||
void *userData_;
|
|
||||||
EventQueue *eventQueue_;
|
|
||||||
UniquePtr<Input> input_;
|
|
||||||
|
|
||||||
ResizeCallback resizeCallback_;
|
|
||||||
FocusCallback focusCallback_;
|
|
||||||
CloseCallback closeCallback_;
|
|
||||||
|
|
||||||
bool initSDL(const WindowConfig &config);
|
|
||||||
void deinitSDL();
|
|
||||||
void initCursors();
|
|
||||||
void deinitCursors();
|
|
||||||
void updateContentScale();
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -5,8 +5,8 @@
|
||||||
#include <extra2d/graphics/render_backend.h>
|
#include <extra2d/graphics/render_backend.h>
|
||||||
#include <extra2d/graphics/viewport_adapter.h>
|
#include <extra2d/graphics/viewport_adapter.h>
|
||||||
#include <extra2d/graphics/vram_manager.h>
|
#include <extra2d/graphics/vram_manager.h>
|
||||||
#include <extra2d/platform/input.h>
|
#include <extra2d/platform/iinput.h>
|
||||||
#include <extra2d/platform/window.h>
|
#include <extra2d/platform/platform_module.h>
|
||||||
#include <extra2d/scene/scene_manager.h>
|
#include <extra2d/scene/scene_manager.h>
|
||||||
#include <extra2d/utils/logger.h>
|
#include <extra2d/utils/logger.h>
|
||||||
#include <extra2d/utils/timer.h>
|
#include <extra2d/utils/timer.h>
|
||||||
|
|
@ -20,416 +20,359 @@
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取当前时间(秒)
|
|
||||||
* @return 从某个固定时间点开始的秒数
|
|
||||||
*
|
|
||||||
* 使用高精度时钟获取当前时间,在Switch平台使用clock_gettime,
|
|
||||||
* 其他平台使用std::chrono::steady_clock
|
|
||||||
*/
|
|
||||||
static double getTimeSeconds() {
|
static double getTimeSeconds() {
|
||||||
#ifdef __SWITCH__
|
#ifdef __SWITCH__
|
||||||
struct timespec ts;
|
struct timespec ts;
|
||||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||||
return static_cast<double>(ts.tv_sec) +
|
return static_cast<double>(ts.tv_sec) +
|
||||||
static_cast<double>(ts.tv_nsec) / 1000000000.0;
|
static_cast<double>(ts.tv_nsec) / 1000000000.0;
|
||||||
#else
|
#else
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
auto now = steady_clock::now();
|
auto now = steady_clock::now();
|
||||||
auto duration = now.time_since_epoch();
|
auto duration = now.time_since_epoch();
|
||||||
return duration_cast<std::chrono::duration<double>>(duration).count();
|
return duration_cast<std::chrono::duration<double>>(duration).count();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
Application& Application::get() {
|
||||||
* @brief 获取Application单例实例
|
static Application instance;
|
||||||
* @return Application单例的引用
|
return instance;
|
||||||
*/
|
|
||||||
Application &Application::get() {
|
|
||||||
static Application instance;
|
|
||||||
return instance;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 析构函数,自动关闭应用程序
|
|
||||||
*/
|
|
||||||
Application::~Application() { shutdown(); }
|
Application::~Application() { shutdown(); }
|
||||||
|
|
||||||
/**
|
bool Application::init() {
|
||||||
* @brief 初始化应用程序
|
AppConfig cfg;
|
||||||
* @param config 应用程序配置
|
return init(cfg);
|
||||||
* @return 初始化成功返回true,失败返回false
|
}
|
||||||
*
|
|
||||||
* 初始化窗口、渲染器、场景管理器、定时器管理器、事件系统、相机和视口适配器等核心组件
|
|
||||||
*/
|
|
||||||
bool Application::init(const AppConfig &config) {
|
|
||||||
if (initialized_) {
|
|
||||||
E2D_LOG_WARN("Application already initialized");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
config_ = config;
|
bool Application::init(const AppConfig& config) {
|
||||||
|
if (initialized_) {
|
||||||
|
E2D_LOG_WARN("Application already initialized");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
PlatformType platform = config_.platform;
|
config_ = config;
|
||||||
if (platform == PlatformType::Auto) {
|
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__
|
#ifdef __SWITCH__
|
||||||
platform = PlatformType::Switch;
|
platform = PlatformType::Switch;
|
||||||
#else
|
#else
|
||||||
platform = PlatformType::PC;
|
platform = PlatformType::PC;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
if (platform == PlatformType::Switch) {
|
if (platform == PlatformType::Switch) {
|
||||||
#ifdef __SWITCH__
|
#ifdef __SWITCH__
|
||||||
Result rc;
|
Result rc;
|
||||||
rc = romfsInit();
|
rc = romfsInit();
|
||||||
if (R_SUCCEEDED(rc)) {
|
if (R_SUCCEEDED(rc)) {
|
||||||
E2D_LOG_INFO("RomFS initialized successfully");
|
E2D_LOG_INFO("RomFS initialized successfully");
|
||||||
} else {
|
} else {
|
||||||
E2D_LOG_WARN("romfsInit failed: {:#08X}, will use regular filesystem",
|
E2D_LOG_WARN("romfsInit failed: {:#08X}, will use regular filesystem", rc);
|
||||||
rc);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
rc = socketInitializeDefault();
|
rc = socketInitializeDefault();
|
||||||
if (R_FAILED(rc)) {
|
if (R_FAILED(rc)) {
|
||||||
E2D_LOG_WARN(
|
E2D_LOG_WARN("socketInitializeDefault failed, nxlink will not be available");
|
||||||
"socketInitializeDefault failed, nxlink will not be available");
|
}
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
}
|
|
||||||
|
|
||||||
window_ = makeUnique<Window>();
|
|
||||||
WindowConfig winConfig;
|
|
||||||
winConfig.title = config.title;
|
|
||||||
winConfig.width = config.width;
|
|
||||||
winConfig.height = config.height;
|
|
||||||
if (platform == PlatformType::Switch) {
|
|
||||||
winConfig.fullscreen = true;
|
|
||||||
winConfig.fullscreenDesktop = false;
|
|
||||||
winConfig.resizable = false;
|
|
||||||
winConfig.enableCursors = false;
|
|
||||||
winConfig.enableDpiScale = false;
|
|
||||||
} else {
|
|
||||||
winConfig.fullscreen = config.fullscreen;
|
|
||||||
winConfig.resizable = config.resizable;
|
|
||||||
winConfig.enableCursors = config.enableCursors;
|
|
||||||
winConfig.enableDpiScale = config.enableDpiScale;
|
|
||||||
}
|
|
||||||
winConfig.vsync = config.vsync;
|
|
||||||
winConfig.msaaSamples = config.msaaSamples;
|
|
||||||
|
|
||||||
if (!window_->create(winConfig)) {
|
|
||||||
E2D_LOG_ERROR("Failed to create window");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderer_ = RenderBackend::create(config.renderBackend);
|
|
||||||
if (!renderer_ || !renderer_->init(window_.get())) {
|
|
||||||
E2D_LOG_ERROR("Failed to initialize renderer");
|
|
||||||
window_->destroy();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
sceneManager_ = makeUnique<SceneManager>();
|
|
||||||
timerManager_ = makeUnique<TimerManager>();
|
|
||||||
eventQueue_ = makeUnique<EventQueue>();
|
|
||||||
eventDispatcher_ = makeUnique<EventDispatcher>();
|
|
||||||
camera_ = makeUnique<Camera>(0, static_cast<float>(window_->getWidth()),
|
|
||||||
static_cast<float>(window_->getHeight()), 0);
|
|
||||||
|
|
||||||
viewportAdapter_ = makeUnique<ViewportAdapter>();
|
|
||||||
ViewportConfig vpConfig;
|
|
||||||
vpConfig.logicWidth = static_cast<float>(config.width);
|
|
||||||
vpConfig.logicHeight = static_cast<float>(config.height);
|
|
||||||
vpConfig.mode = ViewportMode::AspectRatio;
|
|
||||||
viewportAdapter_->setConfig(vpConfig);
|
|
||||||
|
|
||||||
camera_->setViewportAdapter(viewportAdapter_.get());
|
|
||||||
input().setViewportAdapter(viewportAdapter_.get());
|
|
||||||
|
|
||||||
viewportAdapter_->update(window_->getWidth(), window_->getHeight());
|
|
||||||
|
|
||||||
window_->setResizeCallback([this](int width, int height) {
|
|
||||||
if (viewportAdapter_) {
|
|
||||||
viewportAdapter_->update(width, height);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (camera_) {
|
std::string backend = config_.backend;
|
||||||
camera_->applyViewportAdapter();
|
#ifdef __SWITCH__
|
||||||
|
backend = "switch";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!BackendFactory::has(backend)) {
|
||||||
|
E2D_LOG_ERROR("Backend '{}' not available", backend);
|
||||||
|
auto backends = BackendFactory::backends();
|
||||||
|
if (backends.empty()) {
|
||||||
|
E2D_LOG_ERROR("No backends registered!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
backend = backends[0];
|
||||||
|
E2D_LOG_WARN("Using fallback backend: {}", backend);
|
||||||
|
}
|
||||||
|
|
||||||
|
window_ = BackendFactory::createWindow(backend);
|
||||||
|
if (!window_) {
|
||||||
|
E2D_LOG_ERROR("Failed to create window for backend: {}", backend);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowConfig winConfig;
|
||||||
|
winConfig.title = config_.title;
|
||||||
|
winConfig.width = config_.width;
|
||||||
|
winConfig.height = config_.height;
|
||||||
|
if (platform == PlatformType::Switch) {
|
||||||
|
winConfig.fullscreen = true;
|
||||||
|
winConfig.fullscreenDesktop = false;
|
||||||
|
winConfig.resizable = false;
|
||||||
|
} else {
|
||||||
|
winConfig.fullscreen = config_.fullscreen;
|
||||||
|
winConfig.resizable = config_.resizable;
|
||||||
|
}
|
||||||
|
winConfig.vsync = config_.vsync;
|
||||||
|
winConfig.msaaSamples = config_.msaaSamples;
|
||||||
|
|
||||||
|
if (!window_->create(winConfig)) {
|
||||||
|
E2D_LOG_ERROR("Failed to create window");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer_ = RenderBackend::create(config_.renderBackend);
|
||||||
|
if (!renderer_ || !renderer_->init(window_.get())) {
|
||||||
|
E2D_LOG_ERROR("Failed to initialize renderer");
|
||||||
|
window_->destroy();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sceneManager_ = makeUnique<SceneManager>();
|
||||||
|
timerManager_ = makeUnique<TimerManager>();
|
||||||
|
eventQueue_ = makeUnique<EventQueue>();
|
||||||
|
eventDispatcher_ = makeUnique<EventDispatcher>();
|
||||||
|
camera_ = makeUnique<Camera>(0, static_cast<float>(window_->width()),
|
||||||
|
static_cast<float>(window_->height()), 0);
|
||||||
|
|
||||||
|
viewportAdapter_ = makeUnique<ViewportAdapter>();
|
||||||
|
ViewportConfig vpConfig;
|
||||||
|
vpConfig.logicWidth = static_cast<float>(config_.width);
|
||||||
|
vpConfig.logicHeight = static_cast<float>(config_.height);
|
||||||
|
vpConfig.mode = ViewportMode::AspectRatio;
|
||||||
|
viewportAdapter_->setConfig(vpConfig);
|
||||||
|
|
||||||
|
camera_->setViewportAdapter(viewportAdapter_.get());
|
||||||
|
|
||||||
|
viewportAdapter_->update(window_->width(), window_->height());
|
||||||
|
|
||||||
|
window_->onResize([this](int width, int height) {
|
||||||
|
if (viewportAdapter_) {
|
||||||
|
viewportAdapter_->update(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (camera_) {
|
||||||
|
camera_->applyViewportAdapter();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sceneManager_) {
|
||||||
|
auto currentScene = sceneManager_->getCurrentScene();
|
||||||
|
if (currentScene) {
|
||||||
|
currentScene->setViewportSize(static_cast<float>(width),
|
||||||
|
static_cast<float>(height));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
initialized_ = true;
|
||||||
|
running_ = true;
|
||||||
|
|
||||||
|
E2D_LOG_INFO("Application initialized (backend: {})", backend);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::shutdown() {
|
||||||
|
if (!initialized_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
E2D_LOG_INFO("Shutting down application...");
|
||||||
|
|
||||||
|
VRAMMgr::get().printStats();
|
||||||
|
|
||||||
|
if (sceneManager_) {
|
||||||
|
sceneManager_->end();
|
||||||
|
}
|
||||||
|
|
||||||
|
sceneManager_.reset();
|
||||||
|
viewportAdapter_.reset();
|
||||||
|
camera_.reset();
|
||||||
|
|
||||||
|
timerManager_.reset();
|
||||||
|
eventQueue_.reset();
|
||||||
|
eventDispatcher_.reset();
|
||||||
|
|
||||||
|
if (renderer_) {
|
||||||
|
renderer_->shutdown();
|
||||||
|
renderer_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window_) {
|
||||||
|
window_->destroy();
|
||||||
|
window_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
PlatformType platform = config_.platform;
|
||||||
|
if (platform == PlatformType::Auto) {
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
platform = PlatformType::Switch;
|
||||||
|
#else
|
||||||
|
platform = PlatformType::PC;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
if (platform == PlatformType::Switch) {
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
romfsExit();
|
||||||
|
socketExit();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
initialized_ = false;
|
||||||
|
running_ = false;
|
||||||
|
|
||||||
|
E2D_LOG_INFO("Application shutdown complete");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::run() {
|
||||||
|
if (!initialized_) {
|
||||||
|
E2D_LOG_ERROR("Application not initialized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastFrameTime_ = getTimeSeconds();
|
||||||
|
|
||||||
|
while (running_ && !window_->shouldClose()) {
|
||||||
|
mainLoop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::quit() {
|
||||||
|
shouldQuit_ = true;
|
||||||
|
running_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::pause() {
|
||||||
|
if (!paused_) {
|
||||||
|
paused_ = true;
|
||||||
|
E2D_LOG_INFO("Application paused");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::resume() {
|
||||||
|
if (paused_) {
|
||||||
|
paused_ = false;
|
||||||
|
lastFrameTime_ = getTimeSeconds();
|
||||||
|
E2D_LOG_INFO("Application resumed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::mainLoop() {
|
||||||
|
double currentTime = getTimeSeconds();
|
||||||
|
deltaTime_ = static_cast<float>(currentTime - lastFrameTime_);
|
||||||
|
lastFrameTime_ = currentTime;
|
||||||
|
|
||||||
|
totalTime_ += deltaTime_;
|
||||||
|
|
||||||
|
frameCount_++;
|
||||||
|
fpsTimer_ += deltaTime_;
|
||||||
|
if (fpsTimer_ >= 1.0f) {
|
||||||
|
currentFps_ = frameCount_;
|
||||||
|
frameCount_ = 0;
|
||||||
|
fpsTimer_ -= 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
window_->poll();
|
||||||
|
|
||||||
|
if (eventDispatcher_ && eventQueue_) {
|
||||||
|
eventDispatcher_->processQueue(*eventQueue_);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!paused_) {
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
render();
|
||||||
|
|
||||||
|
if (!config_.vsync && config_.fpsLimit > 0) {
|
||||||
|
double frameEndTime = getTimeSeconds();
|
||||||
|
double frameTime = frameEndTime - currentTime;
|
||||||
|
double target = 1.0 / static_cast<double>(config_.fpsLimit);
|
||||||
|
if (frameTime < target) {
|
||||||
|
auto sleepSeconds = target - frameTime;
|
||||||
|
std::this_thread::sleep_for(std::chrono::duration<double>(sleepSeconds));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::update() {
|
||||||
|
if (timerManager_) {
|
||||||
|
timerManager_->update(deltaTime_);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sceneManager_) {
|
if (sceneManager_) {
|
||||||
auto currentScene = sceneManager_->getCurrentScene();
|
sceneManager_->update(deltaTime_);
|
||||||
if (currentScene) {
|
|
||||||
currentScene->setViewportSize(static_cast<float>(width),
|
|
||||||
static_cast<float>(height));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
initialized_ = true;
|
|
||||||
running_ = true;
|
|
||||||
|
|
||||||
E2D_LOG_INFO("Application initialized successfully");
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 关闭应用程序
|
|
||||||
*
|
|
||||||
* 释放所有资源,包括场景管理器、渲染器、窗口等,并关闭平台相关服务
|
|
||||||
*/
|
|
||||||
void Application::shutdown() {
|
|
||||||
if (!initialized_)
|
|
||||||
return;
|
|
||||||
|
|
||||||
E2D_LOG_INFO("Shutting down application...");
|
|
||||||
|
|
||||||
VRAMMgr::get().printStats();
|
|
||||||
|
|
||||||
if (sceneManager_) {
|
|
||||||
sceneManager_->end();
|
|
||||||
}
|
|
||||||
|
|
||||||
sceneManager_.reset();
|
|
||||||
viewportAdapter_.reset();
|
|
||||||
camera_.reset();
|
|
||||||
|
|
||||||
timerManager_.reset();
|
|
||||||
eventQueue_.reset();
|
|
||||||
eventDispatcher_.reset();
|
|
||||||
|
|
||||||
if (renderer_) {
|
|
||||||
renderer_->shutdown();
|
|
||||||
renderer_.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (window_) {
|
|
||||||
window_->destroy();
|
|
||||||
window_.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
PlatformType platform = config_.platform;
|
|
||||||
if (platform == PlatformType::Auto) {
|
|
||||||
#ifdef __SWITCH__
|
|
||||||
platform = PlatformType::Switch;
|
|
||||||
#else
|
|
||||||
platform = PlatformType::PC;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
if (platform == PlatformType::Switch) {
|
|
||||||
#ifdef __SWITCH__
|
|
||||||
romfsExit();
|
|
||||||
socketExit();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
initialized_ = false;
|
|
||||||
running_ = false;
|
|
||||||
|
|
||||||
E2D_LOG_INFO("Application shutdown complete");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 运行应用程序主循环
|
|
||||||
*
|
|
||||||
* 进入应用程序主循环,持续处理事件、更新和渲染直到应用程序退出
|
|
||||||
*/
|
|
||||||
void Application::run() {
|
|
||||||
if (!initialized_) {
|
|
||||||
E2D_LOG_ERROR("Application not initialized");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastFrameTime_ = getTimeSeconds();
|
|
||||||
|
|
||||||
#ifdef __SWITCH__
|
|
||||||
while (running_ && !window_->shouldClose()) {
|
|
||||||
mainLoop();
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
while (running_ && !window_->shouldClose()) {
|
|
||||||
mainLoop();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 请求退出应用程序
|
|
||||||
*
|
|
||||||
* 设置退出标志,主循环将在下一次迭代时退出
|
|
||||||
*/
|
|
||||||
void Application::quit() {
|
|
||||||
shouldQuit_ = true;
|
|
||||||
running_ = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 暂停应用程序
|
|
||||||
*
|
|
||||||
* 暂停应用程序更新,渲染继续进行
|
|
||||||
*/
|
|
||||||
void Application::pause() {
|
|
||||||
if (!paused_) {
|
|
||||||
paused_ = true;
|
|
||||||
E2D_LOG_INFO("Application paused");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 恢复应用程序
|
|
||||||
*
|
|
||||||
* 恢复应用程序更新,重置帧时间以避免大的deltaTime跳跃
|
|
||||||
*/
|
|
||||||
void Application::resume() {
|
|
||||||
if (paused_) {
|
|
||||||
paused_ = false;
|
|
||||||
lastFrameTime_ = getTimeSeconds();
|
|
||||||
E2D_LOG_INFO("Application resumed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 主循环迭代
|
|
||||||
*
|
|
||||||
* 执行一次主循环迭代,包括计算帧时间、处理事件、更新和渲染
|
|
||||||
*/
|
|
||||||
void Application::mainLoop() {
|
|
||||||
double currentTime = getTimeSeconds();
|
|
||||||
deltaTime_ = static_cast<float>(currentTime - lastFrameTime_);
|
|
||||||
lastFrameTime_ = currentTime;
|
|
||||||
|
|
||||||
totalTime_ += deltaTime_;
|
|
||||||
|
|
||||||
frameCount_++;
|
|
||||||
fpsTimer_ += deltaTime_;
|
|
||||||
if (fpsTimer_ >= 1.0f) {
|
|
||||||
currentFps_ = frameCount_;
|
|
||||||
frameCount_ = 0;
|
|
||||||
fpsTimer_ -= 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
window_->pollEvents();
|
|
||||||
|
|
||||||
if (eventDispatcher_ && eventQueue_) {
|
|
||||||
eventDispatcher_->processQueue(*eventQueue_);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!paused_) {
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
render();
|
|
||||||
|
|
||||||
if (!config_.vsync && config_.fpsLimit > 0) {
|
|
||||||
double frameEndTime = getTimeSeconds();
|
|
||||||
double frameTime = frameEndTime - currentTime;
|
|
||||||
double target = 1.0 / static_cast<double>(config_.fpsLimit);
|
|
||||||
if (frameTime < target) {
|
|
||||||
auto sleepSeconds = target - frameTime;
|
|
||||||
std::this_thread::sleep_for(std::chrono::duration<double>(sleepSeconds));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 更新应用程序状态
|
|
||||||
*
|
|
||||||
* 更新定时器管理器和场景管理器
|
|
||||||
*/
|
|
||||||
void Application::update() {
|
|
||||||
if (timerManager_) {
|
|
||||||
timerManager_->update(deltaTime_);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sceneManager_) {
|
|
||||||
sceneManager_->update(deltaTime_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 渲染应用程序画面
|
|
||||||
*
|
|
||||||
* 设置视口并渲染当前场景
|
|
||||||
*/
|
|
||||||
void Application::render() {
|
void Application::render() {
|
||||||
if (!renderer_) {
|
if (!renderer_) {
|
||||||
E2D_LOG_ERROR("Render failed: renderer is null");
|
E2D_LOG_ERROR("Render failed: renderer is null");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (viewportAdapter_) {
|
if (viewportAdapter_) {
|
||||||
const auto &vp = viewportAdapter_->getViewport();
|
const auto& vp = viewportAdapter_->getViewport();
|
||||||
renderer_->setViewport(
|
renderer_->setViewport(
|
||||||
static_cast<int>(vp.origin.x), static_cast<int>(vp.origin.y),
|
static_cast<int>(vp.origin.x), static_cast<int>(vp.origin.y),
|
||||||
static_cast<int>(vp.size.width), static_cast<int>(vp.size.height));
|
static_cast<int>(vp.size.width), static_cast<int>(vp.size.height));
|
||||||
} else {
|
} else {
|
||||||
renderer_->setViewport(0, 0, window_->getWidth(), window_->getHeight());
|
renderer_->setViewport(0, 0, window_->width(), window_->height());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sceneManager_) {
|
if (sceneManager_) {
|
||||||
sceneManager_->render(*renderer_);
|
sceneManager_->render(*renderer_);
|
||||||
} else {
|
} else {
|
||||||
E2D_LOG_WARN("Render: sceneManager is null");
|
E2D_LOG_WARN("Render: sceneManager is null");
|
||||||
}
|
}
|
||||||
|
|
||||||
window_->swapBuffers();
|
window_->swap();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
IInput& Application::input() { return *window_->input(); }
|
||||||
* @brief 获取输入管理器
|
|
||||||
* @return 输入管理器的引用
|
|
||||||
*/
|
|
||||||
Input &Application::input() { return *window_->getInput(); }
|
|
||||||
|
|
||||||
/**
|
SceneManager& Application::scenes() { return *sceneManager_; }
|
||||||
* @brief 获取场景管理器
|
|
||||||
* @return 场景管理器的引用
|
|
||||||
*/
|
|
||||||
SceneManager &Application::scenes() { return *sceneManager_; }
|
|
||||||
|
|
||||||
/**
|
TimerManager& Application::timers() { return *timerManager_; }
|
||||||
* @brief 获取定时器管理器
|
|
||||||
* @return 定时器管理器的引用
|
|
||||||
*/
|
|
||||||
TimerManager &Application::timers() { return *timerManager_; }
|
|
||||||
|
|
||||||
/**
|
EventQueue& Application::eventQueue() { return *eventQueue_; }
|
||||||
* @brief 获取事件队列
|
|
||||||
* @return 事件队列的引用
|
|
||||||
*/
|
|
||||||
EventQueue &Application::eventQueue() { return *eventQueue_; }
|
|
||||||
|
|
||||||
/**
|
EventDispatcher& Application::eventDispatcher() { return *eventDispatcher_; }
|
||||||
* @brief 获取事件分发器
|
|
||||||
* @return 事件分发器的引用
|
|
||||||
*/
|
|
||||||
EventDispatcher &Application::eventDispatcher() { return *eventDispatcher_; }
|
|
||||||
|
|
||||||
/**
|
Camera& Application::camera() { return *camera_; }
|
||||||
* @brief 获取相机
|
|
||||||
* @return 相机的引用
|
|
||||||
*/
|
|
||||||
Camera &Application::camera() { return *camera_; }
|
|
||||||
|
|
||||||
/**
|
ViewportAdapter& Application::viewportAdapter() { return *viewportAdapter_; }
|
||||||
* @brief 获取视口适配器
|
|
||||||
* @return 视口适配器的引用
|
|
||||||
*/
|
|
||||||
ViewportAdapter &Application::viewportAdapter() { return *viewportAdapter_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 进入指定场景
|
|
||||||
* @param scene 要进入的场景
|
|
||||||
*
|
|
||||||
* 设置场景的视口大小并将其设置为当前场景
|
|
||||||
*/
|
|
||||||
void Application::enterScene(Ptr<Scene> scene) {
|
void Application::enterScene(Ptr<Scene> scene) {
|
||||||
if (sceneManager_ && scene) {
|
if (sceneManager_ && scene) {
|
||||||
scene->setViewportSize(static_cast<float>(window_->getWidth()),
|
scene->setViewportSize(static_cast<float>(window_->width()),
|
||||||
static_cast<float>(window_->getHeight()));
|
static_cast<float>(window_->height()));
|
||||||
sceneManager_->enterScene(scene);
|
sceneManager_->enterScene(scene);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
#include <extra2d/graphics/opengl/gl_renderer.h>
|
#include <extra2d/graphics/opengl/gl_renderer.h>
|
||||||
#include <extra2d/graphics/opengl/gl_texture.h>
|
#include <extra2d/graphics/opengl/gl_texture.h>
|
||||||
#include <extra2d/graphics/vram_manager.h>
|
#include <extra2d/graphics/vram_manager.h>
|
||||||
#include <extra2d/platform/window.h>
|
#include <extra2d/platform/iwindow.h>
|
||||||
#include <extra2d/utils/logger.h>
|
#include <extra2d/utils/logger.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
|
@ -85,7 +85,7 @@ GLRenderer::~GLRenderer() { shutdown(); }
|
||||||
* @param window 窗口指针
|
* @param window 窗口指针
|
||||||
* @return 初始化成功返回true,失败返回false
|
* @return 初始化成功返回true,失败返回false
|
||||||
*/
|
*/
|
||||||
bool GLRenderer::init(Window *window) {
|
bool GLRenderer::init(IWindow* window) {
|
||||||
window_ = window;
|
window_ = window;
|
||||||
|
|
||||||
// Switch: GL 上下文已通过 SDL2 + EGL 初始化,无需 glewInit()
|
// Switch: GL 上下文已通过 SDL2 + EGL 初始化,无需 glewInit()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
#include "sdl2_window.h"
|
||||||
|
#include "sdl2_input.h"
|
||||||
|
#include <extra2d/platform/platform_module.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
E2D_REG_BACKEND(sdl2, SDL2Window, SDL2Input)
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,386 @@
|
||||||
|
#include "sdl2_input.h"
|
||||||
|
#include <extra2d/utils/logger.h>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
SDL2Input::SDL2Input() {
|
||||||
|
keyCurrent_.fill(false);
|
||||||
|
keyPrevious_.fill(false);
|
||||||
|
mouseCurrent_.fill(false);
|
||||||
|
mousePrevious_.fill(false);
|
||||||
|
gamepadCurrent_.fill(false);
|
||||||
|
gamepadPrevious_.fill(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL2Input::~SDL2Input() {
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL2Input::init() {
|
||||||
|
SDL_GameControllerEventState(SDL_ENABLE);
|
||||||
|
openGamepad();
|
||||||
|
E2D_LOG_DEBUG("SDL2 input initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL2Input::shutdown() {
|
||||||
|
closeGamepad();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL2Input::update() {
|
||||||
|
keyPrevious_ = keyCurrent_;
|
||||||
|
mousePrevious_ = mouseCurrent_;
|
||||||
|
gamepadPrevious_ = gamepadCurrent_;
|
||||||
|
scrollDelta_ = 0.0f;
|
||||||
|
mouseDelta_ = Vec2::Zero();
|
||||||
|
|
||||||
|
updateKeyboard();
|
||||||
|
updateMouse();
|
||||||
|
updateGamepad();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDL2Input::down(Key key) const {
|
||||||
|
size_t idx = static_cast<size_t>(key);
|
||||||
|
return idx < keyCurrent_.size() ? keyCurrent_[idx] : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDL2Input::pressed(Key key) const {
|
||||||
|
size_t idx = static_cast<size_t>(key);
|
||||||
|
return idx < keyCurrent_.size() ? (keyCurrent_[idx] && !keyPrevious_[idx]) : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDL2Input::released(Key key) const {
|
||||||
|
size_t idx = static_cast<size_t>(key);
|
||||||
|
return idx < keyCurrent_.size() ? (!keyCurrent_[idx] && keyPrevious_[idx]) : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDL2Input::down(Mouse btn) const {
|
||||||
|
size_t idx = static_cast<size_t>(btn);
|
||||||
|
return idx < mouseCurrent_.size() ? mouseCurrent_[idx] : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDL2Input::pressed(Mouse btn) const {
|
||||||
|
size_t idx = static_cast<size_t>(btn);
|
||||||
|
return idx < mouseCurrent_.size() ? (mouseCurrent_[idx] && !mousePrevious_[idx]) : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDL2Input::released(Mouse btn) const {
|
||||||
|
size_t idx = static_cast<size_t>(btn);
|
||||||
|
return idx < mouseCurrent_.size() ? (!mouseCurrent_[idx] && mousePrevious_[idx]) : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2 SDL2Input::mouse() const {
|
||||||
|
return mousePos_;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2 SDL2Input::mouseDelta() const {
|
||||||
|
return mouseDelta_;
|
||||||
|
}
|
||||||
|
|
||||||
|
float SDL2Input::scroll() const {
|
||||||
|
return scroll_;
|
||||||
|
}
|
||||||
|
|
||||||
|
float SDL2Input::scrollDelta() const {
|
||||||
|
return scrollDelta_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL2Input::setMouse(const Vec2& pos) {
|
||||||
|
SDL_WarpMouseInWindow(nullptr, static_cast<int>(pos.x), static_cast<int>(pos.y));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDL2Input::gamepad() const {
|
||||||
|
return gamepad_ != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDL2Input::down(Gamepad btn) const {
|
||||||
|
size_t idx = static_cast<size_t>(btn);
|
||||||
|
return idx < gamepadCurrent_.size() ? gamepadCurrent_[idx] : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDL2Input::pressed(Gamepad btn) const {
|
||||||
|
size_t idx = static_cast<size_t>(btn);
|
||||||
|
return idx < gamepadCurrent_.size() ? (gamepadCurrent_[idx] && !gamepadPrevious_[idx]) : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDL2Input::released(Gamepad btn) const {
|
||||||
|
size_t idx = static_cast<size_t>(btn);
|
||||||
|
return idx < gamepadCurrent_.size() ? (!gamepadCurrent_[idx] && gamepadPrevious_[idx]) : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2 SDL2Input::leftStick() const {
|
||||||
|
return leftStick_;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2 SDL2Input::rightStick() const {
|
||||||
|
return rightStick_;
|
||||||
|
}
|
||||||
|
|
||||||
|
float SDL2Input::leftTrigger() const {
|
||||||
|
return leftTrigger_;
|
||||||
|
}
|
||||||
|
|
||||||
|
float SDL2Input::rightTrigger() const {
|
||||||
|
return rightTrigger_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL2Input::vibrate(float left, float right) {
|
||||||
|
if (gamepad_) {
|
||||||
|
Uint16 lowFreq = static_cast<Uint16>(std::clamp(left, 0.0f, 1.0f) * 65535);
|
||||||
|
Uint16 highFreq = static_cast<Uint16>(std::clamp(right, 0.0f, 1.0f) * 65535);
|
||||||
|
SDL_GameControllerRumble(gamepad_, lowFreq, highFreq, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDL2Input::touching() const {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int SDL2Input::touchCount() const {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2 SDL2Input::touch(int index) const {
|
||||||
|
(void)index;
|
||||||
|
return Vec2::Zero();
|
||||||
|
}
|
||||||
|
|
||||||
|
TouchPoint SDL2Input::touchPoint(int index) const {
|
||||||
|
(void)index;
|
||||||
|
return TouchPoint{};
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL2Input::updateKeyboard() {
|
||||||
|
int numKeys = 0;
|
||||||
|
const Uint8* state = SDL_GetKeyboardState(&numKeys);
|
||||||
|
|
||||||
|
auto updateKey = [&](Key key, int sdlScancode) {
|
||||||
|
size_t idx = static_cast<size_t>(key);
|
||||||
|
if (idx < keyCurrent_.size() && sdlScancode < numKeys) {
|
||||||
|
keyCurrent_[idx] = state[sdlScancode] != 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
updateKey(Key::A, SDL_SCANCODE_A);
|
||||||
|
updateKey(Key::B, SDL_SCANCODE_B);
|
||||||
|
updateKey(Key::C, SDL_SCANCODE_C);
|
||||||
|
updateKey(Key::D, SDL_SCANCODE_D);
|
||||||
|
updateKey(Key::E, SDL_SCANCODE_E);
|
||||||
|
updateKey(Key::F, SDL_SCANCODE_F);
|
||||||
|
updateKey(Key::G, SDL_SCANCODE_G);
|
||||||
|
updateKey(Key::H, SDL_SCANCODE_H);
|
||||||
|
updateKey(Key::I, SDL_SCANCODE_I);
|
||||||
|
updateKey(Key::J, SDL_SCANCODE_J);
|
||||||
|
updateKey(Key::K, SDL_SCANCODE_K);
|
||||||
|
updateKey(Key::L, SDL_SCANCODE_L);
|
||||||
|
updateKey(Key::M, SDL_SCANCODE_M);
|
||||||
|
updateKey(Key::N, SDL_SCANCODE_N);
|
||||||
|
updateKey(Key::O, SDL_SCANCODE_O);
|
||||||
|
updateKey(Key::P, SDL_SCANCODE_P);
|
||||||
|
updateKey(Key::Q, SDL_SCANCODE_Q);
|
||||||
|
updateKey(Key::R, SDL_SCANCODE_R);
|
||||||
|
updateKey(Key::S, SDL_SCANCODE_S);
|
||||||
|
updateKey(Key::T, SDL_SCANCODE_T);
|
||||||
|
updateKey(Key::U, SDL_SCANCODE_U);
|
||||||
|
updateKey(Key::V, SDL_SCANCODE_V);
|
||||||
|
updateKey(Key::W, SDL_SCANCODE_W);
|
||||||
|
updateKey(Key::X, SDL_SCANCODE_X);
|
||||||
|
updateKey(Key::Y, SDL_SCANCODE_Y);
|
||||||
|
updateKey(Key::Z, SDL_SCANCODE_Z);
|
||||||
|
updateKey(Key::Num0, SDL_SCANCODE_0);
|
||||||
|
updateKey(Key::Num1, SDL_SCANCODE_1);
|
||||||
|
updateKey(Key::Num2, SDL_SCANCODE_2);
|
||||||
|
updateKey(Key::Num3, SDL_SCANCODE_3);
|
||||||
|
updateKey(Key::Num4, SDL_SCANCODE_4);
|
||||||
|
updateKey(Key::Num5, SDL_SCANCODE_5);
|
||||||
|
updateKey(Key::Num6, SDL_SCANCODE_6);
|
||||||
|
updateKey(Key::Num7, SDL_SCANCODE_7);
|
||||||
|
updateKey(Key::Num8, SDL_SCANCODE_8);
|
||||||
|
updateKey(Key::Num9, SDL_SCANCODE_9);
|
||||||
|
updateKey(Key::F1, SDL_SCANCODE_F1);
|
||||||
|
updateKey(Key::F2, SDL_SCANCODE_F2);
|
||||||
|
updateKey(Key::F3, SDL_SCANCODE_F3);
|
||||||
|
updateKey(Key::F4, SDL_SCANCODE_F4);
|
||||||
|
updateKey(Key::F5, SDL_SCANCODE_F5);
|
||||||
|
updateKey(Key::F6, SDL_SCANCODE_F6);
|
||||||
|
updateKey(Key::F7, SDL_SCANCODE_F7);
|
||||||
|
updateKey(Key::F8, SDL_SCANCODE_F8);
|
||||||
|
updateKey(Key::F9, SDL_SCANCODE_F9);
|
||||||
|
updateKey(Key::F10, SDL_SCANCODE_F10);
|
||||||
|
updateKey(Key::F11, SDL_SCANCODE_F11);
|
||||||
|
updateKey(Key::F12, SDL_SCANCODE_F12);
|
||||||
|
updateKey(Key::Space, SDL_SCANCODE_SPACE);
|
||||||
|
updateKey(Key::Enter, SDL_SCANCODE_RETURN);
|
||||||
|
updateKey(Key::Escape, SDL_SCANCODE_ESCAPE);
|
||||||
|
updateKey(Key::Tab, SDL_SCANCODE_TAB);
|
||||||
|
updateKey(Key::Backspace, SDL_SCANCODE_BACKSPACE);
|
||||||
|
updateKey(Key::Insert, SDL_SCANCODE_INSERT);
|
||||||
|
updateKey(Key::Delete, SDL_SCANCODE_DELETE);
|
||||||
|
updateKey(Key::Home, SDL_SCANCODE_HOME);
|
||||||
|
updateKey(Key::End, SDL_SCANCODE_END);
|
||||||
|
updateKey(Key::PageUp, SDL_SCANCODE_PAGEUP);
|
||||||
|
updateKey(Key::PageDown, SDL_SCANCODE_PAGEDOWN);
|
||||||
|
updateKey(Key::Up, SDL_SCANCODE_UP);
|
||||||
|
updateKey(Key::Down, SDL_SCANCODE_DOWN);
|
||||||
|
updateKey(Key::Left, SDL_SCANCODE_LEFT);
|
||||||
|
updateKey(Key::Right, SDL_SCANCODE_RIGHT);
|
||||||
|
updateKey(Key::LShift, SDL_SCANCODE_LSHIFT);
|
||||||
|
updateKey(Key::RShift, SDL_SCANCODE_RSHIFT);
|
||||||
|
updateKey(Key::LCtrl, SDL_SCANCODE_LCTRL);
|
||||||
|
updateKey(Key::RCtrl, SDL_SCANCODE_RCTRL);
|
||||||
|
updateKey(Key::LAlt, SDL_SCANCODE_LALT);
|
||||||
|
updateKey(Key::RAlt, SDL_SCANCODE_RALT);
|
||||||
|
updateKey(Key::CapsLock, SDL_SCANCODE_CAPSLOCK);
|
||||||
|
updateKey(Key::NumLock, SDL_SCANCODE_NUMLOCKCLEAR);
|
||||||
|
updateKey(Key::ScrollLock, SDL_SCANCODE_SCROLLLOCK);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL2Input::updateMouse() {
|
||||||
|
int x, y;
|
||||||
|
Uint32 state = SDL_GetMouseState(&x, &y);
|
||||||
|
|
||||||
|
Vec2 newPos(static_cast<float>(x), static_cast<float>(y));
|
||||||
|
mouseDelta_ = newPos - mousePos_;
|
||||||
|
mousePos_ = newPos;
|
||||||
|
|
||||||
|
mouseCurrent_[static_cast<size_t>(Mouse::Left)] = (state & SDL_BUTTON_LMASK) != 0;
|
||||||
|
mouseCurrent_[static_cast<size_t>(Mouse::Right)] = (state & SDL_BUTTON_RMASK) != 0;
|
||||||
|
mouseCurrent_[static_cast<size_t>(Mouse::Middle)] = (state & SDL_BUTTON_MMASK) != 0;
|
||||||
|
mouseCurrent_[static_cast<size_t>(Mouse::X1)] = (state & SDL_BUTTON_X1MASK) != 0;
|
||||||
|
mouseCurrent_[static_cast<size_t>(Mouse::X2)] = (state & SDL_BUTTON_X2MASK) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL2Input::updateGamepad() {
|
||||||
|
if (!gamepad_) {
|
||||||
|
openGamepad();
|
||||||
|
if (!gamepad_) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto applyDeadzone = [this](float value) -> float {
|
||||||
|
if (std::abs(value) < deadzone_) return 0.0f;
|
||||||
|
float sign = value >= 0 ? 1.0f : -1.0f;
|
||||||
|
return sign * (std::abs(value) - deadzone_) / (1.0f - deadzone_);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto getAxis = [this](SDL_GameControllerAxis axis) -> float {
|
||||||
|
if (!gamepad_) return 0.0f;
|
||||||
|
Sint16 value = SDL_GameControllerGetAxis(gamepad_, axis);
|
||||||
|
return static_cast<float>(value) / 32767.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto getButton = [this](SDL_GameControllerButton btn) -> bool {
|
||||||
|
return gamepad_ ? SDL_GameControllerGetButton(gamepad_, btn) != 0 : false;
|
||||||
|
};
|
||||||
|
|
||||||
|
leftStick_.x = applyDeadzone(getAxis(SDL_CONTROLLER_AXIS_LEFTX));
|
||||||
|
leftStick_.y = applyDeadzone(getAxis(SDL_CONTROLLER_AXIS_LEFTY));
|
||||||
|
rightStick_.x = applyDeadzone(getAxis(SDL_CONTROLLER_AXIS_RIGHTX));
|
||||||
|
rightStick_.y = applyDeadzone(getAxis(SDL_CONTROLLER_AXIS_RIGHTY));
|
||||||
|
|
||||||
|
leftTrigger_ = getAxis(SDL_CONTROLLER_AXIS_TRIGGERLEFT);
|
||||||
|
rightTrigger_ = getAxis(SDL_CONTROLLER_AXIS_TRIGGERRIGHT);
|
||||||
|
|
||||||
|
gamepadCurrent_[static_cast<size_t>(Gamepad::A)] = getButton(SDL_CONTROLLER_BUTTON_A);
|
||||||
|
gamepadCurrent_[static_cast<size_t>(Gamepad::B)] = getButton(SDL_CONTROLLER_BUTTON_B);
|
||||||
|
gamepadCurrent_[static_cast<size_t>(Gamepad::X)] = getButton(SDL_CONTROLLER_BUTTON_X);
|
||||||
|
gamepadCurrent_[static_cast<size_t>(Gamepad::Y)] = getButton(SDL_CONTROLLER_BUTTON_Y);
|
||||||
|
gamepadCurrent_[static_cast<size_t>(Gamepad::LB)] = getButton(SDL_CONTROLLER_BUTTON_LEFTSHOULDER);
|
||||||
|
gamepadCurrent_[static_cast<size_t>(Gamepad::RB)] = getButton(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER);
|
||||||
|
gamepadCurrent_[static_cast<size_t>(Gamepad::Back)] = getButton(SDL_CONTROLLER_BUTTON_BACK);
|
||||||
|
gamepadCurrent_[static_cast<size_t>(Gamepad::Start)] = getButton(SDL_CONTROLLER_BUTTON_START);
|
||||||
|
gamepadCurrent_[static_cast<size_t>(Gamepad::Guide)] = getButton(SDL_CONTROLLER_BUTTON_GUIDE);
|
||||||
|
gamepadCurrent_[static_cast<size_t>(Gamepad::LStick)] = getButton(SDL_CONTROLLER_BUTTON_LEFTSTICK);
|
||||||
|
gamepadCurrent_[static_cast<size_t>(Gamepad::RStick)] = getButton(SDL_CONTROLLER_BUTTON_RIGHTSTICK);
|
||||||
|
gamepadCurrent_[static_cast<size_t>(Gamepad::DUp)] = getButton(SDL_CONTROLLER_BUTTON_DPAD_UP);
|
||||||
|
gamepadCurrent_[static_cast<size_t>(Gamepad::DDown)] = getButton(SDL_CONTROLLER_BUTTON_DPAD_DOWN);
|
||||||
|
gamepadCurrent_[static_cast<size_t>(Gamepad::DLeft)] = getButton(SDL_CONTROLLER_BUTTON_DPAD_LEFT);
|
||||||
|
gamepadCurrent_[static_cast<size_t>(Gamepad::DRight)] = getButton(SDL_CONTROLLER_BUTTON_DPAD_RIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL2Input::openGamepad() {
|
||||||
|
for (int i = 0; i < SDL_NumJoysticks(); ++i) {
|
||||||
|
if (SDL_IsGameController(i)) {
|
||||||
|
gamepad_ = SDL_GameControllerOpen(i);
|
||||||
|
if (gamepad_) {
|
||||||
|
gamepadIndex_ = i;
|
||||||
|
E2D_LOG_INFO("Gamepad connected: {}", SDL_GameControllerName(gamepad_));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL2Input::closeGamepad() {
|
||||||
|
if (gamepad_) {
|
||||||
|
SDL_GameControllerClose(gamepad_);
|
||||||
|
gamepad_ = nullptr;
|
||||||
|
gamepadIndex_ = -1;
|
||||||
|
E2D_LOG_INFO("Gamepad disconnected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int SDL2Input::keyToSDL(Key key) {
|
||||||
|
switch (key) {
|
||||||
|
case Key::A: return SDL_SCANCODE_A;
|
||||||
|
case Key::B: return SDL_SCANCODE_B;
|
||||||
|
case Key::C: return SDL_SCANCODE_C;
|
||||||
|
case Key::D: return SDL_SCANCODE_D;
|
||||||
|
case Key::E: return SDL_SCANCODE_E;
|
||||||
|
case Key::F: return SDL_SCANCODE_F;
|
||||||
|
case Key::G: return SDL_SCANCODE_G;
|
||||||
|
case Key::H: return SDL_SCANCODE_H;
|
||||||
|
case Key::I: return SDL_SCANCODE_I;
|
||||||
|
case Key::J: return SDL_SCANCODE_J;
|
||||||
|
case Key::K: return SDL_SCANCODE_K;
|
||||||
|
case Key::L: return SDL_SCANCODE_L;
|
||||||
|
case Key::M: return SDL_SCANCODE_M;
|
||||||
|
case Key::N: return SDL_SCANCODE_N;
|
||||||
|
case Key::O: return SDL_SCANCODE_O;
|
||||||
|
case Key::P: return SDL_SCANCODE_P;
|
||||||
|
case Key::Q: return SDL_SCANCODE_Q;
|
||||||
|
case Key::R: return SDL_SCANCODE_R;
|
||||||
|
case Key::S: return SDL_SCANCODE_S;
|
||||||
|
case Key::T: return SDL_SCANCODE_T;
|
||||||
|
case Key::U: return SDL_SCANCODE_U;
|
||||||
|
case Key::V: return SDL_SCANCODE_V;
|
||||||
|
case Key::W: return SDL_SCANCODE_W;
|
||||||
|
case Key::X: return SDL_SCANCODE_X;
|
||||||
|
case Key::Y: return SDL_SCANCODE_Y;
|
||||||
|
case Key::Z: return SDL_SCANCODE_Z;
|
||||||
|
default: return SDL_SCANCODE_UNKNOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int SDL2Input::mouseToSDL(Mouse btn) {
|
||||||
|
switch (btn) {
|
||||||
|
case Mouse::Left: return SDL_BUTTON_LEFT;
|
||||||
|
case Mouse::Right: return SDL_BUTTON_RIGHT;
|
||||||
|
case Mouse::Middle: return SDL_BUTTON_MIDDLE;
|
||||||
|
case Mouse::X1: return SDL_BUTTON_X1;
|
||||||
|
case Mouse::X2: return SDL_BUTTON_X2;
|
||||||
|
default: return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int SDL2Input::gamepadToSDL(Gamepad btn) {
|
||||||
|
switch (btn) {
|
||||||
|
case Gamepad::A: return SDL_CONTROLLER_BUTTON_A;
|
||||||
|
case Gamepad::B: return SDL_CONTROLLER_BUTTON_B;
|
||||||
|
case Gamepad::X: return SDL_CONTROLLER_BUTTON_X;
|
||||||
|
case Gamepad::Y: return SDL_CONTROLLER_BUTTON_Y;
|
||||||
|
case Gamepad::LB: return SDL_CONTROLLER_BUTTON_LEFTSHOULDER;
|
||||||
|
case Gamepad::RB: return SDL_CONTROLLER_BUTTON_RIGHTSHOULDER;
|
||||||
|
case Gamepad::Back: return SDL_CONTROLLER_BUTTON_BACK;
|
||||||
|
case Gamepad::Start: return SDL_CONTROLLER_BUTTON_START;
|
||||||
|
case Gamepad::Guide: return SDL_CONTROLLER_BUTTON_GUIDE;
|
||||||
|
case Gamepad::LStick: return SDL_CONTROLLER_BUTTON_LEFTSTICK;
|
||||||
|
case Gamepad::RStick: return SDL_CONTROLLER_BUTTON_RIGHTSTICK;
|
||||||
|
case Gamepad::DUp: return SDL_CONTROLLER_BUTTON_DPAD_UP;
|
||||||
|
case Gamepad::DDown: return SDL_CONTROLLER_BUTTON_DPAD_DOWN;
|
||||||
|
case Gamepad::DLeft: return SDL_CONTROLLER_BUTTON_DPAD_LEFT;
|
||||||
|
case Gamepad::DRight: return SDL_CONTROLLER_BUTTON_DPAD_RIGHT;
|
||||||
|
default: return SDL_CONTROLLER_BUTTON_INVALID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/platform/iinput.h>
|
||||||
|
#include <SDL.h>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief SDL2 输入实现
|
||||||
|
*/
|
||||||
|
class SDL2Input : public IInput {
|
||||||
|
public:
|
||||||
|
SDL2Input();
|
||||||
|
~SDL2Input() override;
|
||||||
|
|
||||||
|
void init() override;
|
||||||
|
void shutdown() override;
|
||||||
|
void update() override;
|
||||||
|
|
||||||
|
bool down(Key key) const override;
|
||||||
|
bool pressed(Key key) const override;
|
||||||
|
bool released(Key key) const override;
|
||||||
|
|
||||||
|
bool down(Mouse btn) const override;
|
||||||
|
bool pressed(Mouse btn) const override;
|
||||||
|
bool released(Mouse btn) const override;
|
||||||
|
Vec2 mouse() const override;
|
||||||
|
Vec2 mouseDelta() const override;
|
||||||
|
float scroll() const override;
|
||||||
|
float scrollDelta() const override;
|
||||||
|
void setMouse(const Vec2& pos) override;
|
||||||
|
|
||||||
|
bool gamepad() const override;
|
||||||
|
bool down(Gamepad btn) const override;
|
||||||
|
bool pressed(Gamepad btn) const override;
|
||||||
|
bool released(Gamepad btn) const override;
|
||||||
|
Vec2 leftStick() const override;
|
||||||
|
Vec2 rightStick() const override;
|
||||||
|
float leftTrigger() const override;
|
||||||
|
float rightTrigger() const override;
|
||||||
|
void vibrate(float left, float right) override;
|
||||||
|
|
||||||
|
bool touching() const override;
|
||||||
|
int touchCount() const override;
|
||||||
|
Vec2 touch(int index) const override;
|
||||||
|
TouchPoint touchPoint(int index) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void updateKeyboard();
|
||||||
|
void updateMouse();
|
||||||
|
void updateGamepad();
|
||||||
|
void openGamepad();
|
||||||
|
void closeGamepad();
|
||||||
|
|
||||||
|
static int keyToSDL(Key key);
|
||||||
|
static int mouseToSDL(Mouse btn);
|
||||||
|
static int gamepadToSDL(Gamepad btn);
|
||||||
|
|
||||||
|
std::array<bool, static_cast<size_t>(Key::Count)> keyCurrent_{};
|
||||||
|
std::array<bool, static_cast<size_t>(Key::Count)> keyPrevious_{};
|
||||||
|
|
||||||
|
std::array<bool, static_cast<size_t>(Mouse::Count)> mouseCurrent_{};
|
||||||
|
std::array<bool, static_cast<size_t>(Mouse::Count)> mousePrevious_{};
|
||||||
|
|
||||||
|
Vec2 mousePos_;
|
||||||
|
Vec2 mouseDelta_;
|
||||||
|
float scroll_ = 0.0f;
|
||||||
|
float scrollDelta_ = 0.0f;
|
||||||
|
|
||||||
|
SDL_GameController* gamepad_ = nullptr;
|
||||||
|
int gamepadIndex_ = -1;
|
||||||
|
std::array<bool, static_cast<size_t>(Gamepad::Count)> gamepadCurrent_{};
|
||||||
|
std::array<bool, static_cast<size_t>(Gamepad::Count)> gamepadPrevious_{};
|
||||||
|
Vec2 leftStick_;
|
||||||
|
Vec2 rightStick_;
|
||||||
|
float leftTrigger_ = 0.0f;
|
||||||
|
float rightTrigger_ = 0.0f;
|
||||||
|
float deadzone_ = 0.15f;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,368 @@
|
||||||
|
#include "sdl2_window.h"
|
||||||
|
#include "sdl2_input.h"
|
||||||
|
#include <extra2d/utils/logger.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
SDL2Window::SDL2Window() {
|
||||||
|
for (int i = 0; i < 7; ++i) {
|
||||||
|
sdlCursors_[i] = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL2Window::~SDL2Window() {
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDL2Window::create(const WindowConfig& cfg) {
|
||||||
|
if (!initSDL()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN;
|
||||||
|
if (cfg.fullscreen) {
|
||||||
|
flags |= cfg.fullscreenDesktop ? SDL_WINDOW_FULLSCREEN_DESKTOP : SDL_WINDOW_FULLSCREEN;
|
||||||
|
}
|
||||||
|
if (cfg.resizable) {
|
||||||
|
flags |= SDL_WINDOW_RESIZABLE;
|
||||||
|
}
|
||||||
|
if (!cfg.decorated) {
|
||||||
|
flags |= SDL_WINDOW_BORDERLESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
|
||||||
|
|
||||||
|
if (cfg.msaaSamples > 0) {
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, cfg.msaaSamples);
|
||||||
|
}
|
||||||
|
|
||||||
|
int x = SDL_WINDOWPOS_CENTERED;
|
||||||
|
int y = SDL_WINDOWPOS_CENTERED;
|
||||||
|
if (!cfg.centerWindow) {
|
||||||
|
x = SDL_WINDOWPOS_UNDEFINED;
|
||||||
|
y = SDL_WINDOWPOS_UNDEFINED;
|
||||||
|
}
|
||||||
|
|
||||||
|
sdlWindow_ = SDL_CreateWindow(
|
||||||
|
cfg.title.c_str(),
|
||||||
|
x, y,
|
||||||
|
cfg.width, cfg.height,
|
||||||
|
flags
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!sdlWindow_) {
|
||||||
|
E2D_LOG_ERROR("Failed to create SDL window: {}", SDL_GetError());
|
||||||
|
deinitSDL();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
glContext_ = SDL_GL_CreateContext(sdlWindow_);
|
||||||
|
if (!glContext_) {
|
||||||
|
E2D_LOG_ERROR("Failed to create OpenGL context: {}", SDL_GetError());
|
||||||
|
SDL_DestroyWindow(sdlWindow_);
|
||||||
|
sdlWindow_ = nullptr;
|
||||||
|
deinitSDL();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_GL_SetSwapInterval(cfg.vsync ? 1 : 0);
|
||||||
|
|
||||||
|
SDL_GetWindowSize(sdlWindow_, &width_, &height_);
|
||||||
|
fullscreen_ = cfg.fullscreen;
|
||||||
|
vsync_ = cfg.vsync;
|
||||||
|
|
||||||
|
initCursors();
|
||||||
|
updateContentScale();
|
||||||
|
|
||||||
|
input_ = makeUnique<SDL2Input>();
|
||||||
|
input_->init();
|
||||||
|
|
||||||
|
E2D_LOG_INFO("SDL2 window created: {}x{}", width_, height_);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL2Window::destroy() {
|
||||||
|
if (input_) {
|
||||||
|
input_->shutdown();
|
||||||
|
input_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
deinitCursors();
|
||||||
|
|
||||||
|
if (glContext_) {
|
||||||
|
SDL_GL_DeleteContext(glContext_);
|
||||||
|
glContext_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sdlWindow_) {
|
||||||
|
SDL_DestroyWindow(sdlWindow_);
|
||||||
|
sdlWindow_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
deinitSDL();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL2Window::poll() {
|
||||||
|
if (!sdlWindow_) return;
|
||||||
|
|
||||||
|
if (input_) {
|
||||||
|
input_->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Event event;
|
||||||
|
while (SDL_PollEvent(&event)) {
|
||||||
|
handleEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL2Window::swap() {
|
||||||
|
if (sdlWindow_ && glContext_) {
|
||||||
|
SDL_GL_SwapWindow(sdlWindow_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDL2Window::shouldClose() const {
|
||||||
|
return shouldClose_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL2Window::close() {
|
||||||
|
shouldClose_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL2Window::setTitle(const std::string& title) {
|
||||||
|
if (sdlWindow_) {
|
||||||
|
SDL_SetWindowTitle(sdlWindow_, title.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL2Window::setSize(int w, int h) {
|
||||||
|
if (sdlWindow_) {
|
||||||
|
SDL_SetWindowSize(sdlWindow_, w, h);
|
||||||
|
width_ = w;
|
||||||
|
height_ = h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL2Window::setPos(int x, int y) {
|
||||||
|
if (sdlWindow_) {
|
||||||
|
SDL_SetWindowPosition(sdlWindow_, x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL2Window::setFullscreen(bool fs) {
|
||||||
|
if (sdlWindow_) {
|
||||||
|
SDL_SetWindowFullscreen(sdlWindow_, fs ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
|
||||||
|
fullscreen_ = fs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL2Window::setVSync(bool vsync) {
|
||||||
|
if (glContext_) {
|
||||||
|
SDL_GL_SetSwapInterval(vsync ? 1 : 0);
|
||||||
|
vsync_ = vsync;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL2Window::setVisible(bool visible) {
|
||||||
|
if (sdlWindow_) {
|
||||||
|
if (visible) {
|
||||||
|
SDL_ShowWindow(sdlWindow_);
|
||||||
|
} else {
|
||||||
|
SDL_HideWindow(sdlWindow_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int SDL2Window::width() const {
|
||||||
|
return width_;
|
||||||
|
}
|
||||||
|
|
||||||
|
int SDL2Window::height() const {
|
||||||
|
return height_;
|
||||||
|
}
|
||||||
|
|
||||||
|
Size SDL2Window::size() const {
|
||||||
|
return Size(static_cast<float>(width_), static_cast<float>(height_));
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2 SDL2Window::pos() const {
|
||||||
|
int x, y;
|
||||||
|
if (sdlWindow_) {
|
||||||
|
SDL_GetWindowPosition(sdlWindow_, &x, &y);
|
||||||
|
} else {
|
||||||
|
x = y = 0;
|
||||||
|
}
|
||||||
|
return Vec2(static_cast<float>(x), static_cast<float>(y));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDL2Window::fullscreen() const {
|
||||||
|
return fullscreen_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDL2Window::vsync() const {
|
||||||
|
return vsync_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDL2Window::focused() const {
|
||||||
|
return focused_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDL2Window::minimized() const {
|
||||||
|
return minimized_;
|
||||||
|
}
|
||||||
|
|
||||||
|
float SDL2Window::scaleX() const {
|
||||||
|
return scaleX_;
|
||||||
|
}
|
||||||
|
|
||||||
|
float SDL2Window::scaleY() const {
|
||||||
|
return scaleY_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL2Window::setCursor(Cursor cursor) {
|
||||||
|
if (cursor == Cursor::Hidden) {
|
||||||
|
SDL_ShowCursor(SDL_DISABLE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_ShowCursor(SDL_ENABLE);
|
||||||
|
|
||||||
|
int idx = static_cast<int>(cursor);
|
||||||
|
if (idx >= 0 && idx < 7 && sdlCursors_[idx]) {
|
||||||
|
SDL_SetCursor(sdlCursors_[idx]);
|
||||||
|
currentCursor_ = idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL2Window::showCursor(bool show) {
|
||||||
|
SDL_ShowCursor(show ? SDL_ENABLE : SDL_DISABLE);
|
||||||
|
cursorVisible_ = show;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL2Window::lockCursor(bool lock) {
|
||||||
|
if (sdlWindow_) {
|
||||||
|
SDL_SetRelativeMouseMode(lock ? SDL_TRUE : SDL_FALSE);
|
||||||
|
cursorLocked_ = lock;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IInput* SDL2Window::input() const {
|
||||||
|
return input_.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL2Window::onResize(ResizeCb cb) {
|
||||||
|
resizeCb_ = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL2Window::onClose(CloseCb cb) {
|
||||||
|
closeCb_ = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL2Window::onFocus(FocusCb cb) {
|
||||||
|
focusCb_ = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* SDL2Window::native() const {
|
||||||
|
return sdlWindow_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDL2Window::initSDL() {
|
||||||
|
static int sdlInitCount = 0;
|
||||||
|
if (sdlInitCount == 0) {
|
||||||
|
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) != 0) {
|
||||||
|
E2D_LOG_ERROR("Failed to initialize SDL: {}", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sdlInitCount++;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL2Window::deinitSDL() {
|
||||||
|
static int sdlInitCount = 1;
|
||||||
|
sdlInitCount--;
|
||||||
|
if (sdlInitCount == 0) {
|
||||||
|
SDL_Quit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL2Window::initCursors() {
|
||||||
|
sdlCursors_[0] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW);
|
||||||
|
sdlCursors_[1] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM);
|
||||||
|
sdlCursors_[2] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_CROSSHAIR);
|
||||||
|
sdlCursors_[3] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND);
|
||||||
|
sdlCursors_[4] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS);
|
||||||
|
sdlCursors_[5] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE);
|
||||||
|
sdlCursors_[6] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL2Window::deinitCursors() {
|
||||||
|
for (int i = 0; i < 7; ++i) {
|
||||||
|
if (sdlCursors_[i]) {
|
||||||
|
SDL_FreeCursor(sdlCursors_[i]);
|
||||||
|
sdlCursors_[i] = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL2Window::updateContentScale() {
|
||||||
|
if (sdlWindow_) {
|
||||||
|
SDL_GetWindowSize(sdlWindow_, &width_, &height_);
|
||||||
|
int dw, dh;
|
||||||
|
SDL_GL_GetDrawableSize(sdlWindow_, &dw, &dh);
|
||||||
|
scaleX_ = dw > 0 ? static_cast<float>(dw) / width_ : 1.0f;
|
||||||
|
scaleY_ = dh > 0 ? static_cast<float>(dh) / height_ : 1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDL2Window::handleEvent(const SDL_Event& event) {
|
||||||
|
switch (event.type) {
|
||||||
|
case SDL_QUIT:
|
||||||
|
shouldClose_ = true;
|
||||||
|
if (closeCb_) closeCb_();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SDL_WINDOWEVENT:
|
||||||
|
switch (event.window.event) {
|
||||||
|
case SDL_WINDOWEVENT_RESIZED:
|
||||||
|
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||||
|
width_ = event.window.data1;
|
||||||
|
height_ = event.window.data2;
|
||||||
|
updateContentScale();
|
||||||
|
if (resizeCb_) resizeCb_(width_, height_);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SDL_WINDOWEVENT_FOCUS_GAINED:
|
||||||
|
focused_ = true;
|
||||||
|
if (focusCb_) focusCb_(true);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SDL_WINDOWEVENT_FOCUS_LOST:
|
||||||
|
focused_ = false;
|
||||||
|
if (focusCb_) focusCb_(false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SDL_WINDOWEVENT_MINIMIZED:
|
||||||
|
minimized_ = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SDL_WINDOWEVENT_RESTORED:
|
||||||
|
minimized_ = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SDL_WINDOWEVENT_CLOSE:
|
||||||
|
shouldClose_ = true;
|
||||||
|
if (closeCb_) closeCb_();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/platform/iwindow.h>
|
||||||
|
#include <SDL.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
class SDL2Input;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief SDL2 窗口实现
|
||||||
|
*/
|
||||||
|
class SDL2Window : public IWindow {
|
||||||
|
public:
|
||||||
|
SDL2Window();
|
||||||
|
~SDL2Window() override;
|
||||||
|
|
||||||
|
bool create(const WindowConfig& cfg) override;
|
||||||
|
void destroy() override;
|
||||||
|
|
||||||
|
void poll() override;
|
||||||
|
void swap() override;
|
||||||
|
bool shouldClose() const override;
|
||||||
|
void close() override;
|
||||||
|
|
||||||
|
void setTitle(const std::string& title) override;
|
||||||
|
void setSize(int w, int h) override;
|
||||||
|
void setPos(int x, int y) override;
|
||||||
|
void setFullscreen(bool fs) override;
|
||||||
|
void setVSync(bool vsync) override;
|
||||||
|
void setVisible(bool visible) override;
|
||||||
|
|
||||||
|
int width() const override;
|
||||||
|
int height() const override;
|
||||||
|
Size size() const override;
|
||||||
|
Vec2 pos() const override;
|
||||||
|
bool fullscreen() const override;
|
||||||
|
bool vsync() const override;
|
||||||
|
bool focused() const override;
|
||||||
|
bool minimized() const override;
|
||||||
|
|
||||||
|
float scaleX() const override;
|
||||||
|
float scaleY() const override;
|
||||||
|
|
||||||
|
void setCursor(Cursor cursor) override;
|
||||||
|
void showCursor(bool show) override;
|
||||||
|
void lockCursor(bool lock) override;
|
||||||
|
|
||||||
|
IInput* input() const override;
|
||||||
|
|
||||||
|
void onResize(ResizeCb cb) override;
|
||||||
|
void onClose(CloseCb cb) override;
|
||||||
|
void onFocus(FocusCb cb) override;
|
||||||
|
|
||||||
|
void* native() const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取 SDL 窗口句柄
|
||||||
|
*/
|
||||||
|
SDL_Window* sdlWindow() const { return sdlWindow_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取 OpenGL 上下文
|
||||||
|
*/
|
||||||
|
SDL_GLContext glContext() const { return glContext_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool initSDL();
|
||||||
|
void deinitSDL();
|
||||||
|
void initCursors();
|
||||||
|
void deinitCursors();
|
||||||
|
void updateContentScale();
|
||||||
|
void handleEvent(const SDL_Event& event);
|
||||||
|
|
||||||
|
SDL_Window* sdlWindow_ = nullptr;
|
||||||
|
SDL_GLContext glContext_ = nullptr;
|
||||||
|
SDL_Cursor* sdlCursors_[7] = {};
|
||||||
|
int currentCursor_ = 0;
|
||||||
|
|
||||||
|
UniquePtr<SDL2Input> input_;
|
||||||
|
|
||||||
|
int width_ = 1280;
|
||||||
|
int height_ = 720;
|
||||||
|
bool fullscreen_ = false;
|
||||||
|
bool vsync_ = true;
|
||||||
|
bool focused_ = true;
|
||||||
|
bool minimized_ = false;
|
||||||
|
bool shouldClose_ = false;
|
||||||
|
float scaleX_ = 1.0f;
|
||||||
|
float scaleY_ = 1.0f;
|
||||||
|
bool cursorVisible_ = true;
|
||||||
|
bool cursorLocked_ = false;
|
||||||
|
|
||||||
|
ResizeCb resizeCb_;
|
||||||
|
CloseCb closeCb_;
|
||||||
|
FocusCb focusCb_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -1,665 +0,0 @@
|
||||||
#include <extra2d/event/input_codes.h>
|
|
||||||
#include <extra2d/graphics/viewport_adapter.h>
|
|
||||||
#include <extra2d/platform/input.h>
|
|
||||||
#include <extra2d/utils/logger.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 默认构造函数
|
|
||||||
*
|
|
||||||
* 初始化输入系统的所有成员变量为默认值,包括控制器指针、摇杆状态、
|
|
||||||
* 鼠标状态、触摸状态等,并将所有按键和按钮状态数组初始化为false。
|
|
||||||
*/
|
|
||||||
Input::Input()
|
|
||||||
: controller_(nullptr),
|
|
||||||
leftStickX_(0.0f), leftStickY_(0.0f),
|
|
||||||
rightStickX_(0.0f), rightStickY_(0.0f),
|
|
||||||
mouseScroll_(0.0f), prevMouseScroll_(0.0f),
|
|
||||||
touching_(false), prevTouching_(false), touchCount_(0),
|
|
||||||
viewportAdapter_(nullptr) {
|
|
||||||
|
|
||||||
keysDown_.fill(false);
|
|
||||||
prevKeysDown_.fill(false);
|
|
||||||
buttonsDown_.fill(false);
|
|
||||||
prevButtonsDown_.fill(false);
|
|
||||||
mouseButtonsDown_.fill(false);
|
|
||||||
prevMouseButtonsDown_.fill(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 析构函数
|
|
||||||
*
|
|
||||||
* 自动调用shutdown()方法释放所有资源。
|
|
||||||
*/
|
|
||||||
Input::~Input() { shutdown(); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化输入系统
|
|
||||||
*
|
|
||||||
* 打开第一个可用的游戏控制器,并在PC端获取初始鼠标位置。
|
|
||||||
*/
|
|
||||||
void Input::init() {
|
|
||||||
for (int i = 0; i < SDL_NumJoysticks(); ++i) {
|
|
||||||
if (SDL_IsGameController(i)) {
|
|
||||||
controller_ = SDL_GameControllerOpen(i);
|
|
||||||
if (controller_) {
|
|
||||||
E2D_LOG_INFO("GameController opened: {}",
|
|
||||||
SDL_GameControllerName(controller_));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!controller_) {
|
|
||||||
E2D_LOG_WARN("No game controller found");
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef PLATFORM_SWITCH
|
|
||||||
int mouseX, mouseY;
|
|
||||||
SDL_GetMouseState(&mouseX, &mouseY);
|
|
||||||
mousePosition_ = Vec2(static_cast<float>(mouseX), static_cast<float>(mouseY));
|
|
||||||
prevMousePosition_ = mousePosition_;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 关闭输入系统
|
|
||||||
*
|
|
||||||
* 关闭并释放游戏控制器资源。
|
|
||||||
*/
|
|
||||||
void Input::shutdown() {
|
|
||||||
if (controller_) {
|
|
||||||
SDL_GameControllerClose(controller_);
|
|
||||||
controller_ = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 更新输入状态
|
|
||||||
*
|
|
||||||
* 保存上一帧的输入状态,并更新键盘、鼠标、手柄和触摸设备的当前状态。
|
|
||||||
*/
|
|
||||||
void Input::update() {
|
|
||||||
prevKeysDown_ = keysDown_;
|
|
||||||
prevButtonsDown_ = buttonsDown_;
|
|
||||||
prevMouseButtonsDown_ = mouseButtonsDown_;
|
|
||||||
prevMousePosition_ = mousePosition_;
|
|
||||||
prevMouseScroll_ = mouseScroll_;
|
|
||||||
prevTouching_ = touching_;
|
|
||||||
prevTouchPosition_ = touchPosition_;
|
|
||||||
|
|
||||||
updateKeyboard();
|
|
||||||
updateMouse();
|
|
||||||
updateGamepad();
|
|
||||||
updateTouch();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 更新键盘状态
|
|
||||||
*
|
|
||||||
* 从SDL获取当前键盘状态并更新按键数组。
|
|
||||||
*/
|
|
||||||
void Input::updateKeyboard() {
|
|
||||||
const Uint8* state = SDL_GetKeyboardState(nullptr);
|
|
||||||
for (int i = 0; i < MAX_KEYS; ++i) {
|
|
||||||
keysDown_[i] = state[i] != 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 更新鼠标状态
|
|
||||||
*
|
|
||||||
* 获取当前鼠标位置和按钮状态。仅在非Switch平台执行。
|
|
||||||
*/
|
|
||||||
void Input::updateMouse() {
|
|
||||||
#ifndef PLATFORM_SWITCH
|
|
||||||
int mouseX, mouseY;
|
|
||||||
Uint32 buttonState = SDL_GetMouseState(&mouseX, &mouseY);
|
|
||||||
mousePosition_ = Vec2(static_cast<float>(mouseX), static_cast<float>(mouseY));
|
|
||||||
|
|
||||||
mouseButtonsDown_[0] = (buttonState & SDL_BUTTON(SDL_BUTTON_LEFT)) != 0;
|
|
||||||
mouseButtonsDown_[1] = (buttonState & SDL_BUTTON(SDL_BUTTON_RIGHT)) != 0;
|
|
||||||
mouseButtonsDown_[2] = (buttonState & SDL_BUTTON(SDL_BUTTON_MIDDLE)) != 0;
|
|
||||||
mouseButtonsDown_[3] = (buttonState & SDL_BUTTON(SDL_BUTTON_X1)) != 0;
|
|
||||||
mouseButtonsDown_[4] = (buttonState & SDL_BUTTON(SDL_BUTTON_X2)) != 0;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 更新手柄状态
|
|
||||||
*
|
|
||||||
* 读取手柄按钮状态和摇杆位置,摇杆值归一化到-1.0~1.0范围。
|
|
||||||
*/
|
|
||||||
void Input::updateGamepad() {
|
|
||||||
if (controller_) {
|
|
||||||
for (int i = 0; i < MAX_BUTTONS; ++i) {
|
|
||||||
buttonsDown_[i] =
|
|
||||||
SDL_GameControllerGetButton(
|
|
||||||
controller_, static_cast<SDL_GameControllerButton>(i)) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
leftStickX_ = static_cast<float>(SDL_GameControllerGetAxis(
|
|
||||||
controller_, SDL_CONTROLLER_AXIS_LEFTX)) / 32767.0f;
|
|
||||||
leftStickY_ = static_cast<float>(SDL_GameControllerGetAxis(
|
|
||||||
controller_, SDL_CONTROLLER_AXIS_LEFTY)) / 32767.0f;
|
|
||||||
rightStickX_ = static_cast<float>(SDL_GameControllerGetAxis(
|
|
||||||
controller_, SDL_CONTROLLER_AXIS_RIGHTX)) / 32767.0f;
|
|
||||||
rightStickY_ = static_cast<float>(SDL_GameControllerGetAxis(
|
|
||||||
controller_, SDL_CONTROLLER_AXIS_RIGHTY)) / 32767.0f;
|
|
||||||
} else {
|
|
||||||
buttonsDown_.fill(false);
|
|
||||||
leftStickX_ = leftStickY_ = rightStickX_ = rightStickY_ = 0.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 更新触摸状态
|
|
||||||
*
|
|
||||||
* 获取触摸设备状态,将归一化坐标转换为像素坐标。
|
|
||||||
* Switch平台使用原生触摸屏支持,PC端支持可选触摸设备。
|
|
||||||
*/
|
|
||||||
void Input::updateTouch() {
|
|
||||||
#ifdef PLATFORM_SWITCH
|
|
||||||
SDL_TouchID touchId = SDL_GetTouchDevice(0);
|
|
||||||
if (touchId != 0) {
|
|
||||||
touchCount_ = SDL_GetNumTouchFingers(touchId);
|
|
||||||
if (touchCount_ > 0) {
|
|
||||||
SDL_Finger *finger = SDL_GetTouchFinger(touchId, 0);
|
|
||||||
if (finger) {
|
|
||||||
touching_ = true;
|
|
||||||
touchPosition_ = Vec2(finger->x * 1280.0f, finger->y * 720.0f);
|
|
||||||
} else {
|
|
||||||
touching_ = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
touching_ = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
touchCount_ = 0;
|
|
||||||
touching_ = false;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
SDL_TouchID touchId = SDL_GetTouchDevice(0);
|
|
||||||
if (touchId != 0) {
|
|
||||||
touchCount_ = SDL_GetNumTouchFingers(touchId);
|
|
||||||
if (touchCount_ > 0) {
|
|
||||||
SDL_Finger *finger = SDL_GetTouchFinger(touchId, 0);
|
|
||||||
if (finger) {
|
|
||||||
touching_ = true;
|
|
||||||
int windowWidth, windowHeight;
|
|
||||||
SDL_Window* window = SDL_GL_GetCurrentWindow();
|
|
||||||
if (window) {
|
|
||||||
SDL_GetWindowSize(window, &windowWidth, &windowHeight);
|
|
||||||
touchPosition_ = Vec2(finger->x * windowWidth, finger->y * windowHeight);
|
|
||||||
} else {
|
|
||||||
touchPosition_ = Vec2(finger->x * 1280.0f, finger->y * 720.0f);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
touching_ = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
touching_ = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
touchCount_ = 0;
|
|
||||||
touching_ = false;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 键盘输入
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 将键盘按键映射到手柄按钮
|
|
||||||
*
|
|
||||||
* 提供键盘到手柄按钮的映射,用于在Switch平台模拟键盘输入。
|
|
||||||
*
|
|
||||||
* @param keyCode 键盘按键码
|
|
||||||
* @return 对应的SDL手柄按钮枚举值
|
|
||||||
*/
|
|
||||||
SDL_GameControllerButton Input::mapKeyToButton(int keyCode) const {
|
|
||||||
switch (keyCode) {
|
|
||||||
case Key::Up:
|
|
||||||
return SDL_CONTROLLER_BUTTON_DPAD_UP;
|
|
||||||
case Key::Down:
|
|
||||||
return SDL_CONTROLLER_BUTTON_DPAD_DOWN;
|
|
||||||
case Key::Left:
|
|
||||||
return SDL_CONTROLLER_BUTTON_DPAD_LEFT;
|
|
||||||
case Key::Right:
|
|
||||||
return SDL_CONTROLLER_BUTTON_DPAD_RIGHT;
|
|
||||||
|
|
||||||
case Key::W:
|
|
||||||
return SDL_CONTROLLER_BUTTON_DPAD_UP;
|
|
||||||
case Key::S:
|
|
||||||
return SDL_CONTROLLER_BUTTON_DPAD_DOWN;
|
|
||||||
case Key::A:
|
|
||||||
return SDL_CONTROLLER_BUTTON_DPAD_LEFT;
|
|
||||||
case Key::D:
|
|
||||||
return SDL_CONTROLLER_BUTTON_DPAD_RIGHT;
|
|
||||||
|
|
||||||
case Key::Z:
|
|
||||||
return SDL_CONTROLLER_BUTTON_B;
|
|
||||||
case Key::X:
|
|
||||||
return SDL_CONTROLLER_BUTTON_A;
|
|
||||||
case Key::C:
|
|
||||||
return SDL_CONTROLLER_BUTTON_Y;
|
|
||||||
case Key::V:
|
|
||||||
return SDL_CONTROLLER_BUTTON_X;
|
|
||||||
case Key::Space:
|
|
||||||
return SDL_CONTROLLER_BUTTON_A;
|
|
||||||
case Key::Enter:
|
|
||||||
return SDL_CONTROLLER_BUTTON_A;
|
|
||||||
case Key::Escape:
|
|
||||||
return SDL_CONTROLLER_BUTTON_START;
|
|
||||||
|
|
||||||
case Key::Q:
|
|
||||||
return SDL_CONTROLLER_BUTTON_LEFTSHOULDER;
|
|
||||||
case Key::E:
|
|
||||||
return SDL_CONTROLLER_BUTTON_RIGHTSHOULDER;
|
|
||||||
|
|
||||||
case Key::Tab:
|
|
||||||
return SDL_CONTROLLER_BUTTON_BACK;
|
|
||||||
case Key::Backspace:
|
|
||||||
return SDL_CONTROLLER_BUTTON_START;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return SDL_CONTROLLER_BUTTON_INVALID;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查按键是否被按下
|
|
||||||
*
|
|
||||||
* 检测指定按键当前是否处于按下状态。
|
|
||||||
* Switch平台映射到手柄按钮,PC端直接读取键盘状态。
|
|
||||||
*
|
|
||||||
* @param keyCode 键盘按键码
|
|
||||||
* @return 按键按下返回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<int>(button);
|
|
||||||
if (index < 0 || index >= 8)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
#ifdef PLATFORM_SWITCH
|
|
||||||
if (button == MouseButton::Left) {
|
|
||||||
return touching_;
|
|
||||||
}
|
|
||||||
if (button == MouseButton::Right) {
|
|
||||||
return buttonsDown_[SDL_CONTROLLER_BUTTON_A];
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
#else
|
|
||||||
return mouseButtonsDown_[index];
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查鼠标按钮是否刚被按下
|
|
||||||
*
|
|
||||||
* @param button 鼠标按钮枚举值
|
|
||||||
* @return 按钮刚按下返回true,否则返回false
|
|
||||||
*/
|
|
||||||
bool Input::isMousePressed(MouseButton button) const {
|
|
||||||
int index = static_cast<int>(button);
|
|
||||||
if (index < 0 || index >= 8)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
#ifdef PLATFORM_SWITCH
|
|
||||||
if (button == MouseButton::Left) {
|
|
||||||
return touching_ && !prevTouching_;
|
|
||||||
}
|
|
||||||
if (button == MouseButton::Right) {
|
|
||||||
return buttonsDown_[SDL_CONTROLLER_BUTTON_A] &&
|
|
||||||
!prevButtonsDown_[SDL_CONTROLLER_BUTTON_A];
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
#else
|
|
||||||
return mouseButtonsDown_[index] && !prevMouseButtonsDown_[index];
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查鼠标按钮是否刚被释放
|
|
||||||
*
|
|
||||||
* @param button 鼠标按钮枚举值
|
|
||||||
* @return 按钮刚释放返回true,否则返回false
|
|
||||||
*/
|
|
||||||
bool Input::isMouseReleased(MouseButton button) const {
|
|
||||||
int index = static_cast<int>(button);
|
|
||||||
if (index < 0 || index >= 8)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
#ifdef PLATFORM_SWITCH
|
|
||||||
if (button == MouseButton::Left) {
|
|
||||||
return !touching_ && prevTouching_;
|
|
||||||
}
|
|
||||||
if (button == MouseButton::Right) {
|
|
||||||
return !buttonsDown_[SDL_CONTROLLER_BUTTON_A] &&
|
|
||||||
prevButtonsDown_[SDL_CONTROLLER_BUTTON_A];
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
#else
|
|
||||||
return !mouseButtonsDown_[index] && prevMouseButtonsDown_[index];
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取鼠标位置
|
|
||||||
*
|
|
||||||
* Switch平台返回触摸位置,PC端返回鼠标位置。
|
|
||||||
*
|
|
||||||
* @return 包含X和Y坐标的二维向量(屏幕像素坐标)
|
|
||||||
*/
|
|
||||||
Vec2 Input::getMousePosition() const {
|
|
||||||
#ifdef PLATFORM_SWITCH
|
|
||||||
return touchPosition_;
|
|
||||||
#else
|
|
||||||
return mousePosition_;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取鼠标移动增量
|
|
||||||
*
|
|
||||||
* 计算当前帧与上一帧之间的鼠标位置差值。
|
|
||||||
*
|
|
||||||
* @return 包含X和Y移动量的二维向量
|
|
||||||
*/
|
|
||||||
Vec2 Input::getMouseDelta() const {
|
|
||||||
#ifdef PLATFORM_SWITCH
|
|
||||||
if (touching_ && prevTouching_) {
|
|
||||||
return touchPosition_ - prevTouchPosition_;
|
|
||||||
}
|
|
||||||
return Vec2::Zero();
|
|
||||||
#else
|
|
||||||
return mousePosition_ - prevMousePosition_;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置鼠标位置
|
|
||||||
*
|
|
||||||
* 将鼠标移动到指定的屏幕坐标位置。仅在PC端有效。
|
|
||||||
*
|
|
||||||
* @param position 目标位置(屏幕像素坐标)
|
|
||||||
*/
|
|
||||||
void Input::setMousePosition(const Vec2 &position) {
|
|
||||||
#ifndef PLATFORM_SWITCH
|
|
||||||
SDL_WarpMouseInWindow(SDL_GL_GetCurrentWindow(),
|
|
||||||
static_cast<int>(position.x),
|
|
||||||
static_cast<int>(position.y));
|
|
||||||
#else
|
|
||||||
(void)position;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置鼠标光标可见性
|
|
||||||
*
|
|
||||||
* 显示或隐藏鼠标光标。仅在PC端有效。
|
|
||||||
*
|
|
||||||
* @param visible true显示光标,false隐藏光标
|
|
||||||
*/
|
|
||||||
void Input::setMouseVisible(bool visible) {
|
|
||||||
#ifndef PLATFORM_SWITCH
|
|
||||||
SDL_ShowCursor(visible ? SDL_ENABLE : SDL_DISABLE);
|
|
||||||
#else
|
|
||||||
(void)visible;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置鼠标锁定模式
|
|
||||||
*
|
|
||||||
* 启用或禁用相对鼠标模式,用于第一人称视角控制等场景。
|
|
||||||
*
|
|
||||||
* @param locked true锁定鼠标到窗口中心,false解锁
|
|
||||||
*/
|
|
||||||
void Input::setMouseLocked(bool locked) {
|
|
||||||
#ifndef PLATFORM_SWITCH
|
|
||||||
SDL_SetRelativeMouseMode(locked ? SDL_TRUE : SDL_FALSE);
|
|
||||||
#else
|
|
||||||
(void)locked;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 便捷方法
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查是否有任意按键被按下
|
|
||||||
*
|
|
||||||
* Switch平台检查手柄按钮,PC端检查键盘按键。
|
|
||||||
*
|
|
||||||
* @return 有按键按下返回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
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
#include <extra2d/platform/platform_module.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
std::unordered_map<std::string, BackendFactory::BackendEntry>& BackendFactory::registry() {
|
||||||
|
static std::unordered_map<std::string, BackendEntry> reg;
|
||||||
|
return reg;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BackendFactory::reg(const std::string& name, WindowFn win, InputFn in) {
|
||||||
|
registry()[name] = {win, in};
|
||||||
|
}
|
||||||
|
|
||||||
|
UniquePtr<IWindow> BackendFactory::createWindow(const std::string& name) {
|
||||||
|
auto& reg = registry();
|
||||||
|
auto it = reg.find(name);
|
||||||
|
if (it != reg.end() && it->second.windowFn) {
|
||||||
|
return it->second.windowFn();
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
UniquePtr<IInput> BackendFactory::createInput(const std::string& name) {
|
||||||
|
auto& reg = registry();
|
||||||
|
auto it = reg.find(name);
|
||||||
|
if (it != reg.end() && it->second.inputFn) {
|
||||||
|
return it->second.inputFn();
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> BackendFactory::backends() {
|
||||||
|
std::vector<std::string> result;
|
||||||
|
for (const auto& pair : registry()) {
|
||||||
|
result.push_back(pair.first);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BackendFactory::has(const std::string& name) {
|
||||||
|
return registry().find(name) != registry().end();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -1,509 +0,0 @@
|
||||||
#include <extra2d/event/event_queue.h>
|
|
||||||
#include <extra2d/platform/input.h>
|
|
||||||
#include <extra2d/platform/window.h>
|
|
||||||
#include <extra2d/utils/logger.h>
|
|
||||||
|
|
||||||
#include <SDL.h>
|
|
||||||
|
|
||||||
#include <glad/glad.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 默认构造函数
|
|
||||||
*
|
|
||||||
* 初始化窗口的所有成员变量为默认值,包括SDL窗口指针、OpenGL上下文、
|
|
||||||
* 光标数组、窗口尺寸、VSync状态等。
|
|
||||||
*/
|
|
||||||
Window::Window()
|
|
||||||
: sdlWindow_(nullptr), glContext_(nullptr), currentCursor_(nullptr),
|
|
||||||
width_(1280), height_(720), vsync_(true), shouldClose_(false),
|
|
||||||
fullscreen_(true), focused_(true), contentScaleX_(1.0f),
|
|
||||||
contentScaleY_(1.0f), enableDpiScale_(true), userData_(nullptr),
|
|
||||||
eventQueue_(nullptr) {
|
|
||||||
for (int i = 0; i < 9; ++i) {
|
|
||||||
sdlCursors_[i] = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 析构函数
|
|
||||||
*
|
|
||||||
* 自动调用destroy()方法释放所有资源。
|
|
||||||
*/
|
|
||||||
Window::~Window() { destroy(); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建窗口
|
|
||||||
*
|
|
||||||
* 根据配置参数创建SDL窗口和OpenGL ES上下文,初始化输入管理器和光标。
|
|
||||||
*
|
|
||||||
* @param config 窗口配置参数,包含尺寸、标题、全屏模式等信息
|
|
||||||
* @return 创建成功返回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>();
|
|
||||||
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<GLADloadproc>(SDL_GL_GetProcAddress)) == 0) {
|
|
||||||
E2D_LOG_ERROR("gladLoadGLES2Loader failed");
|
|
||||||
SDL_GL_DeleteContext(glContext_);
|
|
||||||
glContext_ = nullptr;
|
|
||||||
SDL_DestroyWindow(sdlWindow_);
|
|
||||||
sdlWindow_ = nullptr;
|
|
||||||
SDL_Quit();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_GL_SetSwapInterval(vsync_ ? 1 : 0);
|
|
||||||
|
|
||||||
if (config.enableDpiScale) {
|
|
||||||
updateContentScale();
|
|
||||||
}
|
|
||||||
|
|
||||||
E2D_LOG_INFO("SDL2 + GLES 3.2 initialized successfully");
|
|
||||||
E2D_LOG_INFO("OpenGL Version: {}",
|
|
||||||
reinterpret_cast<const char *>(glGetString(GL_VERSION)));
|
|
||||||
E2D_LOG_INFO("OpenGL Renderer: {}",
|
|
||||||
reinterpret_cast<const char *>(glGetString(GL_RENDERER)));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 反初始化SDL资源
|
|
||||||
*
|
|
||||||
* 释放光标资源、删除OpenGL上下文、销毁SDL窗口并退出SDL库。
|
|
||||||
*/
|
|
||||||
void Window::deinitSDL() {
|
|
||||||
deinitCursors();
|
|
||||||
|
|
||||||
if (glContext_) {
|
|
||||||
SDL_GL_DeleteContext(glContext_);
|
|
||||||
glContext_ = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sdlWindow_) {
|
|
||||||
SDL_DestroyWindow(sdlWindow_);
|
|
||||||
sdlWindow_ = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_Quit();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 销毁窗口
|
|
||||||
*
|
|
||||||
* 释放输入管理器并调用deinitSDL()清理所有SDL相关资源。
|
|
||||||
*/
|
|
||||||
void Window::destroy() {
|
|
||||||
if (sdlWindow_ != nullptr) {
|
|
||||||
input_.reset();
|
|
||||||
deinitSDL();
|
|
||||||
E2D_LOG_INFO("Window destroyed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 轮询并处理窗口事件
|
|
||||||
*
|
|
||||||
* 处理SDL事件队列中的所有事件,包括窗口关闭、大小改变、焦点变化等,
|
|
||||||
* 并更新输入管理器状态。
|
|
||||||
*/
|
|
||||||
void Window::pollEvents() {
|
|
||||||
SDL_Event event;
|
|
||||||
while (SDL_PollEvent(&event)) {
|
|
||||||
switch (event.type) {
|
|
||||||
case SDL_QUIT:
|
|
||||||
shouldClose_ = true;
|
|
||||||
if (closeCallback_) {
|
|
||||||
closeCallback_();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SDL_WINDOWEVENT:
|
|
||||||
switch (event.window.event) {
|
|
||||||
case SDL_WINDOWEVENT_RESIZED:
|
|
||||||
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
|
||||||
width_ = event.window.data1;
|
|
||||||
height_ = event.window.data2;
|
|
||||||
updateContentScale();
|
|
||||||
if (resizeCallback_) {
|
|
||||||
resizeCallback_(width_, height_);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case SDL_WINDOWEVENT_FOCUS_GAINED:
|
|
||||||
focused_ = true;
|
|
||||||
if (focusCallback_) {
|
|
||||||
focusCallback_(true);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case SDL_WINDOWEVENT_FOCUS_LOST:
|
|
||||||
focused_ = false;
|
|
||||||
if (focusCallback_) {
|
|
||||||
focusCallback_(false);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input_) {
|
|
||||||
input_->update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 交换前后缓冲区
|
|
||||||
*
|
|
||||||
* 将后台缓冲区内容呈现到屏幕上,实现双缓冲渲染。
|
|
||||||
*/
|
|
||||||
void Window::swapBuffers() {
|
|
||||||
if (sdlWindow_) {
|
|
||||||
SDL_GL_SwapWindow(sdlWindow_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查窗口是否应该关闭
|
|
||||||
*
|
|
||||||
* @return 如果窗口应该关闭返回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<float>(x), static_cast<float>(y));
|
|
||||||
}
|
|
||||||
return Vec2::Zero();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取X轴内容缩放比例
|
|
||||||
*
|
|
||||||
* 根据DPI设置返回X轴的内容缩放比例,用于高DPI显示适配。
|
|
||||||
*
|
|
||||||
* @return X轴缩放比例,如果DPI缩放被禁用则返回1.0
|
|
||||||
*/
|
|
||||||
float Window::getContentScaleX() const {
|
|
||||||
return enableDpiScale_ ? contentScaleX_ : 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取Y轴内容缩放比例
|
|
||||||
*
|
|
||||||
* 根据DPI设置返回Y轴的内容缩放比例,用于高DPI显示适配。
|
|
||||||
*
|
|
||||||
* @return Y轴缩放比例,如果DPI缩放被禁用则返回1.0
|
|
||||||
*/
|
|
||||||
float Window::getContentScaleY() const {
|
|
||||||
return enableDpiScale_ ? contentScaleY_ : 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取内容缩放比例
|
|
||||||
*
|
|
||||||
* @return 包含X和Y轴缩放比例的二维向量
|
|
||||||
*/
|
|
||||||
Vec2 Window::getContentScale() const {
|
|
||||||
return Vec2(getContentScaleX(), getContentScaleY());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查窗口是否最小化
|
|
||||||
*
|
|
||||||
* @return 如果窗口处于最小化状态返回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<int>(shape);
|
|
||||||
if (index >= 0 && index < 9 && sdlCursors_[index]) {
|
|
||||||
SDL_SetCursor(sdlCursors_[index]);
|
|
||||||
currentCursor_ = sdlCursors_[index];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 重置鼠标光标为默认样式
|
|
||||||
*
|
|
||||||
* 将鼠标光标恢复为系统默认光标。
|
|
||||||
*/
|
|
||||||
void Window::resetCursor() {
|
|
||||||
SDL_SetCursor(SDL_GetDefaultCursor());
|
|
||||||
currentCursor_ = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置鼠标光标可见性
|
|
||||||
*
|
|
||||||
* @param visible true显示鼠标光标,false隐藏鼠标光标
|
|
||||||
*/
|
|
||||||
void Window::setMouseVisible(bool visible) {
|
|
||||||
SDL_ShowCursor(visible ? SDL_ENABLE : SDL_DISABLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 更新内容缩放比例
|
|
||||||
*
|
|
||||||
* 根据当前窗口所在显示器的DPI值更新内容缩放比例,
|
|
||||||
* 以标准96 DPI为基准计算缩放因子。
|
|
||||||
*/
|
|
||||||
void Window::updateContentScale() {
|
|
||||||
if (sdlWindow_) {
|
|
||||||
int displayIndex = SDL_GetWindowDisplayIndex(sdlWindow_);
|
|
||||||
if (displayIndex >= 0) {
|
|
||||||
float ddpi, hdpi, vdpi;
|
|
||||||
if (SDL_GetDisplayDPI(displayIndex, &ddpi, &hdpi, &vdpi) == 0) {
|
|
||||||
contentScaleX_ = hdpi / 96.0f;
|
|
||||||
contentScaleY_ = vdpi / 96.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
#include <extra2d/app/application.h>
|
#include <extra2d/app/application.h>
|
||||||
#include <extra2d/graphics/render_backend.h>
|
#include <extra2d/graphics/render_backend.h>
|
||||||
#include <extra2d/graphics/render_command.h>
|
#include <extra2d/graphics/render_command.h>
|
||||||
#include <extra2d/platform/input.h>
|
#include <extra2d/platform/iinput.h>
|
||||||
#include <extra2d/scene/scene_manager.h>
|
#include <extra2d/scene/scene_manager.h>
|
||||||
#include <extra2d/utils/logger.h>
|
#include <extra2d/utils/logger.h>
|
||||||
|
|
||||||
|
|
@ -389,7 +389,7 @@ void SceneManager::purgeCachedScenes() { namedScenes_.clear(); }
|
||||||
*/
|
*/
|
||||||
void SceneManager::dispatchPointerEvents(Scene &scene) {
|
void SceneManager::dispatchPointerEvents(Scene &scene) {
|
||||||
auto &input = Application::get().input();
|
auto &input = Application::get().input();
|
||||||
Vec2 screenPos = input.getMousePosition();
|
Vec2 screenPos = input.mouse();
|
||||||
|
|
||||||
Vec2 worldPos = screenPos;
|
Vec2 worldPos = screenPos;
|
||||||
if (auto *camera = scene.getActiveCamera()) {
|
if (auto *camera = scene.getActiveCamera()) {
|
||||||
|
|
@ -426,17 +426,17 @@ void SceneManager::dispatchPointerEvents(Scene &scene) {
|
||||||
dispatchToNode(hoverTarget_, evt);
|
dispatchToNode(hoverTarget_, evt);
|
||||||
}
|
}
|
||||||
|
|
||||||
float scrollDelta = input.getMouseScrollDelta();
|
float scrollDelta = input.scrollDelta();
|
||||||
if (hoverTarget_ && scrollDelta != 0.0f) {
|
if (hoverTarget_ && scrollDelta != 0.0f) {
|
||||||
Event evt = Event::createMouseScroll(Vec2(0.0f, scrollDelta), worldPos);
|
Event evt = Event::createMouseScroll(Vec2(0.0f, scrollDelta), worldPos);
|
||||||
dispatchToNode(hoverTarget_, evt);
|
dispatchToNode(hoverTarget_, evt);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.isMousePressed(MouseButton::Left)) {
|
if (input.pressed(Mouse::Left)) {
|
||||||
captureTarget_ = hoverTarget_;
|
captureTarget_ = hoverTarget_;
|
||||||
if (captureTarget_) {
|
if (captureTarget_) {
|
||||||
Event evt = Event::createMouseButtonPress(
|
Event evt = Event::createMouseButtonPress(static_cast<int>(Mouse::Left),
|
||||||
static_cast<int>(MouseButton::Left), 0, worldPos);
|
0, worldPos);
|
||||||
dispatchToNode(captureTarget_, evt);
|
dispatchToNode(captureTarget_, evt);
|
||||||
|
|
||||||
Event pressed;
|
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_;
|
Node *target = captureTarget_ ? captureTarget_ : hoverTarget_;
|
||||||
if (target) {
|
if (target) {
|
||||||
Event evt = Event::createMouseButtonRelease(
|
Event evt = Event::createMouseButtonRelease(static_cast<int>(Mouse::Left),
|
||||||
static_cast<int>(MouseButton::Left), 0, worldPos);
|
0, worldPos);
|
||||||
dispatchToNode(target, evt);
|
dispatchToNode(target, evt);
|
||||||
|
|
||||||
Event released;
|
Event released;
|
||||||
|
|
|
||||||
18
xmake.lua
18
xmake.lua
|
|
@ -26,6 +26,13 @@ option("debug_logs")
|
||||||
set_description("Enable debug logging")
|
set_description("Enable debug logging")
|
||||||
option_end()
|
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
|
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
|
end
|
||||||
|
|
||||||
-- ==============================================
|
-- ==============================================
|
||||||
|
|
|
||||||
|
|
@ -8,21 +8,40 @@ local function get_current_plat()
|
||||||
return get_config("plat") or os.host()
|
return get_config("plat") or os.host()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- 获取后端配置
|
||||||
|
local function get_backend()
|
||||||
|
return get_config("backend") or "sdl2"
|
||||||
|
end
|
||||||
|
|
||||||
-- 定义 Extra2D 引擎库目标
|
-- 定义 Extra2D 引擎库目标
|
||||||
function define_extra2d_engine()
|
function define_extra2d_engine()
|
||||||
target("extra2d")
|
target("extra2d")
|
||||||
set_kind("static")
|
set_kind("static")
|
||||||
|
|
||||||
-- 引擎源文件
|
-- 引擎核心源文件
|
||||||
add_files("Extra2D/src/**.cpp")
|
add_files("Extra2D/src/**.cpp")
|
||||||
add_files("Extra2D/src/glad/glad.c")
|
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", {public = true})
|
||||||
add_includedirs("Extra2D/include/extra2d/platform", {public = true})
|
add_includedirs("Extra2D/include/extra2d/platform", {public = true})
|
||||||
|
|
||||||
-- 平台配置
|
-- 平台配置
|
||||||
local plat = get_current_plat()
|
|
||||||
if plat == "switch" then
|
if plat == "switch" then
|
||||||
local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro"
|
local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro"
|
||||||
add_includedirs(devkitPro .. "/portlibs/switch/include", {public = true})
|
add_includedirs(devkitPro .. "/portlibs/switch/include", {public = true})
|
||||||
|
|
@ -30,7 +49,14 @@ function define_extra2d_engine()
|
||||||
add_syslinks("SDL2", "GLESv2", "EGL", "glapi", "drm_nouveau",
|
add_syslinks("SDL2", "GLESv2", "EGL", "glapi", "drm_nouveau",
|
||||||
{public = true})
|
{public = true})
|
||||||
elseif plat == "mingw" then
|
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})
|
add_syslinks("opengl32", "glu32", "winmm", "imm32", "version", "setupapi", {public = true})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue