394 lines
9.9 KiB
C++
394 lines
9.9 KiB
C++
#include <platform/input.h>
|
||
#include <platform/window.h>
|
||
#include <utils/logger.h>
|
||
|
||
#include <SDL.h>
|
||
|
||
#include <glad/glad.h>
|
||
|
||
namespace extra2d {
|
||
|
||
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) {
|
||
// 初始化光标数组
|
||
for (int i = 0; i < 9; ++i) {
|
||
sdlCursors_[i] = nullptr;
|
||
}
|
||
}
|
||
|
||
Window::~Window() { destroy(); }
|
||
|
||
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;
|
||
|
||
// 初始化 SDL2 + 创建窗口 + GL 上下文
|
||
if (!initSDL(config)) {
|
||
E2D_LOG_ERROR("Failed to initialize SDL2");
|
||
return false;
|
||
}
|
||
|
||
// 创建输入管理器
|
||
input_ = unique<Input>();
|
||
input_->init();
|
||
|
||
// 初始化光标
|
||
if (config.enableCursors) {
|
||
initCursors();
|
||
}
|
||
|
||
E2D_LOG_INFO("Window created: {}x{}", width_, height_);
|
||
return true;
|
||
}
|
||
|
||
bool Window::initSDL(const WindowConfig &config) {
|
||
// SDL2 全局初始化(视频 + 游戏控制器 + 音频)
|
||
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER | SDL_INIT_AUDIO) !=
|
||
0) {
|
||
E2D_LOG_ERROR("SDL_Init failed: {}", SDL_GetError());
|
||
return false;
|
||
}
|
||
|
||
// 设置 OpenGL ES 3.2 上下文属性
|
||
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);
|
||
|
||
// 创建 SDL2 窗口
|
||
Uint32 windowFlags = SDL_WINDOW_OPENGL;
|
||
|
||
// 根据配置设置窗口模式
|
||
if (config.fullscreen) {
|
||
// Switch 平台使用 SDL_WINDOW_FULLSCREEN(固定分辨率)
|
||
// PC 平台使用 SDL_WINDOW_FULLSCREEN_DESKTOP(桌面全屏)
|
||
if (config.fullscreenDesktop) {
|
||
windowFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||
} else {
|
||
windowFlags |= SDL_WINDOW_FULLSCREEN;
|
||
}
|
||
} else {
|
||
if (config.resizable) {
|
||
windowFlags |= SDL_WINDOW_RESIZABLE;
|
||
}
|
||
// 注意:SDL_WINDOWPOS_CENTERED 是位置参数,不是窗口标志
|
||
// 窗口居中在 SDL_CreateWindow 的位置参数中处理
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
// 创建 OpenGL ES 上下文
|
||
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;
|
||
}
|
||
|
||
// 加载 OpenGL ES 函数指针
|
||
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;
|
||
}
|
||
|
||
// 设置 VSync
|
||
SDL_GL_SetSwapInterval(vsync_ ? 1 : 0);
|
||
|
||
// 更新 DPI 缩放
|
||
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;
|
||
}
|
||
|
||
void Window::deinitSDL() {
|
||
deinitCursors();
|
||
|
||
if (glContext_) {
|
||
SDL_GL_DeleteContext(glContext_);
|
||
glContext_ = nullptr;
|
||
}
|
||
|
||
if (sdlWindow_) {
|
||
SDL_DestroyWindow(sdlWindow_);
|
||
sdlWindow_ = nullptr;
|
||
}
|
||
|
||
SDL_Quit();
|
||
}
|
||
|
||
void Window::destroy() {
|
||
if (sdlWindow_ != nullptr) {
|
||
input_.reset();
|
||
deinitSDL();
|
||
E2D_LOG_INFO("Window destroyed");
|
||
}
|
||
}
|
||
|
||
void Window::pollEvents() {
|
||
// SDL2 事件循环
|
||
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;
|
||
|
||
case SDL_CONTROLLERDEVICEADDED:
|
||
if (input_) {
|
||
input_->onControllerAdded(event.cdevice.which);
|
||
}
|
||
break;
|
||
|
||
case SDL_CONTROLLERDEVICEREMOVED:
|
||
if (input_) {
|
||
input_->onControllerRemoved(event.cdevice.which);
|
||
}
|
||
break;
|
||
|
||
case SDL_CONTROLLERDEVICEREMAPPED:
|
||
E2D_LOG_INFO("Controller device remapped");
|
||
break;
|
||
|
||
case SDL_MOUSEWHEEL:
|
||
if (input_) {
|
||
input_->onMouseWheel(event.wheel.x, event.wheel.y);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
// 输入更新
|
||
if (input_) {
|
||
input_->update();
|
||
}
|
||
}
|
||
|
||
void Window::swapBuffers() {
|
||
if (sdlWindow_) {
|
||
SDL_GL_SwapWindow(sdlWindow_);
|
||
}
|
||
}
|
||
|
||
bool Window::shouldClose() const { return shouldClose_; }
|
||
|
||
void Window::setShouldClose(bool close) { shouldClose_ = close; }
|
||
|
||
void Window::setTitle(const std::string &title) {
|
||
if (sdlWindow_) {
|
||
SDL_SetWindowTitle(sdlWindow_, title.c_str());
|
||
}
|
||
}
|
||
|
||
void Window::setSize(int width, int height) {
|
||
if (sdlWindow_) {
|
||
SDL_SetWindowSize(sdlWindow_, width, height);
|
||
width_ = width;
|
||
height_ = height;
|
||
}
|
||
}
|
||
|
||
void Window::setPosition(int x, int y) {
|
||
if (sdlWindow_) {
|
||
SDL_SetWindowPosition(sdlWindow_, x, y);
|
||
}
|
||
}
|
||
|
||
void Window::setFullscreen(bool fullscreen) {
|
||
if (sdlWindow_) {
|
||
// 默认使用桌面全屏模式(PC 平台)
|
||
Uint32 flags = fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0;
|
||
SDL_SetWindowFullscreen(sdlWindow_, flags);
|
||
fullscreen_ = fullscreen;
|
||
}
|
||
}
|
||
|
||
void Window::setVSync(bool enabled) {
|
||
vsync_ = enabled;
|
||
SDL_GL_SetSwapInterval(enabled ? 1 : 0);
|
||
}
|
||
|
||
void Window::setResizable(bool resizable) {
|
||
if (sdlWindow_) {
|
||
SDL_SetWindowResizable(sdlWindow_, resizable ? SDL_TRUE : SDL_FALSE);
|
||
}
|
||
}
|
||
|
||
Vec2 Window::pos() const {
|
||
if (sdlWindow_) {
|
||
int x, y;
|
||
SDL_GetWindowPosition(sdlWindow_, &x, &y);
|
||
return Vec2(static_cast<float>(x), static_cast<float>(y));
|
||
}
|
||
return Vec2::Zero();
|
||
}
|
||
|
||
float Window::getContentScaleX() const {
|
||
return enableDpiScale_ ? contentScaleX_ : 1.0f;
|
||
}
|
||
|
||
float Window::getContentScaleY() const {
|
||
return enableDpiScale_ ? contentScaleY_ : 1.0f;
|
||
}
|
||
|
||
Vec2 Window::getContentScale() const {
|
||
return Vec2(getContentScaleX(), getContentScaleY());
|
||
}
|
||
|
||
bool Window::isMinimized() const {
|
||
if (sdlWindow_) {
|
||
Uint32 flags = SDL_GetWindowFlags(sdlWindow_);
|
||
return (flags & SDL_WINDOW_MINIMIZED) != 0;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
bool Window::isMaximized() const {
|
||
if (sdlWindow_) {
|
||
Uint32 flags = SDL_GetWindowFlags(sdlWindow_);
|
||
return (flags & SDL_WINDOW_MAXIMIZED) != 0;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
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);
|
||
}
|
||
|
||
void Window::deinitCursors() {
|
||
for (int i = 0; i < 9; ++i) {
|
||
if (sdlCursors_[i]) {
|
||
SDL_FreeCursor(sdlCursors_[i]);
|
||
sdlCursors_[i] = nullptr;
|
||
}
|
||
}
|
||
currentCursor_ = nullptr;
|
||
}
|
||
|
||
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];
|
||
}
|
||
}
|
||
|
||
void Window::resetCursor() {
|
||
SDL_SetCursor(SDL_GetDefaultCursor());
|
||
currentCursor_ = nullptr;
|
||
}
|
||
|
||
void Window::setMouseVisible(bool visible) {
|
||
SDL_ShowCursor(visible ? SDL_ENABLE : SDL_DISABLE);
|
||
}
|
||
|
||
void Window::updateContentScale() {
|
||
if (sdlWindow_) {
|
||
// 使用 DPI 计算内容缩放比例
|
||
int displayIndex = SDL_GetWindowDisplayIndex(sdlWindow_);
|
||
if (displayIndex >= 0) {
|
||
float ddpi, hdpi, vdpi;
|
||
if (SDL_GetDisplayDPI(displayIndex, &ddpi, &hdpi, &vdpi) == 0) {
|
||
// 假设标准 DPI 为 96
|
||
contentScaleX_ = hdpi / 96.0f;
|
||
contentScaleY_ = vdpi / 96.0f;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
} // namespace extra2d
|