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/graphics/render_backend.h>
|
||||
#include <extra2d/platform/window.h>
|
||||
#include <extra2d/platform/iwindow.h>
|
||||
#include <string>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class Input;
|
||||
class IInput;
|
||||
class SceneManager;
|
||||
class TimerManager;
|
||||
class EventQueue;
|
||||
|
|
@ -14,12 +15,18 @@ class EventDispatcher;
|
|||
class Camera;
|
||||
class ViewportAdapter;
|
||||
|
||||
/**
|
||||
* @brief 平台类型
|
||||
*/
|
||||
enum class PlatformType { Auto = 0, PC, Switch };
|
||||
|
||||
/**
|
||||
* @brief 应用配置
|
||||
*/
|
||||
struct AppConfig {
|
||||
std::string title = "Easy2D Application";
|
||||
int width = 800;
|
||||
int height = 600;
|
||||
std::string title = "Extra2D Application";
|
||||
int width = 1280;
|
||||
int height = 720;
|
||||
bool fullscreen = false;
|
||||
bool resizable = true;
|
||||
bool vsync = true;
|
||||
|
|
@ -29,6 +36,8 @@ struct AppConfig {
|
|||
PlatformType platform = PlatformType::Auto;
|
||||
bool enableCursors = true;
|
||||
bool enableDpiScale = false;
|
||||
|
||||
std::string backend = "sdl2";
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -41,7 +50,22 @@ public:
|
|||
Application(const Application&) = delete;
|
||||
Application& operator=(const Application&) = delete;
|
||||
|
||||
/**
|
||||
* @brief 使用默认配置初始化
|
||||
*/
|
||||
bool init();
|
||||
|
||||
/**
|
||||
* @brief 使用配置结构初始化
|
||||
*/
|
||||
bool init(const AppConfig& config);
|
||||
|
||||
/**
|
||||
* @brief 从配置文件初始化
|
||||
* @param path 配置文件路径(支持 .json 和 .ini)
|
||||
*/
|
||||
bool init(const std::string& path);
|
||||
|
||||
void shutdown();
|
||||
void run();
|
||||
void quit();
|
||||
|
|
@ -51,9 +75,9 @@ public:
|
|||
bool isPaused() const { return paused_; }
|
||||
bool isRunning() const { return running_; }
|
||||
|
||||
Window &window() { return *window_; }
|
||||
IWindow& window() { return *window_; }
|
||||
RenderBackend& renderer() { return *renderer_; }
|
||||
Input &input();
|
||||
IInput& input();
|
||||
SceneManager& scenes();
|
||||
TimerManager& timers();
|
||||
EventQueue& eventQueue();
|
||||
|
|
@ -73,13 +97,14 @@ private:
|
|||
Application() = default;
|
||||
~Application();
|
||||
|
||||
bool initImpl();
|
||||
void mainLoop();
|
||||
void update();
|
||||
void render();
|
||||
|
||||
AppConfig config_;
|
||||
|
||||
UniquePtr<Window> window_;
|
||||
UniquePtr<IWindow> window_;
|
||||
UniquePtr<RenderBackend> renderer_;
|
||||
UniquePtr<SceneManager> sceneManager_;
|
||||
UniquePtr<TimerManager> timerManager_;
|
||||
|
|
|
|||
|
|
@ -9,8 +9,10 @@
|
|||
#include <extra2d/core/types.h>
|
||||
|
||||
// Platform
|
||||
#include <extra2d/platform/input.h>
|
||||
#include <extra2d/platform/window.h>
|
||||
#include <extra2d/platform/iinput.h>
|
||||
#include <extra2d/platform/iwindow.h>
|
||||
#include <extra2d/platform/keys.h>
|
||||
#include <extra2d/platform/platform_module.h>
|
||||
|
||||
// Graphics
|
||||
#include <extra2d/graphics/camera.h>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
namespace extra2d {
|
||||
|
||||
class Window;
|
||||
class IWindow;
|
||||
|
||||
// ============================================================================
|
||||
// OpenGL 渲染器实现
|
||||
|
|
@ -21,7 +21,7 @@ public:
|
|||
~GLRenderer() override;
|
||||
|
||||
// RenderBackend 接口实现
|
||||
bool init(Window *window) override;
|
||||
bool init(IWindow* window) override;
|
||||
void shutdown() override;
|
||||
|
||||
void beginFrame(const Color &clearColor) override;
|
||||
|
|
@ -88,7 +88,7 @@ private:
|
|||
float r, g, b, a;
|
||||
};
|
||||
|
||||
Window *window_;
|
||||
IWindow* window_;
|
||||
GLSpriteBatch spriteBatch_;
|
||||
GLShader shapeShader_;
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
namespace extra2d {
|
||||
|
||||
// 前向声明
|
||||
class Window;
|
||||
class IWindow;
|
||||
class Texture;
|
||||
class FontAtlas;
|
||||
class Shader;
|
||||
|
|
@ -44,7 +44,7 @@ public:
|
|||
// ------------------------------------------------------------------------
|
||||
// 生命周期
|
||||
// ------------------------------------------------------------------------
|
||||
virtual bool init(Window *window) = 0;
|
||||
virtual bool init(IWindow* window) = 0;
|
||||
virtual void shutdown() = 0;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -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/viewport_adapter.h>
|
||||
#include <extra2d/graphics/vram_manager.h>
|
||||
#include <extra2d/platform/input.h>
|
||||
#include <extra2d/platform/window.h>
|
||||
#include <extra2d/platform/iinput.h>
|
||||
#include <extra2d/platform/platform_module.h>
|
||||
#include <extra2d/scene/scene_manager.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <extra2d/utils/timer.h>
|
||||
|
|
@ -20,13 +20,6 @@
|
|||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 获取当前时间(秒)
|
||||
* @return 从某个固定时间点开始的秒数
|
||||
*
|
||||
* 使用高精度时钟获取当前时间,在Switch平台使用clock_gettime,
|
||||
* 其他平台使用std::chrono::steady_clock
|
||||
*/
|
||||
static double getTimeSeconds() {
|
||||
#ifdef __SWITCH__
|
||||
struct timespec ts;
|
||||
|
|
@ -41,27 +34,18 @@ static double getTimeSeconds() {
|
|||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取Application单例实例
|
||||
* @return Application单例的引用
|
||||
*/
|
||||
Application& Application::get() {
|
||||
static Application instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 析构函数,自动关闭应用程序
|
||||
*/
|
||||
Application::~Application() { shutdown(); }
|
||||
|
||||
/**
|
||||
* @brief 初始化应用程序
|
||||
* @param config 应用程序配置
|
||||
* @return 初始化成功返回true,失败返回false
|
||||
*
|
||||
* 初始化窗口、渲染器、场景管理器、定时器管理器、事件系统、相机和视口适配器等核心组件
|
||||
*/
|
||||
bool Application::init() {
|
||||
AppConfig cfg;
|
||||
return init(cfg);
|
||||
}
|
||||
|
||||
bool Application::init(const AppConfig& config) {
|
||||
if (initialized_) {
|
||||
E2D_LOG_WARN("Application already initialized");
|
||||
|
|
@ -69,7 +53,32 @@ bool Application::init(const AppConfig &config) {
|
|||
}
|
||||
|
||||
config_ = config;
|
||||
return initImpl();
|
||||
}
|
||||
|
||||
bool Application::init(const std::string& path) {
|
||||
if (initialized_) {
|
||||
E2D_LOG_WARN("Application already initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("Loading config from: {}", path);
|
||||
|
||||
AppConfig cfg;
|
||||
|
||||
if (path.find(".json") != std::string::npos) {
|
||||
// TODO: 使用 nlohmann_json 加载配置
|
||||
E2D_LOG_WARN("JSON config loading not yet implemented, using defaults");
|
||||
} else if (path.find(".ini") != std::string::npos) {
|
||||
// TODO: 实现 INI 配置加载
|
||||
E2D_LOG_WARN("INI config loading not yet implemented, using defaults");
|
||||
}
|
||||
|
||||
config_ = cfg;
|
||||
return initImpl();
|
||||
}
|
||||
|
||||
bool Application::initImpl() {
|
||||
PlatformType platform = config_.platform;
|
||||
if (platform == PlatformType::Auto) {
|
||||
#ifdef __SWITCH__
|
||||
|
|
@ -86,44 +95,59 @@ bool Application::init(const AppConfig &config) {
|
|||
if (R_SUCCEEDED(rc)) {
|
||||
E2D_LOG_INFO("RomFS initialized successfully");
|
||||
} else {
|
||||
E2D_LOG_WARN("romfsInit failed: {:#08X}, will use regular filesystem",
|
||||
rc);
|
||||
E2D_LOG_WARN("romfsInit failed: {:#08X}, will use regular filesystem", rc);
|
||||
}
|
||||
|
||||
rc = socketInitializeDefault();
|
||||
if (R_FAILED(rc)) {
|
||||
E2D_LOG_WARN(
|
||||
"socketInitializeDefault failed, nxlink will not be available");
|
||||
E2D_LOG_WARN("socketInitializeDefault failed, nxlink will not be available");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
window_ = makeUnique<Window>();
|
||||
std::string backend = config_.backend;
|
||||
#ifdef __SWITCH__
|
||||
backend = "switch";
|
||||
#endif
|
||||
|
||||
if (!BackendFactory::has(backend)) {
|
||||
E2D_LOG_ERROR("Backend '{}' not available", backend);
|
||||
auto backends = BackendFactory::backends();
|
||||
if (backends.empty()) {
|
||||
E2D_LOG_ERROR("No backends registered!");
|
||||
return false;
|
||||
}
|
||||
backend = backends[0];
|
||||
E2D_LOG_WARN("Using fallback backend: {}", backend);
|
||||
}
|
||||
|
||||
window_ = BackendFactory::createWindow(backend);
|
||||
if (!window_) {
|
||||
E2D_LOG_ERROR("Failed to create window for backend: {}", backend);
|
||||
return false;
|
||||
}
|
||||
|
||||
WindowConfig winConfig;
|
||||
winConfig.title = config.title;
|
||||
winConfig.width = config.width;
|
||||
winConfig.height = config.height;
|
||||
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.fullscreen = config_.fullscreen;
|
||||
winConfig.resizable = config_.resizable;
|
||||
}
|
||||
winConfig.vsync = config.vsync;
|
||||
winConfig.msaaSamples = config.msaaSamples;
|
||||
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);
|
||||
renderer_ = RenderBackend::create(config_.renderBackend);
|
||||
if (!renderer_ || !renderer_->init(window_.get())) {
|
||||
E2D_LOG_ERROR("Failed to initialize renderer");
|
||||
window_->destroy();
|
||||
|
|
@ -134,22 +158,21 @@ bool Application::init(const AppConfig &config) {
|
|||
timerManager_ = makeUnique<TimerManager>();
|
||||
eventQueue_ = makeUnique<EventQueue>();
|
||||
eventDispatcher_ = makeUnique<EventDispatcher>();
|
||||
camera_ = makeUnique<Camera>(0, static_cast<float>(window_->getWidth()),
|
||||
static_cast<float>(window_->getHeight()), 0);
|
||||
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.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());
|
||||
viewportAdapter_->update(window_->width(), window_->height());
|
||||
|
||||
window_->setResizeCallback([this](int width, int height) {
|
||||
window_->onResize([this](int width, int height) {
|
||||
if (viewportAdapter_) {
|
||||
viewportAdapter_->update(width, height);
|
||||
}
|
||||
|
|
@ -170,15 +193,10 @@ bool Application::init(const AppConfig &config) {
|
|||
initialized_ = true;
|
||||
running_ = true;
|
||||
|
||||
E2D_LOG_INFO("Application initialized successfully");
|
||||
E2D_LOG_INFO("Application initialized (backend: {})", backend);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 关闭应用程序
|
||||
*
|
||||
* 释放所有资源,包括场景管理器、渲染器、窗口等,并关闭平台相关服务
|
||||
*/
|
||||
void Application::shutdown() {
|
||||
if (!initialized_)
|
||||
return;
|
||||
|
|
@ -230,11 +248,6 @@ void Application::shutdown() {
|
|||
E2D_LOG_INFO("Application shutdown complete");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 运行应用程序主循环
|
||||
*
|
||||
* 进入应用程序主循环,持续处理事件、更新和渲染直到应用程序退出
|
||||
*/
|
||||
void Application::run() {
|
||||
if (!initialized_) {
|
||||
E2D_LOG_ERROR("Application not initialized");
|
||||
|
|
@ -243,32 +256,16 @@ void Application::run() {
|
|||
|
||||
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;
|
||||
|
|
@ -276,11 +273,6 @@ void Application::pause() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 恢复应用程序
|
||||
*
|
||||
* 恢复应用程序更新,重置帧时间以避免大的deltaTime跳跃
|
||||
*/
|
||||
void Application::resume() {
|
||||
if (paused_) {
|
||||
paused_ = false;
|
||||
|
|
@ -289,11 +281,6 @@ void Application::resume() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 主循环迭代
|
||||
*
|
||||
* 执行一次主循环迭代,包括计算帧时间、处理事件、更新和渲染
|
||||
*/
|
||||
void Application::mainLoop() {
|
||||
double currentTime = getTimeSeconds();
|
||||
deltaTime_ = static_cast<float>(currentTime - lastFrameTime_);
|
||||
|
|
@ -309,7 +296,7 @@ void Application::mainLoop() {
|
|||
fpsTimer_ -= 1.0f;
|
||||
}
|
||||
|
||||
window_->pollEvents();
|
||||
window_->poll();
|
||||
|
||||
if (eventDispatcher_ && eventQueue_) {
|
||||
eventDispatcher_->processQueue(*eventQueue_);
|
||||
|
|
@ -332,11 +319,6 @@ void Application::mainLoop() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 更新应用程序状态
|
||||
*
|
||||
* 更新定时器管理器和场景管理器
|
||||
*/
|
||||
void Application::update() {
|
||||
if (timerManager_) {
|
||||
timerManager_->update(deltaTime_);
|
||||
|
|
@ -347,11 +329,6 @@ void Application::update() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 渲染应用程序画面
|
||||
*
|
||||
* 设置视口并渲染当前场景
|
||||
*/
|
||||
void Application::render() {
|
||||
if (!renderer_) {
|
||||
E2D_LOG_ERROR("Render failed: renderer is null");
|
||||
|
|
@ -364,7 +341,7 @@ void Application::render() {
|
|||
static_cast<int>(vp.origin.x), static_cast<int>(vp.origin.y),
|
||||
static_cast<int>(vp.size.width), static_cast<int>(vp.size.height));
|
||||
} else {
|
||||
renderer_->setViewport(0, 0, window_->getWidth(), window_->getHeight());
|
||||
renderer_->setViewport(0, 0, window_->width(), window_->height());
|
||||
}
|
||||
|
||||
if (sceneManager_) {
|
||||
|
|
@ -373,61 +350,27 @@ void Application::render() {
|
|||
E2D_LOG_WARN("Render: sceneManager is null");
|
||||
}
|
||||
|
||||
window_->swapBuffers();
|
||||
window_->swap();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取输入管理器
|
||||
* @return 输入管理器的引用
|
||||
*/
|
||||
Input &Application::input() { return *window_->getInput(); }
|
||||
IInput& Application::input() { return *window_->input(); }
|
||||
|
||||
/**
|
||||
* @brief 获取场景管理器
|
||||
* @return 场景管理器的引用
|
||||
*/
|
||||
SceneManager& Application::scenes() { return *sceneManager_; }
|
||||
|
||||
/**
|
||||
* @brief 获取定时器管理器
|
||||
* @return 定时器管理器的引用
|
||||
*/
|
||||
TimerManager& Application::timers() { return *timerManager_; }
|
||||
|
||||
/**
|
||||
* @brief 获取事件队列
|
||||
* @return 事件队列的引用
|
||||
*/
|
||||
EventQueue& Application::eventQueue() { return *eventQueue_; }
|
||||
|
||||
/**
|
||||
* @brief 获取事件分发器
|
||||
* @return 事件分发器的引用
|
||||
*/
|
||||
EventDispatcher& Application::eventDispatcher() { return *eventDispatcher_; }
|
||||
|
||||
/**
|
||||
* @brief 获取相机
|
||||
* @return 相机的引用
|
||||
*/
|
||||
Camera& Application::camera() { return *camera_; }
|
||||
|
||||
/**
|
||||
* @brief 获取视口适配器
|
||||
* @return 视口适配器的引用
|
||||
*/
|
||||
ViewportAdapter& Application::viewportAdapter() { return *viewportAdapter_; }
|
||||
|
||||
/**
|
||||
* @brief 进入指定场景
|
||||
* @param scene 要进入的场景
|
||||
*
|
||||
* 设置场景的视口大小并将其设置为当前场景
|
||||
*/
|
||||
void Application::enterScene(Ptr<Scene> scene) {
|
||||
if (sceneManager_ && scene) {
|
||||
scene->setViewportSize(static_cast<float>(window_->getWidth()),
|
||||
static_cast<float>(window_->getHeight()));
|
||||
scene->setViewportSize(static_cast<float>(window_->width()),
|
||||
static_cast<float>(window_->height()));
|
||||
sceneManager_->enterScene(scene);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
#include <extra2d/graphics/opengl/gl_renderer.h>
|
||||
#include <extra2d/graphics/opengl/gl_texture.h>
|
||||
#include <extra2d/graphics/vram_manager.h>
|
||||
#include <extra2d/platform/window.h>
|
||||
#include <extra2d/platform/iwindow.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <vector>
|
||||
|
||||
|
|
@ -85,7 +85,7 @@ GLRenderer::~GLRenderer() { shutdown(); }
|
|||
* @param window 窗口指针
|
||||
* @return 初始化成功返回true,失败返回false
|
||||
*/
|
||||
bool GLRenderer::init(Window *window) {
|
||||
bool GLRenderer::init(IWindow* window) {
|
||||
window_ = window;
|
||||
|
||||
// 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/graphics/render_backend.h>
|
||||
#include <extra2d/graphics/render_command.h>
|
||||
#include <extra2d/platform/input.h>
|
||||
#include <extra2d/platform/iinput.h>
|
||||
#include <extra2d/scene/scene_manager.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
||||
|
|
@ -389,7 +389,7 @@ void SceneManager::purgeCachedScenes() { namedScenes_.clear(); }
|
|||
*/
|
||||
void SceneManager::dispatchPointerEvents(Scene &scene) {
|
||||
auto &input = Application::get().input();
|
||||
Vec2 screenPos = input.getMousePosition();
|
||||
Vec2 screenPos = input.mouse();
|
||||
|
||||
Vec2 worldPos = screenPos;
|
||||
if (auto *camera = scene.getActiveCamera()) {
|
||||
|
|
@ -426,17 +426,17 @@ void SceneManager::dispatchPointerEvents(Scene &scene) {
|
|||
dispatchToNode(hoverTarget_, evt);
|
||||
}
|
||||
|
||||
float scrollDelta = input.getMouseScrollDelta();
|
||||
float scrollDelta = input.scrollDelta();
|
||||
if (hoverTarget_ && scrollDelta != 0.0f) {
|
||||
Event evt = Event::createMouseScroll(Vec2(0.0f, scrollDelta), worldPos);
|
||||
dispatchToNode(hoverTarget_, evt);
|
||||
}
|
||||
|
||||
if (input.isMousePressed(MouseButton::Left)) {
|
||||
if (input.pressed(Mouse::Left)) {
|
||||
captureTarget_ = hoverTarget_;
|
||||
if (captureTarget_) {
|
||||
Event evt = Event::createMouseButtonPress(
|
||||
static_cast<int>(MouseButton::Left), 0, worldPos);
|
||||
Event evt = Event::createMouseButtonPress(static_cast<int>(Mouse::Left),
|
||||
0, worldPos);
|
||||
dispatchToNode(captureTarget_, evt);
|
||||
|
||||
Event pressed;
|
||||
|
|
@ -446,11 +446,11 @@ void SceneManager::dispatchPointerEvents(Scene &scene) {
|
|||
}
|
||||
}
|
||||
|
||||
if (input.isMouseReleased(MouseButton::Left)) {
|
||||
if (input.released(Mouse::Left)) {
|
||||
Node *target = captureTarget_ ? captureTarget_ : hoverTarget_;
|
||||
if (target) {
|
||||
Event evt = Event::createMouseButtonRelease(
|
||||
static_cast<int>(MouseButton::Left), 0, worldPos);
|
||||
Event evt = Event::createMouseButtonRelease(static_cast<int>(Mouse::Left),
|
||||
0, worldPos);
|
||||
dispatchToNode(target, evt);
|
||||
|
||||
Event released;
|
||||
|
|
|
|||
18
xmake.lua
18
xmake.lua
|
|
@ -26,6 +26,13 @@ option("debug_logs")
|
|||
set_description("Enable debug logging")
|
||||
option_end()
|
||||
|
||||
option("backend")
|
||||
set_default("sdl2")
|
||||
set_showmenu(true)
|
||||
set_values("sdl2", "glfw")
|
||||
set_description("Platform backend (sdl2, glfw)")
|
||||
option_end()
|
||||
|
||||
-- ==============================================
|
||||
-- 平台检测与配置
|
||||
-- ==============================================
|
||||
|
|
@ -66,7 +73,16 @@ end
|
|||
-- ==============================================
|
||||
|
||||
if target_plat == "mingw" then
|
||||
add_requires("glm", "libsdl2")
|
||||
local backend = get_config("backend") or "sdl2"
|
||||
|
||||
add_requires("glm")
|
||||
add_requires("nlohmann_json")
|
||||
|
||||
if backend == "sdl2" then
|
||||
add_requires("libsdl2")
|
||||
elseif backend == "glfw" then
|
||||
add_requires("glfw")
|
||||
end
|
||||
end
|
||||
|
||||
-- ==============================================
|
||||
|
|
|
|||
|
|
@ -8,21 +8,40 @@ local function get_current_plat()
|
|||
return get_config("plat") or os.host()
|
||||
end
|
||||
|
||||
-- 获取后端配置
|
||||
local function get_backend()
|
||||
return get_config("backend") or "sdl2"
|
||||
end
|
||||
|
||||
-- 定义 Extra2D 引擎库目标
|
||||
function define_extra2d_engine()
|
||||
target("extra2d")
|
||||
set_kind("static")
|
||||
|
||||
-- 引擎源文件
|
||||
-- 引擎核心源文件
|
||||
add_files("Extra2D/src/**.cpp")
|
||||
add_files("Extra2D/src/glad/glad.c")
|
||||
|
||||
-- 平台后端源文件
|
||||
local plat = get_current_plat()
|
||||
local backend = get_backend()
|
||||
|
||||
if plat == "switch" then
|
||||
add_files("Extra2D/src/platform/backends/switch/*.cpp")
|
||||
add_defines("E2D_BACKEND_SWITCH")
|
||||
elseif backend == "sdl2" then
|
||||
add_files("Extra2D/src/platform/backends/sdl2/*.cpp")
|
||||
add_defines("E2D_BACKEND_SDL2")
|
||||
elseif backend == "glfw" then
|
||||
add_files("Extra2D/src/platform/backends/glfw/*.cpp")
|
||||
add_defines("E2D_BACKEND_GLFW")
|
||||
end
|
||||
|
||||
-- 头文件路径
|
||||
add_includedirs("Extra2D/include", {public = true})
|
||||
add_includedirs("Extra2D/include/extra2d/platform", {public = true})
|
||||
|
||||
-- 平台配置
|
||||
local plat = get_current_plat()
|
||||
if plat == "switch" then
|
||||
local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro"
|
||||
add_includedirs(devkitPro .. "/portlibs/switch/include", {public = true})
|
||||
|
|
@ -30,7 +49,14 @@ function define_extra2d_engine()
|
|||
add_syslinks("SDL2", "GLESv2", "EGL", "glapi", "drm_nouveau",
|
||||
{public = true})
|
||||
elseif plat == "mingw" then
|
||||
add_packages("glm", "libsdl2", {public = true})
|
||||
add_packages("glm", "nlohmann_json", {public = true})
|
||||
|
||||
if backend == "sdl2" then
|
||||
add_packages("libsdl2", {public = true})
|
||||
elseif backend == "glfw" then
|
||||
add_packages("glfw", {public = true})
|
||||
end
|
||||
|
||||
add_syslinks("opengl32", "glu32", "winmm", "imm32", "version", "setupapi", {public = true})
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue