feat: 添加SDL2平台支持及核心功能模块
实现SDL2平台初始化、窗口管理、输入处理和文件系统服务 重构服务管理机制,支持暂停/恢复状态 添加事件总线系统,实现引擎事件分发 优化应用主循环和更新机制 完善跨平台文件操作接口
This commit is contained in:
parent
5ef1873a44
commit
8abf58e3d5
|
|
@ -5,17 +5,28 @@
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
enum class PlatformType { Auto = 0, PC, Switch };
|
/**
|
||||||
|
* @brief 平台类型枚举
|
||||||
|
*/
|
||||||
|
enum class PlatformType {
|
||||||
|
Auto = 0,
|
||||||
|
PC,
|
||||||
|
Switch
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 应用程序配置
|
||||||
|
*/
|
||||||
struct AppConfig {
|
struct AppConfig {
|
||||||
std::string title = "Extra2D Application";
|
std::string title = "Extra2D Application";
|
||||||
int width = 800;
|
int32 width = 1280;
|
||||||
int height = 600;
|
int32 height = 720;
|
||||||
bool fullscreen = false;
|
bool fullscreen = false;
|
||||||
bool resizable = true;
|
bool resizable = true;
|
||||||
bool vsync = true;
|
bool vsync = true;
|
||||||
int fpsLimit = 0;
|
int32 fpsLimit = 0;
|
||||||
int msaaSamples = 0;
|
int32 glMajor = 3;
|
||||||
|
int32 glMinor = 3;
|
||||||
PlatformType platform = PlatformType::Auto;
|
PlatformType platform = PlatformType::Auto;
|
||||||
bool enableCursors = true;
|
bool enableCursors = true;
|
||||||
bool enableDpiScale = false;
|
bool enableDpiScale = false;
|
||||||
|
|
@ -28,25 +39,72 @@ struct AppConfig {
|
||||||
*/
|
*/
|
||||||
class Application {
|
class Application {
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @brief 获取单例实例
|
||||||
|
*/
|
||||||
static Application& instance();
|
static Application& instance();
|
||||||
|
|
||||||
Application(const Application&) = delete;
|
Application(const Application&) = delete;
|
||||||
Application& operator=(const Application&) = delete;
|
Application& operator=(const Application&) = delete;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化应用程序
|
||||||
|
*/
|
||||||
bool init(const AppConfig& config);
|
bool init(const AppConfig& config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 关闭应用程序
|
||||||
|
*/
|
||||||
void shutdown();
|
void shutdown();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 运行主循环
|
||||||
|
*/
|
||||||
void run();
|
void run();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 请求退出
|
||||||
|
*/
|
||||||
void quit();
|
void quit();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 暂停应用
|
||||||
|
*/
|
||||||
void pause();
|
void pause();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 恢复应用
|
||||||
|
*/
|
||||||
void resume();
|
void resume();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 是否暂停
|
||||||
|
*/
|
||||||
bool isPaused() const { return paused_; }
|
bool isPaused() const { return paused_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 是否运行中
|
||||||
|
*/
|
||||||
bool isRunning() const { return running_; }
|
bool isRunning() const { return running_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取帧时间
|
||||||
|
*/
|
||||||
float deltaTime() const { return deltaTime_; }
|
float deltaTime() const { return deltaTime_; }
|
||||||
float totalTime() const { return totalTime_; }
|
|
||||||
int fps() const { return currentFps_; }
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取总运行时间
|
||||||
|
*/
|
||||||
|
float totalTime() const { return totalTime_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取当前 FPS
|
||||||
|
*/
|
||||||
|
int32 fps() const { return currentFps_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取配置
|
||||||
|
*/
|
||||||
const AppConfig& getConfig() const { return config_; }
|
const AppConfig& getConfig() const { return config_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
@ -66,9 +124,9 @@ private:
|
||||||
float deltaTime_ = 0.0f;
|
float deltaTime_ = 0.0f;
|
||||||
float totalTime_ = 0.0f;
|
float totalTime_ = 0.0f;
|
||||||
double lastFrameTime_ = 0.0;
|
double lastFrameTime_ = 0.0;
|
||||||
int frameCount_ = 0;
|
int32 frameCount_ = 0;
|
||||||
float fpsTimer_ = 0.0f;
|
float fpsTimer_ = 0.0f;
|
||||||
int currentFps_ = 0;
|
int32 currentFps_ = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define APP extra2d::Application::instance()
|
#define APP extra2d::Application::instance()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,177 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <types/base/types.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <tuple>
|
||||||
|
#include <vector>
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
namespace extra2d::event {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 监听器链表节点
|
||||||
|
*/
|
||||||
|
struct ListenerEntry {
|
||||||
|
ListenerEntry* next = nullptr;
|
||||||
|
ListenerEntry* prev = nullptr;
|
||||||
|
class ListenerBase* listener = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 监听器基类
|
||||||
|
*/
|
||||||
|
class ListenerBase {
|
||||||
|
protected:
|
||||||
|
ListenerEntry* entry_ = nullptr;
|
||||||
|
friend class ListenerContainer;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 监听器容器,管理同一事件的所有监听器
|
||||||
|
*/
|
||||||
|
class ListenerContainer {
|
||||||
|
public:
|
||||||
|
ListenerContainer() = default;
|
||||||
|
~ListenerContainer();
|
||||||
|
|
||||||
|
template<typename EHandler, typename... Args>
|
||||||
|
bool broadcast(Args&&... args);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
ListenerEntry* listenerList_ = nullptr;
|
||||||
|
ListenerEntry* listenersToAdd_ = nullptr;
|
||||||
|
std::vector<ListenerEntry*> listenersToRemove_;
|
||||||
|
int broadcasting_ = 0;
|
||||||
|
|
||||||
|
void addListener(ListenerBase* listener);
|
||||||
|
void removeListener(ListenerBase* listener);
|
||||||
|
void processPendingListeners();
|
||||||
|
bool hasPendingListeners() const;
|
||||||
|
|
||||||
|
template<typename EHandler>
|
||||||
|
friend class Listener;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 事件处理器数据库,每个事件类型一个容器
|
||||||
|
*/
|
||||||
|
template<typename EHandler>
|
||||||
|
class ListenerDB {
|
||||||
|
public:
|
||||||
|
static ListenerContainer* container() {
|
||||||
|
static ListenerContainer* ctn = new ListenerContainer();
|
||||||
|
return ctn;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 事件特征定义
|
||||||
|
*/
|
||||||
|
template<typename BusT, typename... Args>
|
||||||
|
struct EventTrait {
|
||||||
|
using Bus = BusT;
|
||||||
|
using ArgTuple = std::tuple<Args...>;
|
||||||
|
static constexpr size_t ARG_COUNT = sizeof...(Args);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 元组提取器
|
||||||
|
*/
|
||||||
|
template<typename T>
|
||||||
|
struct TupleExtractor {
|
||||||
|
using FuncType = void();
|
||||||
|
using StdFuncType = std::function<void()>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Args>
|
||||||
|
struct TupleExtractor<std::tuple<Args...>> {
|
||||||
|
using FuncType = void(Args...);
|
||||||
|
using StdFuncType = std::function<void(Args...)>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 监听器模板类
|
||||||
|
*
|
||||||
|
* 构造时自动注册,析构时自动注销
|
||||||
|
*/
|
||||||
|
template<typename EHandler>
|
||||||
|
class Listener : public ListenerBase {
|
||||||
|
public:
|
||||||
|
using ArgTuple = typename EHandler::ArgTuple;
|
||||||
|
using StdFuncType = typename TupleExtractor<ArgTuple>::StdFuncType;
|
||||||
|
|
||||||
|
Listener();
|
||||||
|
~Listener();
|
||||||
|
|
||||||
|
template<typename Fn>
|
||||||
|
void bind(Fn&& func) {
|
||||||
|
callback_ = std::forward<Fn>(func);
|
||||||
|
}
|
||||||
|
|
||||||
|
void enable() { enabled_ = true; }
|
||||||
|
void disable() { enabled_ = false; }
|
||||||
|
bool isEnabled() const { return enabled_; }
|
||||||
|
void reset() { callback_ = nullptr; }
|
||||||
|
|
||||||
|
template<typename... Args>
|
||||||
|
void invoke(Args&&... args) {
|
||||||
|
if (callback_ && enabled_) {
|
||||||
|
callback_(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* busName() const { return EHandler::BUS_NAME; }
|
||||||
|
const char* eventName() const { return EHandler::NAME; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool enabled_ = true;
|
||||||
|
StdFuncType callback_;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename EHandler>
|
||||||
|
Listener<EHandler>::Listener() {
|
||||||
|
entry_ = new ListenerEntry();
|
||||||
|
entry_->listener = this;
|
||||||
|
ListenerDB<EHandler>::container()->addListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename EHandler>
|
||||||
|
Listener<EHandler>::~Listener() {
|
||||||
|
ListenerDB<EHandler>::container()->removeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define EVENT_LIST_LOOP_BEGIN(curr, list) \
|
||||||
|
for (ListenerEntry* curr = list; curr != nullptr; curr = curr->next) {
|
||||||
|
|
||||||
|
#define EVENT_LIST_LOOP_END(curr, list) }
|
||||||
|
|
||||||
|
template<typename EHandler, typename... Args>
|
||||||
|
bool ListenerContainer::broadcast(Args&&... args) {
|
||||||
|
broadcasting_++;
|
||||||
|
|
||||||
|
EVENT_LIST_LOOP_BEGIN(curr, listenerList_)
|
||||||
|
if (curr->listener) {
|
||||||
|
static_cast<Listener<EHandler>*>(curr->listener)->invoke(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
EVENT_LIST_LOOP_END(curr, listenerList_)
|
||||||
|
|
||||||
|
broadcasting_--;
|
||||||
|
|
||||||
|
if (!broadcasting_ && hasPendingListeners()) {
|
||||||
|
processPendingListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 广播事件
|
||||||
|
*/
|
||||||
|
template<typename EHandler, typename... Args>
|
||||||
|
void broadcast(Args&&... args) {
|
||||||
|
static_assert(sizeof...(Args) == EHandler::ARG_COUNT, "Parameter count incorrect");
|
||||||
|
auto* container = ListenerDB<EHandler>::container();
|
||||||
|
container->template broadcast<EHandler, Args...>(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d::event
|
||||||
|
|
@ -0,0 +1,112 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <core/event/event_bus.h>
|
||||||
|
|
||||||
|
#define E2D_EVENT_BUS_NAME_(n) n##_Bus
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 声明事件总线
|
||||||
|
*/
|
||||||
|
#define DECLARE_EVENT_BUS(BusName) \
|
||||||
|
struct BusName##_Bus { \
|
||||||
|
static constexpr const char* NAME = #BusName; \
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 声明无参数事件
|
||||||
|
*/
|
||||||
|
#define DECLARE_EVENT_0(EventName, BusName) \
|
||||||
|
struct EventName : ::extra2d::event::EventTrait<E2D_EVENT_BUS_NAME_(BusName)> { \
|
||||||
|
using Listener = ::extra2d::event::Listener<EventName>; \
|
||||||
|
static constexpr const char* NAME = #EventName; \
|
||||||
|
static constexpr const char* BUS_NAME = E2D_EVENT_BUS_NAME_(BusName)::NAME; \
|
||||||
|
static void emit() { ::extra2d::event::broadcast<EventName>(); } \
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 声明单参数事件
|
||||||
|
*/
|
||||||
|
#define DECLARE_EVENT_1(EventName, BusName, Arg0) \
|
||||||
|
struct EventName : ::extra2d::event::EventTrait<E2D_EVENT_BUS_NAME_(BusName), Arg0> { \
|
||||||
|
using Listener = ::extra2d::event::Listener<EventName>; \
|
||||||
|
static constexpr const char* NAME = #EventName; \
|
||||||
|
static constexpr const char* BUS_NAME = E2D_EVENT_BUS_NAME_(BusName)::NAME; \
|
||||||
|
static void emit(Arg0 arg0) { ::extra2d::event::broadcast<EventName>(arg0); } \
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 声明双参数事件
|
||||||
|
*/
|
||||||
|
#define DECLARE_EVENT_2(EventName, BusName, Arg0, Arg1) \
|
||||||
|
struct EventName : ::extra2d::event::EventTrait<E2D_EVENT_BUS_NAME_(BusName), Arg0, Arg1> { \
|
||||||
|
using Listener = ::extra2d::event::Listener<EventName>; \
|
||||||
|
static constexpr const char* NAME = #EventName; \
|
||||||
|
static constexpr const char* BUS_NAME = E2D_EVENT_BUS_NAME_(BusName)::NAME; \
|
||||||
|
static void emit(Arg0 arg0, Arg1 arg1) { ::extra2d::event::broadcast<EventName>(arg0, arg1); } \
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 声明三参数事件
|
||||||
|
*/
|
||||||
|
#define DECLARE_EVENT_3(EventName, BusName, Arg0, Arg1, Arg2) \
|
||||||
|
struct EventName : ::extra2d::event::EventTrait<E2D_EVENT_BUS_NAME_(BusName), Arg0, Arg1, Arg2> { \
|
||||||
|
using Listener = ::extra2d::event::Listener<EventName>; \
|
||||||
|
static constexpr const char* NAME = #EventName; \
|
||||||
|
static constexpr const char* BUS_NAME = E2D_EVENT_BUS_NAME_(BusName)::NAME; \
|
||||||
|
static void emit(Arg0 arg0, Arg1 arg1, Arg2 arg2) { ::extra2d::event::broadcast<EventName>(arg0, arg1, arg2); } \
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 声明四参数事件
|
||||||
|
*/
|
||||||
|
#define DECLARE_EVENT_4(EventName, BusName, Arg0, Arg1, Arg2, Arg3) \
|
||||||
|
struct EventName : ::extra2d::event::EventTrait<E2D_EVENT_BUS_NAME_(BusName), Arg0, Arg1, Arg2, Arg3> { \
|
||||||
|
using Listener = ::extra2d::event::Listener<EventName>; \
|
||||||
|
static constexpr const char* NAME = #EventName; \
|
||||||
|
static constexpr const char* BUS_NAME = E2D_EVENT_BUS_NAME_(BusName)::NAME; \
|
||||||
|
static void emit(Arg0 arg0, Arg1 arg1, Arg2 arg2, Arg3 arg3) { ::extra2d::event::broadcast<EventName>(arg0, arg1, arg2, arg3); } \
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 声明五参数事件
|
||||||
|
*/
|
||||||
|
#define DECLARE_EVENT_5(EventName, BusName, Arg0, Arg1, Arg2, Arg3, Arg4) \
|
||||||
|
struct EventName : ::extra2d::event::EventTrait<E2D_EVENT_BUS_NAME_(BusName), Arg0, Arg1, Arg2, Arg3, Arg4> { \
|
||||||
|
using Listener = ::extra2d::event::Listener<EventName>; \
|
||||||
|
static constexpr const char* NAME = #EventName; \
|
||||||
|
static constexpr const char* BUS_NAME = E2D_EVENT_BUS_NAME_(BusName)::NAME; \
|
||||||
|
static void emit(Arg0 arg0, Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4) { ::extra2d::event::broadcast<EventName>(arg0, arg1, arg2, arg3, arg4); } \
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 声明六参数事件
|
||||||
|
*/
|
||||||
|
#define DECLARE_EVENT_6(EventName, BusName, Arg0, Arg1, Arg2, Arg3, Arg4, Arg5) \
|
||||||
|
struct EventName : ::extra2d::event::EventTrait<E2D_EVENT_BUS_NAME_(BusName), Arg0, Arg1, Arg2, Arg3, Arg4, Arg5> { \
|
||||||
|
using Listener = ::extra2d::event::Listener<EventName>; \
|
||||||
|
static constexpr const char* NAME = #EventName; \
|
||||||
|
static constexpr const char* BUS_NAME = E2D_EVENT_BUS_NAME_(BusName)::NAME; \
|
||||||
|
static void emit(Arg0 arg0, Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5) { ::extra2d::event::broadcast<EventName>(arg0, arg1, arg2, arg3, arg4, arg5); } \
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 声明七参数事件
|
||||||
|
*/
|
||||||
|
#define DECLARE_EVENT_7(EventName, BusName, Arg0, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) \
|
||||||
|
struct EventName : ::extra2d::event::EventTrait<E2D_EVENT_BUS_NAME_(BusName), Arg0, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6> { \
|
||||||
|
using Listener = ::extra2d::event::Listener<EventName>; \
|
||||||
|
static constexpr const char* NAME = #EventName; \
|
||||||
|
static constexpr const char* BUS_NAME = E2D_EVENT_BUS_NAME_(BusName)::NAME; \
|
||||||
|
static void emit(Arg0 arg0, Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6) { ::extra2d::event::broadcast<EventName>(arg0, arg1, arg2, arg3, arg4, arg5, arg6); } \
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 声明八参数事件
|
||||||
|
*/
|
||||||
|
#define DECLARE_EVENT_8(EventName, BusName, Arg0, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7) \
|
||||||
|
struct EventName : ::extra2d::event::EventTrait<E2D_EVENT_BUS_NAME_(BusName), Arg0, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7> { \
|
||||||
|
using Listener = ::extra2d::event::Listener<EventName>; \
|
||||||
|
static constexpr const char* NAME = #EventName; \
|
||||||
|
static constexpr const char* BUS_NAME = E2D_EVENT_BUS_NAME_(BusName)::NAME; \
|
||||||
|
static void emit(Arg0 arg0, Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6, Arg7 arg7) { ::extra2d::event::broadcast<EventName>(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7); } \
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,194 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <core/event/event_bus_macros.h>
|
||||||
|
#include <types/base/types.h>
|
||||||
|
#include <platform/input.h>
|
||||||
|
|
||||||
|
namespace extra2d::events {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 引擎事件总线
|
||||||
|
*/
|
||||||
|
DECLARE_EVENT_BUS(Engine)
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 应用生命周期事件
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 应用初始化完成事件
|
||||||
|
*/
|
||||||
|
DECLARE_EVENT_0(OnInit, Engine)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 应用关闭事件
|
||||||
|
*/
|
||||||
|
DECLARE_EVENT_0(OnShutdown, Engine)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 应用暂停事件
|
||||||
|
*/
|
||||||
|
DECLARE_EVENT_0(OnPause, Engine)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 应用恢复事件
|
||||||
|
*/
|
||||||
|
DECLARE_EVENT_0(OnResume, Engine)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 帧更新事件
|
||||||
|
* @param dt 帧间隔时间
|
||||||
|
*/
|
||||||
|
DECLARE_EVENT_1(OnUpdate, Engine, float)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 帧更新后事件
|
||||||
|
* @param dt 帧间隔时间
|
||||||
|
*/
|
||||||
|
DECLARE_EVENT_1(OnLateUpdate, Engine, float)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 固定更新事件
|
||||||
|
* @param dt 固定时间步长
|
||||||
|
*/
|
||||||
|
DECLARE_EVENT_1(OnFixedUpdate, Engine, float)
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 窗口事件
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 窗口大小改变事件
|
||||||
|
* @param width 新宽度
|
||||||
|
* @param height 新高度
|
||||||
|
*/
|
||||||
|
DECLARE_EVENT_2(OnResize, Engine, int32, int32)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 窗口关闭事件
|
||||||
|
*/
|
||||||
|
DECLARE_EVENT_0(OnClose, Engine)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 窗口获得焦点事件
|
||||||
|
*/
|
||||||
|
DECLARE_EVENT_0(OnFocus, Engine)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 窗口失去焦点事件
|
||||||
|
*/
|
||||||
|
DECLARE_EVENT_0(OnBlur, Engine)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 窗口显示事件
|
||||||
|
*/
|
||||||
|
DECLARE_EVENT_0(OnShow, Engine)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 窗口隐藏事件
|
||||||
|
*/
|
||||||
|
DECLARE_EVENT_0(OnHide, Engine)
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 输入事件
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 键盘按下事件
|
||||||
|
* @param key 按键码
|
||||||
|
*/
|
||||||
|
DECLARE_EVENT_1(OnKeyDown, Engine, int32)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 键盘释放事件
|
||||||
|
* @param key 按键码
|
||||||
|
*/
|
||||||
|
DECLARE_EVENT_1(OnKeyUp, Engine, int32)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 鼠标按下事件
|
||||||
|
* @param button 鼠标按键
|
||||||
|
* @param x X 坐标
|
||||||
|
* @param y Y 坐标
|
||||||
|
*/
|
||||||
|
DECLARE_EVENT_3(OnMouseDown, Engine, int32, int32, int32)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 鼠标释放事件
|
||||||
|
* @param button 鼠标按键
|
||||||
|
* @param x X 坐标
|
||||||
|
* @param y Y 坐标
|
||||||
|
*/
|
||||||
|
DECLARE_EVENT_3(OnMouseUp, Engine, int32, int32, int32)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 鼠标移动事件
|
||||||
|
* @param x X 坐标
|
||||||
|
* @param y Y 坐标
|
||||||
|
*/
|
||||||
|
DECLARE_EVENT_2(OnMouseMove, Engine, int32, int32)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 鼠标滚轮事件
|
||||||
|
* @param delta 滚轮增量
|
||||||
|
*/
|
||||||
|
DECLARE_EVENT_1(OnMouseWheel, Engine, int32)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 触摸开始事件
|
||||||
|
* @param touch 触摸点信息
|
||||||
|
*/
|
||||||
|
DECLARE_EVENT_1(OnTouchBegan, Engine, const TouchPoint&)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 触摸移动事件
|
||||||
|
* @param touch 触摸点信息
|
||||||
|
*/
|
||||||
|
DECLARE_EVENT_1(OnTouchMoved, Engine, const TouchPoint&)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 触摸结束事件
|
||||||
|
* @param touch 触摸点信息
|
||||||
|
*/
|
||||||
|
DECLARE_EVENT_1(OnTouchEnded, Engine, const TouchPoint&)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 触摸取消事件
|
||||||
|
* @param touch 触摸点信息
|
||||||
|
*/
|
||||||
|
DECLARE_EVENT_1(OnTouchCancelled, Engine, const TouchPoint&)
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 场景事件
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 场景加载完成事件
|
||||||
|
*/
|
||||||
|
DECLARE_EVENT_0(OnSceneLoad, Engine)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 场景卸载事件
|
||||||
|
*/
|
||||||
|
DECLARE_EVENT_0(OnSceneUnload, Engine)
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 系统事件
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 低内存警告事件
|
||||||
|
*/
|
||||||
|
DECLARE_EVENT_0(OnLowMemory, Engine)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 进入后台事件
|
||||||
|
*/
|
||||||
|
DECLARE_EVENT_0(OnEnterBackground, Engine)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 进入前台事件
|
||||||
|
*/
|
||||||
|
DECLARE_EVENT_0(OnEnterForeground, Engine)
|
||||||
|
|
||||||
|
} // namespace extra2d::events
|
||||||
|
|
@ -4,10 +4,12 @@
|
||||||
#include <types/ptr/ref_counted.h>
|
#include <types/ptr/ref_counted.h>
|
||||||
#include <types/ptr/intrusive_ptr.h>
|
#include <types/ptr/intrusive_ptr.h>
|
||||||
#include <types/const/priority.h>
|
#include <types/const/priority.h>
|
||||||
#include <tbb/concurrent_vector.h>
|
|
||||||
#include <tbb/concurrent_hash_map.h>
|
#include <tbb/concurrent_hash_map.h>
|
||||||
#include <tbb/concurrent_priority_queue.h>
|
#include <tbb/concurrent_priority_queue.h>
|
||||||
|
#include <tbb/parallel_for.h>
|
||||||
|
#include <tbb/blocked_range.h>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
|
|
@ -105,7 +107,7 @@ private:
|
||||||
bool operator<(const UpdateEntry& o) const { return pri > o.pri; }
|
bool operator<(const UpdateEntry& o) const { return pri > o.pri; }
|
||||||
};
|
};
|
||||||
|
|
||||||
tbb::concurrent_vector<UpdateEntry> updates_;
|
std::vector<UpdateEntry> updates_;
|
||||||
tbb::concurrent_hash_map<TimerTarget*, size_t> updateIndex_;
|
tbb::concurrent_hash_map<TimerTarget*, size_t> updateIndex_;
|
||||||
tbb::concurrent_hash_map<TimerHdl, Ptr<Timer>> timers_;
|
tbb::concurrent_hash_map<TimerHdl, Ptr<Timer>> timers_;
|
||||||
tbb::concurrent_priority_queue<UpdateEntry> updateQueue_;
|
tbb::concurrent_priority_queue<UpdateEntry> updateQueue_;
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,17 @@
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 服务状态枚举
|
||||||
|
*/
|
||||||
|
enum class SvcState : uint8 {
|
||||||
|
None,
|
||||||
|
Inited,
|
||||||
|
Running,
|
||||||
|
Paused,
|
||||||
|
Shutdown
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 服务基类
|
* @brief 服务基类
|
||||||
*
|
*
|
||||||
|
|
@ -21,6 +32,8 @@ public:
|
||||||
|
|
||||||
virtual bool init() { return true; }
|
virtual bool init() { return true; }
|
||||||
virtual void shutdown() {}
|
virtual void shutdown() {}
|
||||||
|
virtual void pause() {}
|
||||||
|
virtual void resume() {}
|
||||||
|
|
||||||
virtual void update(float dt) {}
|
virtual void update(float dt) {}
|
||||||
virtual void lateUpdate(float dt) {}
|
virtual void lateUpdate(float dt) {}
|
||||||
|
|
@ -29,13 +42,14 @@ public:
|
||||||
virtual const char* name() const = 0;
|
virtual const char* name() const = 0;
|
||||||
virtual int pri() const { return Pri::Default; }
|
virtual int pri() const { return Pri::Default; }
|
||||||
|
|
||||||
bool isInited() const { return inited_; }
|
SvcState state() const { return state_; }
|
||||||
|
bool isInited() const { return state_ >= SvcState::Inited; }
|
||||||
bool isEnabled() const { return enabled_; }
|
bool isEnabled() const { return enabled_; }
|
||||||
void setEnabled(bool v) { enabled_ = v; }
|
void setEnabled(bool v) { enabled_ = v; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
IService() = default;
|
IService() = default;
|
||||||
bool inited_ = false;
|
SvcState state_ = SvcState::None;
|
||||||
bool enabled_ = true;
|
bool enabled_ = true;
|
||||||
|
|
||||||
friend class SvcMgr;
|
friend class SvcMgr;
|
||||||
|
|
@ -50,7 +64,7 @@ class SvcMgr {
|
||||||
public:
|
public:
|
||||||
static SvcMgr& inst();
|
static SvcMgr& inst();
|
||||||
|
|
||||||
void reg(Ptr<IService> svc);
|
void reg(IService* svc);
|
||||||
void unreg(const char* name);
|
void unreg(const char* name);
|
||||||
Ptr<IService> get(const char* name);
|
Ptr<IService> get(const char* name);
|
||||||
|
|
||||||
|
|
@ -60,6 +74,8 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
bool initAll();
|
bool initAll();
|
||||||
|
void pauseAll();
|
||||||
|
void resumeAll();
|
||||||
void shutdownAll();
|
void shutdownAll();
|
||||||
|
|
||||||
void updateAll(float dt);
|
void updateAll(float dt);
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,12 @@
|
||||||
#include <core/scheduler.h>
|
#include <core/scheduler.h>
|
||||||
#include <core/service.h>
|
#include <core/service.h>
|
||||||
|
|
||||||
|
// Platform
|
||||||
|
#include <platform/sdl2.h>
|
||||||
|
#include <platform/input.h>
|
||||||
|
#include <platform/window.h>
|
||||||
|
#include <platform/file.h>
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
#include <utils/logger.h>
|
#include <utils/logger.h>
|
||||||
#include <utils/random.h>
|
#include <utils/random.h>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,156 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <types/base/types.h>
|
||||||
|
#include <core/service.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 文件信息
|
||||||
|
*/
|
||||||
|
struct FileInfo {
|
||||||
|
std::string path;
|
||||||
|
std::string name;
|
||||||
|
bool isDir = false;
|
||||||
|
int64 size = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 文件读取结果
|
||||||
|
*/
|
||||||
|
struct FileData {
|
||||||
|
bool ok = false;
|
||||||
|
std::vector<uint8> data;
|
||||||
|
std::string error;
|
||||||
|
|
||||||
|
operator bool() const { return ok; }
|
||||||
|
const uint8* ptr() const { return data.data(); }
|
||||||
|
size_t size() const { return data.size(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 文件服务
|
||||||
|
*
|
||||||
|
* 提供跨平台文件系统操作
|
||||||
|
*/
|
||||||
|
class FileSvc : public IService {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief 获取单例实例
|
||||||
|
*/
|
||||||
|
static FileSvc& inst();
|
||||||
|
|
||||||
|
const char* name() const override { return "FileSvc"; }
|
||||||
|
int pri() const override { return Pri::System; }
|
||||||
|
|
||||||
|
bool init() override;
|
||||||
|
void shutdown() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查文件是否存在
|
||||||
|
*/
|
||||||
|
bool exists(const std::string& path) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否为目录
|
||||||
|
*/
|
||||||
|
bool isDir(const std::string& path) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 读取整个文件到内存
|
||||||
|
*/
|
||||||
|
FileData read(const std::string& path) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 读取文件为字符串
|
||||||
|
*/
|
||||||
|
std::string readString(const std::string& path) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 写入数据到文件
|
||||||
|
*/
|
||||||
|
bool write(const std::string& path, const void* data, size_t size) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 写入字符串到文件
|
||||||
|
*/
|
||||||
|
bool writeString(const std::string& path, const std::string& content) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 追加数据到文件
|
||||||
|
*/
|
||||||
|
bool append(const std::string& path, const void* data, size_t size) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 删除文件
|
||||||
|
*/
|
||||||
|
bool remove(const std::string& path) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建目录
|
||||||
|
*/
|
||||||
|
bool mkdir(const std::string& path) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 列出目录内容
|
||||||
|
*/
|
||||||
|
std::vector<FileInfo> listDir(const std::string& path) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取文件大小
|
||||||
|
*/
|
||||||
|
int64 fileSize(const std::string& path) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取文件扩展名
|
||||||
|
*/
|
||||||
|
std::string ext(const std::string& path) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取文件名(不含路径)
|
||||||
|
*/
|
||||||
|
std::string fileName(const std::string& path) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取文件所在目录
|
||||||
|
*/
|
||||||
|
std::string dirName(const std::string& path) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 连接路径
|
||||||
|
*/
|
||||||
|
std::string join(const std::string& a, const std::string& b) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取可写目录(用于存档等)
|
||||||
|
*/
|
||||||
|
std::string writableDir() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置资源根目录
|
||||||
|
*/
|
||||||
|
void setAssetRoot(const std::string& root) { assetRoot_ = root; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取资源根目录
|
||||||
|
*/
|
||||||
|
const std::string& assetRoot() const { return assetRoot_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取资源完整路径
|
||||||
|
*/
|
||||||
|
std::string assetPath(const std::string& relPath) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
FileSvc() = default;
|
||||||
|
|
||||||
|
std::string assetRoot_;
|
||||||
|
std::string writableDir_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define FILE_SVC extra2d::FileSvc::inst()
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,369 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
#include <array>
|
||||||
|
#include <core/service.h>
|
||||||
|
#include <types/base/types.h>
|
||||||
|
#include <types/ptr/ref_counted.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 键键码别名
|
||||||
|
*/
|
||||||
|
using Key = SDL_Scancode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 按键常量
|
||||||
|
*/
|
||||||
|
namespace Keys {
|
||||||
|
constexpr Key Unknown = SDL_SCANCODE_UNKNOWN;
|
||||||
|
constexpr Key A = SDL_SCANCODE_A;
|
||||||
|
constexpr Key B = SDL_SCANCODE_B;
|
||||||
|
constexpr Key C = SDL_SCANCODE_C;
|
||||||
|
constexpr Key D = SDL_SCANCODE_D;
|
||||||
|
constexpr Key E = SDL_SCANCODE_E;
|
||||||
|
constexpr Key F = SDL_SCANCODE_F;
|
||||||
|
constexpr Key G = SDL_SCANCODE_G;
|
||||||
|
constexpr Key H = SDL_SCANCODE_H;
|
||||||
|
constexpr Key I = SDL_SCANCODE_I;
|
||||||
|
constexpr Key J = SDL_SCANCODE_J;
|
||||||
|
constexpr Key K = SDL_SCANCODE_K;
|
||||||
|
constexpr Key L = SDL_SCANCODE_L;
|
||||||
|
constexpr Key M = SDL_SCANCODE_M;
|
||||||
|
constexpr Key N = SDL_SCANCODE_N;
|
||||||
|
constexpr Key O = SDL_SCANCODE_O;
|
||||||
|
constexpr Key P = SDL_SCANCODE_P;
|
||||||
|
constexpr Key Q = SDL_SCANCODE_Q;
|
||||||
|
constexpr Key R = SDL_SCANCODE_R;
|
||||||
|
constexpr Key S = SDL_SCANCODE_S;
|
||||||
|
constexpr Key T = SDL_SCANCODE_T;
|
||||||
|
constexpr Key U = SDL_SCANCODE_U;
|
||||||
|
constexpr Key V = SDL_SCANCODE_V;
|
||||||
|
constexpr Key W = SDL_SCANCODE_W;
|
||||||
|
constexpr Key X = SDL_SCANCODE_X;
|
||||||
|
constexpr Key Y = SDL_SCANCODE_Y;
|
||||||
|
constexpr Key Z = SDL_SCANCODE_Z;
|
||||||
|
constexpr Key Num0 = SDL_SCANCODE_0;
|
||||||
|
constexpr Key Num1 = SDL_SCANCODE_1;
|
||||||
|
constexpr Key Num2 = SDL_SCANCODE_2;
|
||||||
|
constexpr Key Num3 = SDL_SCANCODE_3;
|
||||||
|
constexpr Key Num4 = SDL_SCANCODE_4;
|
||||||
|
constexpr Key Num5 = SDL_SCANCODE_5;
|
||||||
|
constexpr Key Num6 = SDL_SCANCODE_6;
|
||||||
|
constexpr Key Num7 = SDL_SCANCODE_7;
|
||||||
|
constexpr Key Num8 = SDL_SCANCODE_8;
|
||||||
|
constexpr Key Num9 = SDL_SCANCODE_9;
|
||||||
|
constexpr Key F1 = SDL_SCANCODE_F1;
|
||||||
|
constexpr Key F2 = SDL_SCANCODE_F2;
|
||||||
|
constexpr Key F3 = SDL_SCANCODE_F3;
|
||||||
|
constexpr Key F4 = SDL_SCANCODE_F4;
|
||||||
|
constexpr Key F5 = SDL_SCANCODE_F5;
|
||||||
|
constexpr Key F6 = SDL_SCANCODE_F6;
|
||||||
|
constexpr Key F7 = SDL_SCANCODE_F7;
|
||||||
|
constexpr Key F8 = SDL_SCANCODE_F8;
|
||||||
|
constexpr Key F9 = SDL_SCANCODE_F9;
|
||||||
|
constexpr Key F10 = SDL_SCANCODE_F10;
|
||||||
|
constexpr Key F11 = SDL_SCANCODE_F11;
|
||||||
|
constexpr Key F12 = SDL_SCANCODE_F12;
|
||||||
|
constexpr Key Space = SDL_SCANCODE_SPACE;
|
||||||
|
constexpr Key Enter = SDL_SCANCODE_RETURN;
|
||||||
|
constexpr Key Escape = SDL_SCANCODE_ESCAPE;
|
||||||
|
constexpr Key Tab = SDL_SCANCODE_TAB;
|
||||||
|
constexpr Key Backspace = SDL_SCANCODE_BACKSPACE;
|
||||||
|
constexpr Key Insert = SDL_SCANCODE_INSERT;
|
||||||
|
constexpr Key Delete = SDL_SCANCODE_DELETE;
|
||||||
|
constexpr Key Home = SDL_SCANCODE_HOME;
|
||||||
|
constexpr Key End = SDL_SCANCODE_END;
|
||||||
|
constexpr Key PageUp = SDL_SCANCODE_PAGEUP;
|
||||||
|
constexpr Key PageDown = SDL_SCANCODE_PAGEDOWN;
|
||||||
|
constexpr Key Left = SDL_SCANCODE_LEFT;
|
||||||
|
constexpr Key Right = SDL_SCANCODE_RIGHT;
|
||||||
|
constexpr Key Up = SDL_SCANCODE_UP;
|
||||||
|
constexpr Key Down = SDL_SCANCODE_DOWN;
|
||||||
|
constexpr Key LeftShift = SDL_SCANCODE_LSHIFT;
|
||||||
|
constexpr Key RightShift = SDL_SCANCODE_RSHIFT;
|
||||||
|
constexpr Key LeftCtrl = SDL_SCANCODE_LCTRL;
|
||||||
|
constexpr Key RightCtrl = SDL_SCANCODE_RCTRL;
|
||||||
|
constexpr Key LeftAlt = SDL_SCANCODE_LALT;
|
||||||
|
constexpr Key RightAlt = SDL_SCANCODE_RALT;
|
||||||
|
} // namespace Keys
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 鼠标按键
|
||||||
|
*/
|
||||||
|
enum class MouseBtn : uint8 {
|
||||||
|
Left = 0,
|
||||||
|
Middle = 1,
|
||||||
|
Right = 2,
|
||||||
|
X1 = 3,
|
||||||
|
X2 = 4,
|
||||||
|
Count = 5
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 游戏手柄按键
|
||||||
|
*/
|
||||||
|
enum class GamepadBtn : uint8 {
|
||||||
|
A = 0,
|
||||||
|
B = 1,
|
||||||
|
X = 2,
|
||||||
|
Y = 3,
|
||||||
|
Back = 4,
|
||||||
|
Guide = 5,
|
||||||
|
Start = 6,
|
||||||
|
LeftStick = 7,
|
||||||
|
RightStick = 8,
|
||||||
|
LeftShoulder = 9,
|
||||||
|
RightShoulder = 10,
|
||||||
|
DPadUp = 11,
|
||||||
|
DPadDown = 12,
|
||||||
|
DPadLeft = 13,
|
||||||
|
DPadRight = 14,
|
||||||
|
Count = 15
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 游戏手柄轴
|
||||||
|
*/
|
||||||
|
enum class GamepadAxis : uint8 {
|
||||||
|
LeftX = 0,
|
||||||
|
LeftY = 1,
|
||||||
|
RightX = 2,
|
||||||
|
RightY = 3,
|
||||||
|
TriggerLeft = 4,
|
||||||
|
TriggerRight = 5,
|
||||||
|
Count = 6
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 触摸状态
|
||||||
|
*/
|
||||||
|
enum class TouchState : uint8 { None = 0, Began, Moved, Ended, Cancelled };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 触摸点信息
|
||||||
|
*/
|
||||||
|
struct TouchPoint {
|
||||||
|
int64 id = 0;
|
||||||
|
float x = 0.0f;
|
||||||
|
float y = 0.0f;
|
||||||
|
float prevX = 0.0f;
|
||||||
|
float prevY = 0.0f;
|
||||||
|
float deltaX = 0.0f;
|
||||||
|
float deltaY = 0.0f;
|
||||||
|
TouchState state = TouchState::None;
|
||||||
|
float pressure = 1.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 按键回调类型
|
||||||
|
*/
|
||||||
|
using KeyCb = Fn<void(Key)>;
|
||||||
|
using MouseBtnCb = Fn<void(MouseBtn, int32 x, int32 y)>;
|
||||||
|
using TouchCb = Fn<void(const TouchPoint &)>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 输入服务
|
||||||
|
*
|
||||||
|
* 管理键盘、鼠标、触摸、游戏手柄输入
|
||||||
|
*/
|
||||||
|
class InputSvc : public IService {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief 获取单例实例
|
||||||
|
*/
|
||||||
|
static InputSvc &inst();
|
||||||
|
|
||||||
|
const char *name() const override { return "InputSvc"; }
|
||||||
|
int pri() const override { return Pri::Input; }
|
||||||
|
|
||||||
|
bool init() override;
|
||||||
|
void shutdown() override;
|
||||||
|
void update(float dt) override;
|
||||||
|
|
||||||
|
// ========== 键盘 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查按键是否按下
|
||||||
|
*/
|
||||||
|
bool isKeyDown(Key key) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查按键是否刚按下
|
||||||
|
*/
|
||||||
|
bool isKeyPressed(Key key) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查按键是否刚释放
|
||||||
|
*/
|
||||||
|
bool isKeyReleased(Key key) const;
|
||||||
|
|
||||||
|
// ========== 鼠标 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取鼠标位置
|
||||||
|
*/
|
||||||
|
void getMousePos(int32 &x, int32 &y) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取鼠标位置(浮点)
|
||||||
|
*/
|
||||||
|
void getMousePos(float &x, float &y) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查鼠标按键是否按下
|
||||||
|
*/
|
||||||
|
bool isMouseBtnDown(MouseBtn btn) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查鼠标按键是否刚按下
|
||||||
|
*/
|
||||||
|
bool isMouseBtnPressed(MouseBtn btn) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查鼠标按键是否刚释放
|
||||||
|
*/
|
||||||
|
bool isMouseBtnReleased(MouseBtn btn) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取鼠标滚轮
|
||||||
|
*/
|
||||||
|
int32 getMouseWheel() const;
|
||||||
|
|
||||||
|
// ========== 触摸 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取触摸点数量
|
||||||
|
*/
|
||||||
|
int32 touchCount() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取触摸点
|
||||||
|
* @param idx 触摸点索引
|
||||||
|
* @return 触摸点信息,无效索引返回 nullptr
|
||||||
|
*/
|
||||||
|
const TouchPoint *getTouch(int32 idx) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 根据 ID 获取触摸点
|
||||||
|
*/
|
||||||
|
const TouchPoint *getTouchById(int64 id) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否有触摸
|
||||||
|
*/
|
||||||
|
bool hasTouch() const { return touchCount() > 0; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取所有活跃触摸点
|
||||||
|
*/
|
||||||
|
const std::vector<TouchPoint> &getTouches() const { return activeTouches_; }
|
||||||
|
|
||||||
|
// ========== 游戏手柄 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 连接的游戏手柄数量
|
||||||
|
*/
|
||||||
|
int32 gamepadCount() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查手柄按键是否按下
|
||||||
|
*/
|
||||||
|
bool isGamepadBtnDown(int32 idx, GamepadBtn btn) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查手柄按键是否刚按下
|
||||||
|
*/
|
||||||
|
bool isGamepadBtnPressed(int32 idx, GamepadBtn btn) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取手柄轴值 (-1.0 到 1.0)
|
||||||
|
*/
|
||||||
|
float getGamepadAxis(int32 idx, GamepadAxis axis) const;
|
||||||
|
|
||||||
|
// ========== 回调设置 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置按键按下回调
|
||||||
|
*/
|
||||||
|
void setOnKeyDown(KeyCb cb) { onKeyDown_ = std::move(cb); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置按键释放回调
|
||||||
|
*/
|
||||||
|
void setOnKeyUp(KeyCb cb) { onKeyUp_ = std::move(cb); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置鼠标按下回调
|
||||||
|
*/
|
||||||
|
void setOnMouseDown(MouseBtnCb cb) { onMouseDown_ = std::move(cb); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置鼠标释放回调
|
||||||
|
*/
|
||||||
|
void setOnMouseUp(MouseBtnCb cb) { onMouseUp_ = std::move(cb); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置触摸开始回调
|
||||||
|
*/
|
||||||
|
void setOnTouchBegan(TouchCb cb) { onTouchBegan_ = std::move(cb); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置触摸移动回调
|
||||||
|
*/
|
||||||
|
void setOnTouchMoved(TouchCb cb) { onTouchMoved_ = std::move(cb); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置触摸结束回调
|
||||||
|
*/
|
||||||
|
void setOnTouchEnded(TouchCb cb) { onTouchEnded_ = std::move(cb); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
InputSvc() = default;
|
||||||
|
|
||||||
|
static constexpr int32 KEY_COUNT = SDL_NUM_SCANCODES;
|
||||||
|
static constexpr int32 MAX_GAMEPADS = 4;
|
||||||
|
static constexpr int32 MAX_TOUCHES = 10;
|
||||||
|
|
||||||
|
std::array<uint8, KEY_COUNT> keyState_{};
|
||||||
|
std::array<uint8, KEY_COUNT> keyPrev_{};
|
||||||
|
|
||||||
|
int32 mouseX_ = 0;
|
||||||
|
int32 mouseY_ = 0;
|
||||||
|
int32 mouseWheel_ = 0;
|
||||||
|
std::array<uint8, static_cast<size_t>(MouseBtn::Count)> mouseState_{};
|
||||||
|
std::array<uint8, static_cast<size_t>(MouseBtn::Count)> mousePrev_{};
|
||||||
|
|
||||||
|
std::vector<TouchPoint> activeTouches_;
|
||||||
|
std::vector<TouchPoint> endedTouches_;
|
||||||
|
|
||||||
|
SDL_GameController *gamepads_[MAX_GAMEPADS] = {};
|
||||||
|
std::array<uint8, static_cast<size_t>(GamepadBtn::Count)>
|
||||||
|
padState_[MAX_GAMEPADS];
|
||||||
|
std::array<uint8, static_cast<size_t>(GamepadBtn::Count)>
|
||||||
|
padPrev_[MAX_GAMEPADS];
|
||||||
|
|
||||||
|
KeyCb onKeyDown_;
|
||||||
|
KeyCb onKeyUp_;
|
||||||
|
MouseBtnCb onMouseDown_;
|
||||||
|
MouseBtnCb onMouseUp_;
|
||||||
|
TouchCb onTouchBegan_;
|
||||||
|
TouchCb onTouchMoved_;
|
||||||
|
TouchCb onTouchEnded_;
|
||||||
|
|
||||||
|
void processEvent(const SDL_Event &evt);
|
||||||
|
void openGamepad(int32 idx);
|
||||||
|
void closeGamepad(int32 idx);
|
||||||
|
|
||||||
|
void processTouchDown(const SDL_TouchFingerEvent &evt);
|
||||||
|
void processTouchUp(const SDL_TouchFingerEvent &evt);
|
||||||
|
void processTouchMotion(const SDL_TouchFingerEvent &evt);
|
||||||
|
|
||||||
|
friend class WindowSvc;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define INPUT_SVC extra2d::InputSvc::inst()
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
#include <SDL_syswm.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief SDL2 初始化和生命周期管理
|
||||||
|
*
|
||||||
|
* 提供 SDL2 子系统的统一初始化和清理接口
|
||||||
|
*/
|
||||||
|
class Sdl2 {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief 初始化 SDL2 核心子系统
|
||||||
|
* @return true 初始化成功
|
||||||
|
*/
|
||||||
|
static bool initCore();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化视频子系统
|
||||||
|
* @return true 初始化成功
|
||||||
|
*/
|
||||||
|
static bool initVideo();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化音频子系统
|
||||||
|
* @return true 初始化成功
|
||||||
|
*/
|
||||||
|
static bool initAudio();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化游戏控制器子系统
|
||||||
|
* @return true 初始化成功
|
||||||
|
*/
|
||||||
|
static bool initGamepad();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化所有子系统
|
||||||
|
* @return true 初始化成功
|
||||||
|
*/
|
||||||
|
static bool initAll();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 关闭 SDL2
|
||||||
|
*/
|
||||||
|
static void shutdown();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否已初始化
|
||||||
|
*/
|
||||||
|
static bool isInited() { return inited_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
static bool inited_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,161 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
#include <core/service.h>
|
||||||
|
#include <string>
|
||||||
|
#include <types/base/types.h>
|
||||||
|
#include <types/math/size.h>
|
||||||
|
#include <types/math/vec2.h>
|
||||||
|
#include <types/ptr/ref_counted.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 窗口配置
|
||||||
|
*/
|
||||||
|
struct WindowCfg {
|
||||||
|
std::string title = "Extra2D";
|
||||||
|
int32 width = 1280;
|
||||||
|
int32 height = 720;
|
||||||
|
bool fullscreen = false;
|
||||||
|
bool resizable = true;
|
||||||
|
bool vsync = true;
|
||||||
|
int32 glMajor = 3;
|
||||||
|
int32 glMinor = 3;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 窗口事件回调
|
||||||
|
*/
|
||||||
|
using ResizeCb = Fn<void(int32 w, int32 h)>;
|
||||||
|
using CloseCb = Fn<void()>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 窗口服务
|
||||||
|
*
|
||||||
|
* 管理 SDL2 窗口和 OpenGL 上下文
|
||||||
|
*/
|
||||||
|
class WindowSvc : public IService {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief 获取单例实例
|
||||||
|
*/
|
||||||
|
static WindowSvc &inst();
|
||||||
|
|
||||||
|
const char *name() const override { return "WindowSvc"; }
|
||||||
|
int pri() const override { return Pri::System; }
|
||||||
|
|
||||||
|
bool init() override;
|
||||||
|
void shutdown() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 使用配置创建窗口
|
||||||
|
*/
|
||||||
|
bool create(const WindowCfg &cfg);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 处理窗口事件
|
||||||
|
* @return true 继续运行,false 应退出
|
||||||
|
*/
|
||||||
|
bool pollEvents();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 交换缓冲区
|
||||||
|
*/
|
||||||
|
void swapBuffers();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取 SDL 窗口句柄
|
||||||
|
*/
|
||||||
|
SDL_Window *handle() const { return window_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取 OpenGL 上下文
|
||||||
|
*/
|
||||||
|
SDL_GLContext glContext() const { return glCtx_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取窗口尺寸
|
||||||
|
*/
|
||||||
|
Size getSize() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取窗口位置
|
||||||
|
*/
|
||||||
|
Vec2 getPosition() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置窗口尺寸
|
||||||
|
*/
|
||||||
|
void setSize(int32 w, int32 h);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置窗口标题
|
||||||
|
*/
|
||||||
|
void setTitle(const std::string &title);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置全屏模式
|
||||||
|
*/
|
||||||
|
void setFullscreen(bool fullscreen);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否全屏
|
||||||
|
*/
|
||||||
|
bool isFullscreen() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置垂直同步
|
||||||
|
*/
|
||||||
|
void setVsync(bool vsync);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否垂直同步
|
||||||
|
*/
|
||||||
|
bool isVsync() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 显示/隐藏窗口
|
||||||
|
*/
|
||||||
|
void setVisible(bool visible);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查窗口是否可见
|
||||||
|
*/
|
||||||
|
bool isVisible() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置窗口关闭回调
|
||||||
|
*/
|
||||||
|
void setOnClose(CloseCb cb) { onClose_ = std::move(cb); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置窗口大小改变回调
|
||||||
|
*/
|
||||||
|
void setOnResize(ResizeCb cb) { onResize_ = std::move(cb); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 请求关闭窗口
|
||||||
|
*/
|
||||||
|
void requestClose() { shouldClose_ = true; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否应该关闭
|
||||||
|
*/
|
||||||
|
bool shouldClose() const { return shouldClose_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
WindowSvc() = default;
|
||||||
|
|
||||||
|
SDL_Window *window_ = nullptr;
|
||||||
|
SDL_GLContext glCtx_ = nullptr;
|
||||||
|
bool shouldClose_ = false;
|
||||||
|
bool vsync_ = true;
|
||||||
|
|
||||||
|
CloseCb onClose_;
|
||||||
|
ResizeCb onResize_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define WINDOW extra2d::WindowSvc::inst()
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -1,5 +1,11 @@
|
||||||
#include <app/application.h>
|
#include <app/application.h>
|
||||||
#include <core/director.h>
|
#include <core/director.h>
|
||||||
|
#include <core/service.h>
|
||||||
|
#include <core/event/events.h>
|
||||||
|
#include <platform/sdl2.h>
|
||||||
|
#include <platform/window.h>
|
||||||
|
#include <platform/input.h>
|
||||||
|
#include <platform/file.h>
|
||||||
#include <utils/logger.h>
|
#include <utils/logger.h>
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
@ -11,6 +17,9 @@
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取当前时间(秒)
|
||||||
|
*/
|
||||||
static double getTimeSeconds() {
|
static double getTimeSeconds() {
|
||||||
#ifdef __SWITCH__
|
#ifdef __SWITCH__
|
||||||
struct timespec ts;
|
struct timespec ts;
|
||||||
|
|
@ -67,24 +76,65 @@ bool Application::init(const AppConfig& config) {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!Sdl2::initAll()) {
|
||||||
|
E2D_LOG_ERROR("Failed to initialize SDL2");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SVC_MGR.reg(&WINDOW);
|
||||||
|
SVC_MGR.reg(&INPUT_SVC);
|
||||||
|
SVC_MGR.reg(&FILE_SVC);
|
||||||
|
|
||||||
|
if (!SVC_MGR.initAll()) {
|
||||||
|
E2D_LOG_ERROR("Failed to initialize services");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowCfg winCfg;
|
||||||
|
winCfg.title = config_.title;
|
||||||
|
winCfg.width = config_.width;
|
||||||
|
winCfg.height = config_.height;
|
||||||
|
winCfg.fullscreen = config_.fullscreen;
|
||||||
|
winCfg.resizable = config_.resizable;
|
||||||
|
winCfg.vsync = config_.vsync;
|
||||||
|
winCfg.glMajor = config_.glMajor;
|
||||||
|
winCfg.glMinor = config_.glMinor;
|
||||||
|
|
||||||
|
if (!WINDOW.create(winCfg)) {
|
||||||
|
E2D_LOG_ERROR("Failed to create window");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!DIRECTOR.init()) {
|
if (!DIRECTOR.init()) {
|
||||||
E2D_LOG_ERROR("Failed to initialize Director");
|
E2D_LOG_ERROR("Failed to initialize Director");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WINDOW.setOnClose([this]() {
|
||||||
|
quit();
|
||||||
|
});
|
||||||
|
|
||||||
initialized_ = true;
|
initialized_ = true;
|
||||||
running_ = true;
|
running_ = true;
|
||||||
|
|
||||||
|
events::OnInit::emit();
|
||||||
|
|
||||||
E2D_LOG_INFO("Application initialized successfully");
|
E2D_LOG_INFO("Application initialized successfully");
|
||||||
|
E2D_LOG_INFO("Window: {}x{}, Fullscreen: {}, VSync: {}",
|
||||||
|
config_.width, config_.height, config_.fullscreen, config_.vsync);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::shutdown() {
|
void Application::shutdown() {
|
||||||
if (!initialized_) return;
|
if (!initialized_) return;
|
||||||
|
|
||||||
|
events::OnShutdown::emit();
|
||||||
|
|
||||||
E2D_LOG_INFO("Shutting down application...");
|
E2D_LOG_INFO("Shutting down application...");
|
||||||
|
|
||||||
DIRECTOR.shutdown();
|
DIRECTOR.shutdown();
|
||||||
|
SVC_MGR.shutdownAll();
|
||||||
|
Sdl2::shutdown();
|
||||||
|
|
||||||
PlatformType platform = config_.platform;
|
PlatformType platform = config_.platform;
|
||||||
if (platform == PlatformType::Auto) {
|
if (platform == PlatformType::Auto) {
|
||||||
|
|
@ -116,7 +166,7 @@ void Application::run() {
|
||||||
|
|
||||||
lastFrameTime_ = getTimeSeconds();
|
lastFrameTime_ = getTimeSeconds();
|
||||||
|
|
||||||
while (running_) {
|
while (running_ && !WINDOW.shouldClose()) {
|
||||||
mainLoop();
|
mainLoop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -124,12 +174,15 @@ void Application::run() {
|
||||||
void Application::quit() {
|
void Application::quit() {
|
||||||
shouldQuit_ = true;
|
shouldQuit_ = true;
|
||||||
running_ = false;
|
running_ = false;
|
||||||
|
WINDOW.requestClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::pause() {
|
void Application::pause() {
|
||||||
if (!paused_) {
|
if (!paused_) {
|
||||||
paused_ = true;
|
paused_ = true;
|
||||||
|
SVC_MGR.pauseAll();
|
||||||
DIRECTOR.pause();
|
DIRECTOR.pause();
|
||||||
|
events::OnPause::emit();
|
||||||
E2D_LOG_INFO("Application paused");
|
E2D_LOG_INFO("Application paused");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -137,13 +190,20 @@ void Application::pause() {
|
||||||
void Application::resume() {
|
void Application::resume() {
|
||||||
if (paused_) {
|
if (paused_) {
|
||||||
paused_ = false;
|
paused_ = false;
|
||||||
|
SVC_MGR.resumeAll();
|
||||||
DIRECTOR.resume();
|
DIRECTOR.resume();
|
||||||
lastFrameTime_ = getTimeSeconds();
|
lastFrameTime_ = getTimeSeconds();
|
||||||
|
events::OnResume::emit();
|
||||||
E2D_LOG_INFO("Application resumed");
|
E2D_LOG_INFO("Application resumed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::mainLoop() {
|
void Application::mainLoop() {
|
||||||
|
if (!WINDOW.pollEvents()) {
|
||||||
|
running_ = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
double currentTime = getTimeSeconds();
|
double currentTime = getTimeSeconds();
|
||||||
deltaTime_ = static_cast<float>(currentTime - lastFrameTime_);
|
deltaTime_ = static_cast<float>(currentTime - lastFrameTime_);
|
||||||
lastFrameTime_ = currentTime;
|
lastFrameTime_ = currentTime;
|
||||||
|
|
@ -162,6 +222,8 @@ void Application::mainLoop() {
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WINDOW.swapBuffers();
|
||||||
|
|
||||||
if (!config_.vsync && config_.fpsLimit > 0) {
|
if (!config_.vsync && config_.fpsLimit > 0) {
|
||||||
double frameEndTime = getTimeSeconds();
|
double frameEndTime = getTimeSeconds();
|
||||||
double frameTime = frameEndTime - currentTime;
|
double frameTime = frameEndTime - currentTime;
|
||||||
|
|
@ -173,6 +235,8 @@ void Application::mainLoop() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::update() {
|
void Application::update() {
|
||||||
|
SVC_MGR.updateAll(deltaTime_);
|
||||||
|
events::OnUpdate::emit(deltaTime_);
|
||||||
DIRECTOR.mainLoop(deltaTime_);
|
DIRECTOR.mainLoop(deltaTime_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
#include <core/event/event_bus.h>
|
||||||
|
|
||||||
|
namespace extra2d::event {
|
||||||
|
|
||||||
|
ListenerContainer::~ListenerContainer() {
|
||||||
|
ListenerEntry* curr = listenerList_;
|
||||||
|
while (curr) {
|
||||||
|
ListenerEntry* next = curr->next;
|
||||||
|
delete curr;
|
||||||
|
curr = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
curr = listenersToAdd_;
|
||||||
|
while (curr) {
|
||||||
|
ListenerEntry* next = curr->next;
|
||||||
|
delete curr;
|
||||||
|
curr = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto* entry : listenersToRemove_) {
|
||||||
|
delete entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ListenerContainer::addListener(ListenerBase* listener) {
|
||||||
|
if (!listener || !listener->entry_) return;
|
||||||
|
|
||||||
|
if (broadcasting_ > 0) {
|
||||||
|
listener->entry_->next = listenersToAdd_;
|
||||||
|
listenersToAdd_ = listener->entry_;
|
||||||
|
} else {
|
||||||
|
listener->entry_->next = listenerList_;
|
||||||
|
listener->entry_->prev = nullptr;
|
||||||
|
if (listenerList_) {
|
||||||
|
listenerList_->prev = listener->entry_;
|
||||||
|
}
|
||||||
|
listenerList_ = listener->entry_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ListenerContainer::removeListener(ListenerBase* listener) {
|
||||||
|
if (!listener || !listener->entry_) return;
|
||||||
|
|
||||||
|
if (broadcasting_ > 0) {
|
||||||
|
listenersToRemove_.push_back(listener->entry_);
|
||||||
|
listener->entry_->listener = nullptr;
|
||||||
|
} else {
|
||||||
|
if (listener->entry_->prev) {
|
||||||
|
listener->entry_->prev->next = listener->entry_->next;
|
||||||
|
} else {
|
||||||
|
listenerList_ = listener->entry_->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listener->entry_->next) {
|
||||||
|
listener->entry_->next->prev = listener->entry_->prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete listener->entry_;
|
||||||
|
listener->entry_ = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ListenerContainer::processPendingListeners() {
|
||||||
|
while (listenersToAdd_) {
|
||||||
|
ListenerEntry* entry = listenersToAdd_;
|
||||||
|
listenersToAdd_ = entry->next;
|
||||||
|
|
||||||
|
entry->next = listenerList_;
|
||||||
|
entry->prev = nullptr;
|
||||||
|
if (listenerList_) {
|
||||||
|
listenerList_->prev = entry;
|
||||||
|
}
|
||||||
|
listenerList_ = entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto* entry : listenersToRemove_) {
|
||||||
|
if (entry->prev) {
|
||||||
|
entry->prev->next = entry->next;
|
||||||
|
} else {
|
||||||
|
listenerList_ = entry->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry->next) {
|
||||||
|
entry->next->prev = entry->prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete entry;
|
||||||
|
}
|
||||||
|
listenersToRemove_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ListenerContainer::hasPendingListeners() const {
|
||||||
|
return listenersToAdd_ != nullptr || !listenersToRemove_.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d::event
|
||||||
|
|
@ -8,11 +8,11 @@ SvcMgr& SvcMgr::inst() {
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SvcMgr::reg(Ptr<IService> svc) {
|
void SvcMgr::reg(IService* svc) {
|
||||||
if (!svc) return;
|
if (!svc) return;
|
||||||
SvcMap::accessor acc;
|
SvcMap::accessor acc;
|
||||||
svcMap_.insert(acc, svc->name());
|
svcMap_.insert(acc, svc->name());
|
||||||
acc->second = svc;
|
acc->second = Ptr<IService>(svc);
|
||||||
sortSvcs();
|
sortSvcs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,28 +31,49 @@ Ptr<IService> SvcMgr::get(const char* name) {
|
||||||
|
|
||||||
bool SvcMgr::initAll() {
|
bool SvcMgr::initAll() {
|
||||||
for (auto& svc : sortedSvcs_) {
|
for (auto& svc : sortedSvcs_) {
|
||||||
if (svc && !svc->isInited()) {
|
if (svc && svc->state_ == SvcState::None) {
|
||||||
if (!svc->init()) {
|
if (!svc->init()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
svc->inited_ = true;
|
svc->state_ = SvcState::Inited;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SvcMgr::pauseAll() {
|
||||||
|
for (auto& svc : sortedSvcs_) {
|
||||||
|
if (svc && svc->state_ == SvcState::Running) {
|
||||||
|
svc->pause();
|
||||||
|
svc->state_ = SvcState::Paused;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SvcMgr::resumeAll() {
|
||||||
|
for (auto& svc : sortedSvcs_) {
|
||||||
|
if (svc && svc->state_ == SvcState::Paused) {
|
||||||
|
svc->resume();
|
||||||
|
svc->state_ = SvcState::Running;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void SvcMgr::shutdownAll() {
|
void SvcMgr::shutdownAll() {
|
||||||
for (auto it = sortedSvcs_.rbegin(); it != sortedSvcs_.rend(); ++it) {
|
for (auto it = sortedSvcs_.rbegin(); it != sortedSvcs_.rend(); ++it) {
|
||||||
if (*it && (*it)->isInited()) {
|
if (*it && (*it)->state_ >= SvcState::Inited) {
|
||||||
(*it)->shutdown();
|
(*it)->shutdown();
|
||||||
(*it)->inited_ = false;
|
(*it)->state_ = SvcState::Shutdown;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SvcMgr::updateAll(float dt) {
|
void SvcMgr::updateAll(float dt) {
|
||||||
for (auto& svc : sortedSvcs_) {
|
for (auto& svc : sortedSvcs_) {
|
||||||
if (svc && svc->isInited() && svc->isEnabled()) {
|
if (svc && svc->state_ >= SvcState::Inited && svc->isEnabled()) {
|
||||||
|
if (svc->state_ == SvcState::Inited) {
|
||||||
|
svc->state_ = SvcState::Running;
|
||||||
|
}
|
||||||
svc->update(dt);
|
svc->update(dt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -60,7 +81,7 @@ void SvcMgr::updateAll(float dt) {
|
||||||
|
|
||||||
void SvcMgr::lateUpdateAll(float dt) {
|
void SvcMgr::lateUpdateAll(float dt) {
|
||||||
for (auto& svc : sortedSvcs_) {
|
for (auto& svc : sortedSvcs_) {
|
||||||
if (svc && svc->isInited() && svc->isEnabled()) {
|
if (svc && svc->state_ >= SvcState::Running && svc->isEnabled()) {
|
||||||
svc->lateUpdate(dt);
|
svc->lateUpdate(dt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -68,7 +89,7 @@ void SvcMgr::lateUpdateAll(float dt) {
|
||||||
|
|
||||||
void SvcMgr::fixedUpdateAll(float dt) {
|
void SvcMgr::fixedUpdateAll(float dt) {
|
||||||
for (auto& svc : sortedSvcs_) {
|
for (auto& svc : sortedSvcs_) {
|
||||||
if (svc && svc->isInited() && svc->isEnabled()) {
|
if (svc && svc->state_ >= SvcState::Running && svc->isEnabled()) {
|
||||||
svc->fixedUpdate(dt);
|
svc->fixedUpdate(dt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,214 @@
|
||||||
|
#include <platform/file.h>
|
||||||
|
#include <SDL.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <direct.h>
|
||||||
|
#include <windows.h>
|
||||||
|
#define mkdir_impl(path, mode) _mkdir(path)
|
||||||
|
#else
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#define mkdir_impl(path, mode) mkdir(path, mode)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
FileSvc& FileSvc::inst() {
|
||||||
|
static FileSvc instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileSvc::init() {
|
||||||
|
if (isInited()) return true;
|
||||||
|
|
||||||
|
writableDir_ = SDL_GetPrefPath("Extra2D", "Extra2D");
|
||||||
|
if (writableDir_.empty()) {
|
||||||
|
writableDir_ = "./";
|
||||||
|
}
|
||||||
|
|
||||||
|
state_ = SvcState::Inited;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileSvc::shutdown() {
|
||||||
|
state_ = SvcState::Shutdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileSvc::exists(const std::string& path) const {
|
||||||
|
struct stat st;
|
||||||
|
return stat(path.c_str(), &st) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileSvc::isDir(const std::string& path) const {
|
||||||
|
struct stat st;
|
||||||
|
if (stat(path.c_str(), &st) != 0) return false;
|
||||||
|
return (st.st_mode & S_IFDIR) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileData FileSvc::read(const std::string& path) const {
|
||||||
|
FileData result;
|
||||||
|
|
||||||
|
std::ifstream file(path, std::ios::binary | std::ios::ate);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
result.error = "Cannot open file: " + path;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::streamsize size = file.tellg();
|
||||||
|
file.seekg(0, std::ios::beg);
|
||||||
|
|
||||||
|
result.data.resize(static_cast<size_t>(size));
|
||||||
|
if (!file.read(reinterpret_cast<char*>(result.data.data()), size)) {
|
||||||
|
result.error = "Failed to read file: " + path;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.ok = true;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string FileSvc::readString(const std::string& path) const {
|
||||||
|
std::ifstream file(path);
|
||||||
|
if (!file.is_open()) return "";
|
||||||
|
|
||||||
|
std::stringstream buffer;
|
||||||
|
buffer << file.rdbuf();
|
||||||
|
return buffer.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileSvc::write(const std::string& path, const void* data, size_t size) const {
|
||||||
|
std::ofstream file(path, std::ios::binary);
|
||||||
|
if (!file.is_open()) return false;
|
||||||
|
|
||||||
|
file.write(static_cast<const char*>(data), static_cast<std::streamsize>(size));
|
||||||
|
return file.good();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileSvc::writeString(const std::string& path, const std::string& content) const {
|
||||||
|
return write(path, content.data(), content.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileSvc::append(const std::string& path, const void* data, size_t size) const {
|
||||||
|
std::ofstream file(path, std::ios::binary | std::ios::app);
|
||||||
|
if (!file.is_open()) return false;
|
||||||
|
|
||||||
|
file.write(static_cast<const char*>(data), static_cast<std::streamsize>(size));
|
||||||
|
return file.good();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileSvc::remove(const std::string& path) const {
|
||||||
|
return std::remove(path.c_str()) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileSvc::mkdir(const std::string& path) const {
|
||||||
|
#ifdef _WIN32
|
||||||
|
return mkdir_impl(path.c_str(), 0755) == 0 || errno == EEXIST;
|
||||||
|
#else
|
||||||
|
return mkdir_impl(path.c_str(), 0755) == 0 || errno == EEXIST;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<FileInfo> FileSvc::listDir(const std::string& path) const {
|
||||||
|
std::vector<FileInfo> result;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
WIN32_FIND_DATAA findData;
|
||||||
|
std::string searchPath = path + "\\*";
|
||||||
|
HANDLE hFind = FindFirstFileA(searchPath.c_str(), &findData);
|
||||||
|
|
||||||
|
if (hFind == INVALID_HANDLE_VALUE) return result;
|
||||||
|
|
||||||
|
do {
|
||||||
|
std::string name = findData.cFileName;
|
||||||
|
if (name == "." || name == "..") continue;
|
||||||
|
|
||||||
|
FileInfo info;
|
||||||
|
info.name = name;
|
||||||
|
info.path = join(path, name);
|
||||||
|
info.isDir = (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
|
||||||
|
if (!info.isDir) {
|
||||||
|
info.size = static_cast<int64>(findData.nFileSizeLow) |
|
||||||
|
(static_cast<int64>(findData.nFileSizeHigh) << 32);
|
||||||
|
}
|
||||||
|
result.push_back(info);
|
||||||
|
} while (FindNextFileA(hFind, &findData));
|
||||||
|
|
||||||
|
FindClose(hFind);
|
||||||
|
#else
|
||||||
|
DIR* dir = opendir(path.c_str());
|
||||||
|
if (!dir) return result;
|
||||||
|
|
||||||
|
struct dirent* entry;
|
||||||
|
while ((entry = readdir(dir)) != nullptr) {
|
||||||
|
std::string name = entry->d_name;
|
||||||
|
if (name == "." || name == "..") continue;
|
||||||
|
|
||||||
|
FileInfo info;
|
||||||
|
info.name = name;
|
||||||
|
info.path = join(path, name);
|
||||||
|
info.isDir = isDir(info.path);
|
||||||
|
if (!info.isDir) {
|
||||||
|
info.size = fileSize(info.path);
|
||||||
|
}
|
||||||
|
result.push_back(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
closedir(dir);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64 FileSvc::fileSize(const std::string& path) const {
|
||||||
|
struct stat st;
|
||||||
|
if (stat(path.c_str(), &st) != 0) return -1;
|
||||||
|
return static_cast<int64>(st.st_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string FileSvc::ext(const std::string& path) const {
|
||||||
|
size_t pos = path.find_last_of('.');
|
||||||
|
if (pos == std::string::npos || pos == 0) return "";
|
||||||
|
|
||||||
|
size_t lastSep = path.find_last_of("/\\");
|
||||||
|
if (lastSep != std::string::npos && pos < lastSep) return "";
|
||||||
|
|
||||||
|
return path.substr(pos + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string FileSvc::fileName(const std::string& path) const {
|
||||||
|
size_t pos = path.find_last_of("/\\");
|
||||||
|
if (pos == std::string::npos) return path;
|
||||||
|
return path.substr(pos + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string FileSvc::dirName(const std::string& path) const {
|
||||||
|
size_t pos = path.find_last_of("/\\");
|
||||||
|
if (pos == std::string::npos) return ".";
|
||||||
|
if (pos == 0) return "/";
|
||||||
|
return path.substr(0, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string FileSvc::join(const std::string& a, const std::string& b) const {
|
||||||
|
if (a.empty()) return b;
|
||||||
|
if (b.empty()) return a;
|
||||||
|
|
||||||
|
char last = a.back();
|
||||||
|
if (last == '/' || last == '\\') {
|
||||||
|
return a + b;
|
||||||
|
}
|
||||||
|
return a + "/" + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string FileSvc::writableDir() const {
|
||||||
|
return writableDir_;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string FileSvc::assetPath(const std::string& relPath) const {
|
||||||
|
if (assetRoot_.empty()) return relPath;
|
||||||
|
return join(assetRoot_, relPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,365 @@
|
||||||
|
#include <platform/input.h>
|
||||||
|
#include <platform/window.h>
|
||||||
|
#include <SDL.h>
|
||||||
|
#include <cstring>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
InputSvc& InputSvc::inst() {
|
||||||
|
static InputSvc instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InputSvc::init() {
|
||||||
|
if (isInited()) return true;
|
||||||
|
|
||||||
|
std::memset(keyState_.data(), 0, KEY_COUNT);
|
||||||
|
std::memset(keyPrev_.data(), 0, KEY_COUNT);
|
||||||
|
std::memset(mouseState_.data(), 0, static_cast<size_t>(MouseBtn::Count));
|
||||||
|
std::memset(mousePrev_.data(), 0, static_cast<size_t>(MouseBtn::Count));
|
||||||
|
|
||||||
|
for (int32 i = 0; i < MAX_GAMEPADS; ++i) {
|
||||||
|
gamepads_[i] = nullptr;
|
||||||
|
std::memset(padState_[i].data(), 0, static_cast<size_t>(GamepadBtn::Count));
|
||||||
|
std::memset(padPrev_[i].data(), 0, static_cast<size_t>(GamepadBtn::Count));
|
||||||
|
}
|
||||||
|
|
||||||
|
activeTouches_.clear();
|
||||||
|
activeTouches_.reserve(MAX_TOUCHES);
|
||||||
|
endedTouches_.clear();
|
||||||
|
|
||||||
|
int32 numJoysticks = SDL_NumJoysticks();
|
||||||
|
for (int32 i = 0; i < numJoysticks && i < MAX_GAMEPADS; ++i) {
|
||||||
|
if (SDL_IsGameController(i)) {
|
||||||
|
openGamepad(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state_ = SvcState::Inited;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputSvc::shutdown() {
|
||||||
|
for (int32 i = 0; i < MAX_GAMEPADS; ++i) {
|
||||||
|
closeGamepad(i);
|
||||||
|
}
|
||||||
|
activeTouches_.clear();
|
||||||
|
endedTouches_.clear();
|
||||||
|
state_ = SvcState::Shutdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputSvc::update(float dt) {
|
||||||
|
std::memcpy(keyPrev_.data(), keyState_.data(), KEY_COUNT);
|
||||||
|
std::memcpy(mousePrev_.data(), mouseState_.data(), static_cast<size_t>(MouseBtn::Count));
|
||||||
|
|
||||||
|
for (int32 i = 0; i < MAX_GAMEPADS; ++i) {
|
||||||
|
if (gamepads_[i]) {
|
||||||
|
std::memcpy(padPrev_[i].data(), padState_[i].data(), static_cast<size_t>(GamepadBtn::Count));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mouseWheel_ = 0;
|
||||||
|
|
||||||
|
const uint8* state = SDL_GetKeyboardState(nullptr);
|
||||||
|
std::memcpy(keyState_.data(), state, KEY_COUNT);
|
||||||
|
|
||||||
|
uint32 btnState = SDL_GetMouseState(&mouseX_, &mouseY_);
|
||||||
|
mouseState_[static_cast<size_t>(MouseBtn::Left)] = (btnState & SDL_BUTTON_LMASK) ? 1 : 0;
|
||||||
|
mouseState_[static_cast<size_t>(MouseBtn::Middle)] = (btnState & SDL_BUTTON_MMASK) ? 1 : 0;
|
||||||
|
mouseState_[static_cast<size_t>(MouseBtn::Right)] = (btnState & SDL_BUTTON_RMASK) ? 1 : 0;
|
||||||
|
mouseState_[static_cast<size_t>(MouseBtn::X1)] = (btnState & SDL_BUTTON_X1MASK) ? 1 : 0;
|
||||||
|
mouseState_[static_cast<size_t>(MouseBtn::X2)] = (btnState & SDL_BUTTON_X2MASK) ? 1 : 0;
|
||||||
|
|
||||||
|
for (int32 i = 0; i < MAX_GAMEPADS; ++i) {
|
||||||
|
if (gamepads_[i]) {
|
||||||
|
SDL_GameController* gc = gamepads_[i];
|
||||||
|
padState_[i][static_cast<size_t>(GamepadBtn::A)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_A);
|
||||||
|
padState_[i][static_cast<size_t>(GamepadBtn::B)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_B);
|
||||||
|
padState_[i][static_cast<size_t>(GamepadBtn::X)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_X);
|
||||||
|
padState_[i][static_cast<size_t>(GamepadBtn::Y)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_Y);
|
||||||
|
padState_[i][static_cast<size_t>(GamepadBtn::Back)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_BACK);
|
||||||
|
padState_[i][static_cast<size_t>(GamepadBtn::Guide)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_GUIDE);
|
||||||
|
padState_[i][static_cast<size_t>(GamepadBtn::Start)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_START);
|
||||||
|
padState_[i][static_cast<size_t>(GamepadBtn::LeftStick)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_LEFTSTICK);
|
||||||
|
padState_[i][static_cast<size_t>(GamepadBtn::RightStick)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_RIGHTSTICK);
|
||||||
|
padState_[i][static_cast<size_t>(GamepadBtn::LeftShoulder)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_LEFTSHOULDER);
|
||||||
|
padState_[i][static_cast<size_t>(GamepadBtn::RightShoulder)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER);
|
||||||
|
padState_[i][static_cast<size_t>(GamepadBtn::DPadUp)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_DPAD_UP);
|
||||||
|
padState_[i][static_cast<size_t>(GamepadBtn::DPadDown)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_DPAD_DOWN);
|
||||||
|
padState_[i][static_cast<size_t>(GamepadBtn::DPadLeft)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_DPAD_LEFT);
|
||||||
|
padState_[i][static_cast<size_t>(GamepadBtn::DPadRight)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_DPAD_RIGHT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
endedTouches_.clear();
|
||||||
|
|
||||||
|
for (auto& touch : activeTouches_) {
|
||||||
|
if (touch.state == TouchState::Ended || touch.state == TouchState::Cancelled) {
|
||||||
|
endedTouches_.push_back(touch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
activeTouches_.erase(
|
||||||
|
std::remove_if(activeTouches_.begin(), activeTouches_.end(),
|
||||||
|
[](const TouchPoint& t) {
|
||||||
|
return t.state == TouchState::Ended || t.state == TouchState::Cancelled;
|
||||||
|
}),
|
||||||
|
activeTouches_.end());
|
||||||
|
|
||||||
|
for (auto& touch : activeTouches_) {
|
||||||
|
touch.prevX = touch.x;
|
||||||
|
touch.prevY = touch.y;
|
||||||
|
touch.deltaX = 0.0f;
|
||||||
|
touch.deltaY = 0.0f;
|
||||||
|
if (touch.state == TouchState::Began) {
|
||||||
|
touch.state = TouchState::Moved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputSvc::processEvent(const SDL_Event& evt) {
|
||||||
|
switch (evt.type) {
|
||||||
|
case SDL_KEYDOWN:
|
||||||
|
if (evt.key.repeat == 0 && onKeyDown_) {
|
||||||
|
onKeyDown_(static_cast<Key>(evt.key.keysym.scancode));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SDL_KEYUP:
|
||||||
|
if (onKeyUp_) {
|
||||||
|
onKeyUp_(static_cast<Key>(evt.key.keysym.scancode));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SDL_MOUSEBUTTONDOWN:
|
||||||
|
if (onMouseDown_) {
|
||||||
|
onMouseDown_(static_cast<MouseBtn>(evt.button.button - 1), evt.button.x, evt.button.y);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SDL_MOUSEBUTTONUP:
|
||||||
|
if (onMouseUp_) {
|
||||||
|
onMouseUp_(static_cast<MouseBtn>(evt.button.button - 1), evt.button.x, evt.button.y);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SDL_MOUSEWHEEL:
|
||||||
|
mouseWheel_ = evt.wheel.y;
|
||||||
|
break;
|
||||||
|
case SDL_CONTROLLERDEVICEADDED:
|
||||||
|
openGamepad(evt.cdevice.which);
|
||||||
|
break;
|
||||||
|
case SDL_CONTROLLERDEVICEREMOVED:
|
||||||
|
for (int32 i = 0; i < MAX_GAMEPADS; ++i) {
|
||||||
|
if (gamepads_[i] && SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(gamepads_[i])) == evt.cdevice.which) {
|
||||||
|
closeGamepad(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SDL_FINGERDOWN:
|
||||||
|
processTouchDown(evt.tfinger);
|
||||||
|
break;
|
||||||
|
case SDL_FINGERUP:
|
||||||
|
processTouchUp(evt.tfinger);
|
||||||
|
break;
|
||||||
|
case SDL_FINGERMOTION:
|
||||||
|
processTouchMotion(evt.tfinger);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputSvc::processTouchDown(const SDL_TouchFingerEvent& evt) {
|
||||||
|
if (static_cast<int32>(activeTouches_.size()) >= MAX_TOUCHES) return;
|
||||||
|
|
||||||
|
int32 winW = 0, winH = 0;
|
||||||
|
SDL_GetWindowSize(SDL_GetWindowFromID(evt.windowID), &winW, &winH);
|
||||||
|
|
||||||
|
TouchPoint touch;
|
||||||
|
touch.id = static_cast<int64>(evt.fingerId);
|
||||||
|
touch.x = evt.x * static_cast<float>(winW);
|
||||||
|
touch.y = evt.y * static_cast<float>(winH);
|
||||||
|
touch.prevX = touch.x;
|
||||||
|
touch.prevY = touch.y;
|
||||||
|
touch.deltaX = 0.0f;
|
||||||
|
touch.deltaY = 0.0f;
|
||||||
|
touch.state = TouchState::Began;
|
||||||
|
touch.pressure = evt.pressure;
|
||||||
|
|
||||||
|
activeTouches_.push_back(touch);
|
||||||
|
|
||||||
|
if (onTouchBegan_) {
|
||||||
|
onTouchBegan_(touch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputSvc::processTouchUp(const SDL_TouchFingerEvent& evt) {
|
||||||
|
int64 fingerId = static_cast<int64>(evt.fingerId);
|
||||||
|
|
||||||
|
for (auto& touch : activeTouches_) {
|
||||||
|
if (touch.id == fingerId) {
|
||||||
|
int32 winW = 0, winH = 0;
|
||||||
|
SDL_GetWindowSize(SDL_GetWindowFromID(evt.windowID), &winW, &winH);
|
||||||
|
|
||||||
|
touch.x = evt.x * static_cast<float>(winW);
|
||||||
|
touch.y = evt.y * static_cast<float>(winH);
|
||||||
|
touch.deltaX = touch.x - touch.prevX;
|
||||||
|
touch.deltaY = touch.y - touch.prevY;
|
||||||
|
touch.state = TouchState::Ended;
|
||||||
|
touch.pressure = evt.pressure;
|
||||||
|
|
||||||
|
if (onTouchEnded_) {
|
||||||
|
onTouchEnded_(touch);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputSvc::processTouchMotion(const SDL_TouchFingerEvent& evt) {
|
||||||
|
int64 fingerId = static_cast<int64>(evt.fingerId);
|
||||||
|
|
||||||
|
for (auto& touch : activeTouches_) {
|
||||||
|
if (touch.id == fingerId) {
|
||||||
|
int32 winW = 0, winH = 0;
|
||||||
|
SDL_GetWindowSize(SDL_GetWindowFromID(evt.windowID), &winW, &winH);
|
||||||
|
|
||||||
|
touch.x = evt.x * static_cast<float>(winW);
|
||||||
|
touch.y = evt.y * static_cast<float>(winH);
|
||||||
|
touch.deltaX = touch.x - touch.prevX;
|
||||||
|
touch.deltaY = touch.y - touch.prevY;
|
||||||
|
touch.state = TouchState::Moved;
|
||||||
|
touch.pressure = evt.pressure;
|
||||||
|
|
||||||
|
if (onTouchMoved_) {
|
||||||
|
onTouchMoved_(touch);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputSvc::openGamepad(int32 idx) {
|
||||||
|
if (idx < 0 || idx >= MAX_GAMEPADS) return;
|
||||||
|
if (gamepads_[idx]) return;
|
||||||
|
|
||||||
|
SDL_GameController* gc = SDL_GameControllerOpen(idx);
|
||||||
|
if (gc) {
|
||||||
|
gamepads_[idx] = gc;
|
||||||
|
std::memset(padState_[idx].data(), 0, static_cast<size_t>(GamepadBtn::Count));
|
||||||
|
std::memset(padPrev_[idx].data(), 0, static_cast<size_t>(GamepadBtn::Count));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputSvc::closeGamepad(int32 idx) {
|
||||||
|
if (idx < 0 || idx >= MAX_GAMEPADS) return;
|
||||||
|
if (gamepads_[idx]) {
|
||||||
|
SDL_GameControllerClose(gamepads_[idx]);
|
||||||
|
gamepads_[idx] = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InputSvc::isKeyDown(Key key) const {
|
||||||
|
if (key < 0 || key >= KEY_COUNT) return false;
|
||||||
|
return keyState_[key] != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InputSvc::isKeyPressed(Key key) const {
|
||||||
|
if (key < 0 || key >= KEY_COUNT) return false;
|
||||||
|
return keyState_[key] != 0 && keyPrev_[key] == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InputSvc::isKeyReleased(Key key) const {
|
||||||
|
if (key < 0 || key >= KEY_COUNT) return false;
|
||||||
|
return keyState_[key] == 0 && keyPrev_[key] != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputSvc::getMousePos(int32& x, int32& y) const {
|
||||||
|
x = mouseX_;
|
||||||
|
y = mouseY_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputSvc::getMousePos(float& x, float& y) const {
|
||||||
|
x = static_cast<float>(mouseX_);
|
||||||
|
y = static_cast<float>(mouseY_);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InputSvc::isMouseBtnDown(MouseBtn btn) const {
|
||||||
|
size_t idx = static_cast<size_t>(btn);
|
||||||
|
if (idx >= static_cast<size_t>(MouseBtn::Count)) return false;
|
||||||
|
return mouseState_[idx] != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InputSvc::isMouseBtnPressed(MouseBtn btn) const {
|
||||||
|
size_t idx = static_cast<size_t>(btn);
|
||||||
|
if (idx >= static_cast<size_t>(MouseBtn::Count)) return false;
|
||||||
|
return mouseState_[idx] != 0 && mousePrev_[idx] == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InputSvc::isMouseBtnReleased(MouseBtn btn) const {
|
||||||
|
size_t idx = static_cast<size_t>(btn);
|
||||||
|
if (idx >= static_cast<size_t>(MouseBtn::Count)) return false;
|
||||||
|
return mouseState_[idx] == 0 && mousePrev_[idx] != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 InputSvc::getMouseWheel() const {
|
||||||
|
return mouseWheel_;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 InputSvc::touchCount() const {
|
||||||
|
return static_cast<int32>(activeTouches_.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
const TouchPoint* InputSvc::getTouch(int32 idx) const {
|
||||||
|
if (idx < 0 || idx >= static_cast<int32>(activeTouches_.size())) return nullptr;
|
||||||
|
return &activeTouches_[idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
const TouchPoint* InputSvc::getTouchById(int64 id) const {
|
||||||
|
for (const auto& touch : activeTouches_) {
|
||||||
|
if (touch.id == id) return &touch;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 InputSvc::gamepadCount() const {
|
||||||
|
int32 count = 0;
|
||||||
|
for (int32 i = 0; i < MAX_GAMEPADS; ++i) {
|
||||||
|
if (gamepads_[i]) ++count;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InputSvc::isGamepadBtnDown(int32 idx, GamepadBtn btn) const {
|
||||||
|
if (idx < 0 || idx >= MAX_GAMEPADS) return false;
|
||||||
|
if (!gamepads_[idx]) return false;
|
||||||
|
size_t b = static_cast<size_t>(btn);
|
||||||
|
if (b >= static_cast<size_t>(GamepadBtn::Count)) return false;
|
||||||
|
return padState_[idx][b] != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InputSvc::isGamepadBtnPressed(int32 idx, GamepadBtn btn) const {
|
||||||
|
if (idx < 0 || idx >= MAX_GAMEPADS) return false;
|
||||||
|
if (!gamepads_[idx]) return false;
|
||||||
|
size_t b = static_cast<size_t>(btn);
|
||||||
|
if (b >= static_cast<size_t>(GamepadBtn::Count)) return false;
|
||||||
|
return padState_[idx][b] != 0 && padPrev_[idx][b] == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
float InputSvc::getGamepadAxis(int32 idx, GamepadAxis axis) const {
|
||||||
|
if (idx < 0 || idx >= MAX_GAMEPADS) return 0.0f;
|
||||||
|
if (!gamepads_[idx]) return 0.0f;
|
||||||
|
|
||||||
|
SDL_GameControllerAxis sdlAxis = SDL_CONTROLLER_AXIS_INVALID;
|
||||||
|
switch (axis) {
|
||||||
|
case GamepadAxis::LeftX: sdlAxis = SDL_CONTROLLER_AXIS_LEFTX; break;
|
||||||
|
case GamepadAxis::LeftY: sdlAxis = SDL_CONTROLLER_AXIS_LEFTY; break;
|
||||||
|
case GamepadAxis::RightX: sdlAxis = SDL_CONTROLLER_AXIS_RIGHTX; break;
|
||||||
|
case GamepadAxis::RightY: sdlAxis = SDL_CONTROLLER_AXIS_RIGHTY; break;
|
||||||
|
case GamepadAxis::TriggerLeft: sdlAxis = SDL_CONTROLLER_AXIS_TRIGGERLEFT; break;
|
||||||
|
case GamepadAxis::TriggerRight: sdlAxis = SDL_CONTROLLER_AXIS_TRIGGERRIGHT; break;
|
||||||
|
default: return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
int16 value = SDL_GameControllerGetAxis(gamepads_[idx], sdlAxis);
|
||||||
|
return static_cast<float>(value) / 32767.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
#include <platform/sdl2.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
bool Sdl2::inited_ = false;
|
||||||
|
|
||||||
|
bool Sdl2::initCore() {
|
||||||
|
if (inited_) return true;
|
||||||
|
|
||||||
|
if (SDL_Init(SDL_INIT_EVENTS) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
inited_ = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Sdl2::initVideo() {
|
||||||
|
if (!initCore()) return false;
|
||||||
|
|
||||||
|
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Sdl2::initAudio() {
|
||||||
|
if (!initCore()) return false;
|
||||||
|
|
||||||
|
if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Sdl2::initGamepad() {
|
||||||
|
if (!initCore()) return false;
|
||||||
|
|
||||||
|
if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Sdl2::initAll() {
|
||||||
|
if (inited_) return true;
|
||||||
|
|
||||||
|
if (SDL_Init(SDL_INIT_EVERYTHING) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
inited_ = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sdl2::shutdown() {
|
||||||
|
if (inited_) {
|
||||||
|
SDL_Quit();
|
||||||
|
inited_ = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,177 @@
|
||||||
|
#include <platform/window.h>
|
||||||
|
#include <platform/input.h>
|
||||||
|
#include <platform/sdl2.h>
|
||||||
|
#include <SDL.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
WindowSvc& WindowSvc::inst() {
|
||||||
|
static WindowSvc instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowSvc::init() {
|
||||||
|
if (isInited()) return true;
|
||||||
|
|
||||||
|
if (!Sdl2::initVideo()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
state_ = SvcState::Inited;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowSvc::shutdown() {
|
||||||
|
if (glCtx_) {
|
||||||
|
SDL_GL_DeleteContext(glCtx_);
|
||||||
|
glCtx_ = nullptr;
|
||||||
|
}
|
||||||
|
if (window_) {
|
||||||
|
SDL_DestroyWindow(window_);
|
||||||
|
window_ = nullptr;
|
||||||
|
}
|
||||||
|
state_ = SvcState::Shutdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowSvc::create(const WindowCfg& cfg) {
|
||||||
|
if (window_) return true;
|
||||||
|
|
||||||
|
uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN;
|
||||||
|
if (cfg.resizable) {
|
||||||
|
flags |= SDL_WINDOW_RESIZABLE;
|
||||||
|
}
|
||||||
|
if (cfg.fullscreen) {
|
||||||
|
flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, cfg.glMajor);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, cfg.glMinor);
|
||||||
|
|
||||||
|
window_ = SDL_CreateWindow(
|
||||||
|
cfg.title.c_str(),
|
||||||
|
SDL_WINDOWPOS_CENTERED,
|
||||||
|
SDL_WINDOWPOS_CENTERED,
|
||||||
|
cfg.width,
|
||||||
|
cfg.height,
|
||||||
|
flags
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!window_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
glCtx_ = SDL_GL_CreateContext(window_);
|
||||||
|
if (!glCtx_) {
|
||||||
|
SDL_DestroyWindow(window_);
|
||||||
|
window_ = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
setVsync(cfg.vsync);
|
||||||
|
vsync_ = cfg.vsync;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowSvc::pollEvents() {
|
||||||
|
SDL_Event evt;
|
||||||
|
while (SDL_PollEvent(&evt)) {
|
||||||
|
switch (evt.type) {
|
||||||
|
case SDL_QUIT:
|
||||||
|
shouldClose_ = true;
|
||||||
|
if (onClose_) onClose_();
|
||||||
|
break;
|
||||||
|
case SDL_WINDOWEVENT:
|
||||||
|
if (evt.window.event == SDL_WINDOWEVENT_RESIZED) {
|
||||||
|
if (onResize_) {
|
||||||
|
onResize_(evt.window.data1, evt.window.data2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (INPUT_SVC.isInited()) {
|
||||||
|
INPUT_SVC.processEvent(evt);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return !shouldClose_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowSvc::swapBuffers() {
|
||||||
|
if (window_ && glCtx_) {
|
||||||
|
SDL_GL_SwapWindow(window_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Size WindowSvc::getSize() const {
|
||||||
|
if (!window_) return Size(0, 0);
|
||||||
|
int w, h;
|
||||||
|
SDL_GetWindowSize(window_, &w, &h);
|
||||||
|
return Size(w, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2 WindowSvc::getPosition() const {
|
||||||
|
if (!window_) return Vec2(0, 0);
|
||||||
|
int x, y;
|
||||||
|
SDL_GetWindowPosition(window_, &x, &y);
|
||||||
|
return Vec2(static_cast<float>(x), static_cast<float>(y));
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowSvc::setSize(int32 w, int32 h) {
|
||||||
|
if (window_) {
|
||||||
|
SDL_SetWindowSize(window_, w, h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowSvc::setTitle(const std::string& title) {
|
||||||
|
if (window_) {
|
||||||
|
SDL_SetWindowTitle(window_, title.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowSvc::setFullscreen(bool fullscreen) {
|
||||||
|
if (window_) {
|
||||||
|
SDL_SetWindowFullscreen(window_, fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowSvc::isFullscreen() const {
|
||||||
|
if (!window_) return false;
|
||||||
|
uint32 flags = SDL_GetWindowFlags(window_);
|
||||||
|
return (flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowSvc::setVsync(bool vsync) {
|
||||||
|
SDL_GL_SetSwapInterval(vsync ? 1 : 0);
|
||||||
|
vsync_ = vsync;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowSvc::isVsync() const {
|
||||||
|
return vsync_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowSvc::setVisible(bool visible) {
|
||||||
|
if (window_) {
|
||||||
|
if (visible) {
|
||||||
|
SDL_ShowWindow(window_);
|
||||||
|
} else {
|
||||||
|
SDL_HideWindow(window_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowSvc::isVisible() const {
|
||||||
|
if (!window_) return false;
|
||||||
|
uint32 flags = SDL_GetWindowFlags(window_);
|
||||||
|
return (flags & SDL_WINDOW_SHOWN) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -24,7 +24,9 @@ function define_tbb_target()
|
||||||
|
|
||||||
local plat = get_current_plat()
|
local plat = get_current_plat()
|
||||||
if plat == "mingw" then
|
if plat == "mingw" then
|
||||||
add_defines("_UNICODE", "UNICODE")
|
-- TBB uses ANSI Windows APIs, don't define UNICODE
|
||||||
|
-- MinGW doesn't define LOAD_LIBRARY_SAFE_CURRENT_DIRS
|
||||||
|
add_defines("LOAD_LIBRARY_SAFE_CURRENT_DIRS=0x00002000")
|
||||||
elseif plat == "switch" then
|
elseif plat == "switch" then
|
||||||
add_defines("__TBB_USE_THREAD_SANITIZER=0")
|
add_defines("__TBB_USE_THREAD_SANITIZER=0")
|
||||||
add_defines("TBB_USE_ASSERT=0")
|
add_defines("TBB_USE_ASSERT=0")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue