feat(窗口/事件): 添加窗口事件处理并统一键盘映射

为 SDL2 和 GLFW 窗口后端添加完整的事件处理,包括窗口关闭、大小调整、键盘、鼠标等事件
添加键盘扫描码到引擎 Key 枚举的映射功能
优化相机服务,支持通过 Lambda 配置视口
统一日志输出语言为英文
This commit is contained in:
ChestnutYueyue 2026-02-19 01:23:39 +08:00
parent e9bd44b63e
commit b794221d49
8 changed files with 426 additions and 33 deletions

View File

@ -4,6 +4,7 @@
#include <extra2d/core/service_locator.h>
#include <extra2d/graphics/camera/camera.h>
#include <extra2d/graphics/camera/viewport_adapter.h>
#include <functional>
namespace extra2d {
@ -43,6 +44,13 @@ public:
virtual void lookAt(const Vec2& target) = 0;
virtual void setViewportConfig(const ViewportConfig& config) = 0;
/**
* @brief 使 Lambda
* @param configFn ViewportConfig
*/
virtual void setViewportConfig(std::function<void(ViewportConfig&)> configFn) = 0;
virtual const ViewportConfig& getViewportConfig() const = 0;
virtual void updateViewport(int screenWidth, int screenHeight) = 0;
virtual const ViewportResult& getViewportResult() const = 0;
@ -93,6 +101,7 @@ public:
void lookAt(const Vec2& target) override;
void setViewportConfig(const ViewportConfig& config) override;
void setViewportConfig(std::function<void(ViewportConfig&)> configFn) override;
const ViewportConfig& getViewportConfig() const override;
void updateViewport(int screenWidth, int screenHeight) override;
const ViewportResult& getViewportResult() const override;

View File

@ -4,7 +4,6 @@
#include <extra2d/services/logger_service.h>
#include <filesystem>
namespace extra2d {
namespace fs = std::filesystem;
@ -112,7 +111,7 @@ void ShaderHotReloader::update() {
*/
void ShaderHotReloader::setEnabled(bool enabled) {
enabled_ = enabled;
E2D_LOG_DEBUG("热重载已{}", enabled ? "启用" : "禁用");
E2D_LOG_DEBUG("热重载已{}", enabled ? "Enable" : "Disable");
}
/**

View File

@ -98,8 +98,7 @@ bool ShaderManager::init(const std::string &shaderDir,
E2D_LOG_INFO("ShaderManager 初始化成功");
E2D_LOG_INFO(" 着色器目录: {}", shaderDir_);
E2D_LOG_INFO(" 缓存目录: {}", cacheDir_);
E2D_LOG_INFO(" 热重载: {}",
hotReloadSupported_ ? "支持" : "不支持");
E2D_LOG_INFO(" 热重载: {}", hotReloadSupported_ ? "支持" : "不支持");
return true;
}
@ -157,8 +156,7 @@ Ptr<IShader> ShaderManager::loadFromFiles(const std::string &name,
loadFromCache(name, sourceHash, result.vertSource, result.fragSource);
if (!shader) {
E2D_LOG_DEBUG("未找到有效缓存,从源码编译着色器: {}",
name);
E2D_LOG_DEBUG("未找到有效缓存,从源码编译着色器: {}", name);
shader =
factory_->createFromSource(name, result.vertSource, result.fragSource);
if (!shader) {
@ -394,7 +392,7 @@ void ShaderManager::setHotReloadEnabled(bool enabled) {
}
hotReloadEnabled_ = enabled;
ShaderHotReloader::getInstance().setEnabled(enabled);
E2D_LOG_INFO("热重载已{}", enabled ? "启用" : "禁用");
E2D_LOG_INFO("热重载已{}", enabled ? "Enable" : "Disable");
}
/**
@ -591,8 +589,7 @@ Ptr<IShader> ShaderManager::loadFromMetadata(const std::string &jsonPath,
auto &opengl = j["backends"]["opengl"];
if (!opengl.contains("vertex") || !opengl.contains("fragment")) {
E2D_LOG_ERROR("着色器元数据中缺少顶点或片段路径: {}",
jsonPath);
E2D_LOG_ERROR("着色器元数据中缺少顶点或片段路径: {}", jsonPath);
return nullptr;
}
@ -603,8 +600,8 @@ Ptr<IShader> ShaderManager::loadFromMetadata(const std::string &jsonPath,
fs::path vertPath = fs::path(shaderDir_) / vertRelativePath;
fs::path fragPath = fs::path(shaderDir_) / fragRelativePath;
E2D_LOG_DEBUG("从元数据加载着色器: {} -> 顶点: {}, 片段: {}",
name, vertPath.string(), fragPath.string());
E2D_LOG_DEBUG("从元数据加载着色器: {} -> 顶点: {}, 片段: {}", name,
vertPath.string(), fragPath.string());
// 使用分离文件加载
Ptr<IShader> shader =

View File

@ -1,6 +1,9 @@
#include "glfw_window.h"
#include "glfw_input.h"
#include <extra2d/core/service_locator.h>
#include <extra2d/event/event.h>
#include <extra2d/platform/keys.h>
#include <extra2d/services/event_service.h>
#include <extra2d/services/logger_service.h>
#include <glad/glad.h>
@ -362,6 +365,13 @@ void GLFWWindow::framebufferSizeCallback(GLFWwindow *window, int width,
self->resizeCb_(width, height);
}
}
// 将事件推送到事件服务
auto eventService = ServiceLocator::instance().getService<IEventService>();
if (eventService) {
Event e = Event::createWindowResize(width, height);
eventService->pushEvent(e);
}
}
void GLFWWindow::windowCloseCallback(GLFWwindow *window) {
@ -373,6 +383,13 @@ void GLFWWindow::windowCloseCallback(GLFWwindow *window) {
self->closeCb_();
}
}
// 将事件推送到事件服务
auto eventService = ServiceLocator::instance().getService<IEventService>();
if (eventService) {
Event e = Event::createWindowClose();
eventService->pushEvent(e);
}
}
void GLFWWindow::windowFocusCallback(GLFWwindow *window, int focused) {
@ -401,6 +418,15 @@ void GLFWWindow::cursorPosCallback(GLFWwindow *window, double xpos,
if (self && self->input_) {
self->input_->handleCursorPosEvent(xpos, ypos);
}
// 将事件推送到事件服务
auto eventService = ServiceLocator::instance().getService<IEventService>();
if (eventService) {
Vec2 pos{static_cast<float>(xpos), static_cast<float>(ypos)};
Vec2 delta{0.0f, 0.0f}; // GLFW 回调中没有增量,需要在其他地方计算
Event e = Event::createMouseMove(pos, delta);
eventService->pushEvent(e);
}
}
void GLFWWindow::mouseButtonCallback(GLFWwindow *window, int button, int action,
@ -410,6 +436,22 @@ void GLFWWindow::mouseButtonCallback(GLFWwindow *window, int button, int action,
if (self && self->input_) {
self->input_->handleMouseButtonEvent(button, action, mods);
}
// 将事件推送到事件服务
auto eventService = ServiceLocator::instance().getService<IEventService>();
if (eventService) {
double x, y;
glfwGetCursorPos(window, &x, &y);
Vec2 pos{static_cast<float>(x), static_cast<float>(y)};
if (action == GLFW_PRESS) {
Event e = Event::createMouseButtonPress(button, mods, pos);
eventService->pushEvent(e);
} else if (action == GLFW_RELEASE) {
Event e = Event::createMouseButtonRelease(button, mods, pos);
eventService->pushEvent(e);
}
}
}
void GLFWWindow::scrollCallback(GLFWwindow *window, double xoffset,
@ -419,6 +461,17 @@ void GLFWWindow::scrollCallback(GLFWwindow *window, double xoffset,
if (self && self->input_) {
self->input_->handleScrollEvent(xoffset, yoffset);
}
// 将事件推送到事件服务
auto eventService = ServiceLocator::instance().getService<IEventService>();
if (eventService) {
double x, y;
glfwGetCursorPos(window, &x, &y);
Vec2 offset{static_cast<float>(xoffset), static_cast<float>(yoffset)};
Vec2 pos{static_cast<float>(x), static_cast<float>(y)};
Event e = Event::createMouseScroll(offset, pos);
eventService->pushEvent(e);
}
}
void GLFWWindow::keyCallback(GLFWwindow *window, int key, int scancode,
@ -428,6 +481,99 @@ void GLFWWindow::keyCallback(GLFWwindow *window, int key, int scancode,
if (self && self->input_) {
self->input_->handleKeyEvent(key, scancode, action, mods);
}
// 将事件推送到事件服务
auto eventService = ServiceLocator::instance().getService<IEventService>();
if (eventService) {
// 将 GLFW key code 转换为引擎 Key 枚举值
Key eKey = Key::None;
switch (key) {
case GLFW_KEY_A: eKey = Key::A; break;
case GLFW_KEY_B: eKey = Key::B; break;
case GLFW_KEY_C: eKey = Key::C; break;
case GLFW_KEY_D: eKey = Key::D; break;
case GLFW_KEY_E: eKey = Key::E; break;
case GLFW_KEY_F: eKey = Key::F; break;
case GLFW_KEY_G: eKey = Key::G; break;
case GLFW_KEY_H: eKey = Key::H; break;
case GLFW_KEY_I: eKey = Key::I; break;
case GLFW_KEY_J: eKey = Key::J; break;
case GLFW_KEY_K: eKey = Key::K; break;
case GLFW_KEY_L: eKey = Key::L; break;
case GLFW_KEY_M: eKey = Key::M; break;
case GLFW_KEY_N: eKey = Key::N; break;
case GLFW_KEY_O: eKey = Key::O; break;
case GLFW_KEY_P: eKey = Key::P; break;
case GLFW_KEY_Q: eKey = Key::Q; break;
case GLFW_KEY_R: eKey = Key::R; break;
case GLFW_KEY_S: eKey = Key::S; break;
case GLFW_KEY_T: eKey = Key::T; break;
case GLFW_KEY_U: eKey = Key::U; break;
case GLFW_KEY_V: eKey = Key::V; break;
case GLFW_KEY_W: eKey = Key::W; break;
case GLFW_KEY_X: eKey = Key::X; break;
case GLFW_KEY_Y: eKey = Key::Y; break;
case GLFW_KEY_Z: eKey = Key::Z; break;
case GLFW_KEY_0: eKey = Key::Num0; break;
case GLFW_KEY_1: eKey = Key::Num1; break;
case GLFW_KEY_2: eKey = Key::Num2; break;
case GLFW_KEY_3: eKey = Key::Num3; break;
case GLFW_KEY_4: eKey = Key::Num4; break;
case GLFW_KEY_5: eKey = Key::Num5; break;
case GLFW_KEY_6: eKey = Key::Num6; break;
case GLFW_KEY_7: eKey = Key::Num7; break;
case GLFW_KEY_8: eKey = Key::Num8; break;
case GLFW_KEY_9: eKey = Key::Num9; break;
case GLFW_KEY_F1: eKey = Key::F1; break;
case GLFW_KEY_F2: eKey = Key::F2; break;
case GLFW_KEY_F3: eKey = Key::F3; break;
case GLFW_KEY_F4: eKey = Key::F4; break;
case GLFW_KEY_F5: eKey = Key::F5; break;
case GLFW_KEY_F6: eKey = Key::F6; break;
case GLFW_KEY_F7: eKey = Key::F7; break;
case GLFW_KEY_F8: eKey = Key::F8; break;
case GLFW_KEY_F9: eKey = Key::F9; break;
case GLFW_KEY_F10: eKey = Key::F10; break;
case GLFW_KEY_F11: eKey = Key::F11; break;
case GLFW_KEY_F12: eKey = Key::F12; break;
case GLFW_KEY_SPACE: eKey = Key::Space; break;
case GLFW_KEY_ENTER: eKey = Key::Enter; break;
case GLFW_KEY_ESCAPE: eKey = Key::Escape; break;
case GLFW_KEY_TAB: eKey = Key::Tab; break;
case GLFW_KEY_BACKSPACE: eKey = Key::Backspace; break;
case GLFW_KEY_INSERT: eKey = Key::Insert; break;
case GLFW_KEY_DELETE: eKey = Key::Delete; break;
case GLFW_KEY_HOME: eKey = Key::Home; break;
case GLFW_KEY_END: eKey = Key::End; break;
case GLFW_KEY_PAGE_UP: eKey = Key::PageUp; break;
case GLFW_KEY_PAGE_DOWN: eKey = Key::PageDown; break;
case GLFW_KEY_UP: eKey = Key::Up; break;
case GLFW_KEY_DOWN: eKey = Key::Down; break;
case GLFW_KEY_LEFT: eKey = Key::Left; break;
case GLFW_KEY_RIGHT: eKey = Key::Right; break;
case GLFW_KEY_LEFT_SHIFT: eKey = Key::LShift; break;
case GLFW_KEY_RIGHT_SHIFT: eKey = Key::RShift; break;
case GLFW_KEY_LEFT_CONTROL: eKey = Key::LCtrl; break;
case GLFW_KEY_RIGHT_CONTROL: eKey = Key::RCtrl; break;
case GLFW_KEY_LEFT_ALT: eKey = Key::LAlt; break;
case GLFW_KEY_RIGHT_ALT: eKey = Key::RAlt; break;
case GLFW_KEY_CAPS_LOCK: eKey = Key::CapsLock; break;
case GLFW_KEY_NUM_LOCK: eKey = Key::NumLock; break;
case GLFW_KEY_SCROLL_LOCK: eKey = Key::ScrollLock; break;
default: eKey = Key::None; break;
}
if (eKey != Key::None) {
int keyCode = static_cast<int>(eKey);
if (action == GLFW_PRESS) {
Event e = Event::createKeyPress(keyCode, scancode, mods);
eventService->pushEvent(e);
} else if (action == GLFW_RELEASE) {
Event e = Event::createKeyRelease(keyCode, scancode, mods);
eventService->pushEvent(e);
}
}
}
}
void GLFWWindow::joystickCallback(int jid, int event) {

View File

@ -1,6 +1,9 @@
#include "sdl2_window.h"
#include "sdl2_input.h"
#include <extra2d/core/service_locator.h>
#include <extra2d/event/event.h>
#include <extra2d/platform/keys.h>
#include <extra2d/services/event_service.h>
#include <extra2d/services/logger_service.h>
#include <glad/glad.h>
@ -322,11 +325,18 @@ void SDL2Window::handleEvent(const SDL_Event &event) {
input_->handleSDLEvent(event);
}
// 将事件推送到事件服务
auto eventService = ServiceLocator::instance().getService<IEventService>();
switch (event.type) {
case SDL_QUIT:
shouldClose_ = true;
if (closeCb_)
closeCb_();
if (eventService) {
Event e = Event::createWindowClose();
eventService->pushEvent(e);
}
break;
case SDL_WINDOWEVENT:
@ -338,6 +348,10 @@ void SDL2Window::handleEvent(const SDL_Event &event) {
updateContentScale();
if (resizeCb_)
resizeCb_(width_, height_);
if (eventService) {
Event e = Event::createWindowResize(width_, height_);
eventService->pushEvent(e);
}
break;
case SDL_WINDOWEVENT_FOCUS_GAINED:
@ -364,9 +378,237 @@ void SDL2Window::handleEvent(const SDL_Event &event) {
shouldClose_ = true;
if (closeCb_)
closeCb_();
if (eventService) {
Event e = Event::createWindowClose();
eventService->pushEvent(e);
}
break;
}
break;
case SDL_KEYDOWN: {
if (event.key.repeat == 0 && eventService) {
// 将 SDL scancode 转换为 Key 枚举
Key key = sdlScancodeToKey(event.key.keysym.scancode);
if (key != Key::None) {
int keyCode = static_cast<int>(key);
Event e = Event::createKeyPress(keyCode, event.key.keysym.scancode,
event.key.keysym.mod);
eventService->pushEvent(e);
}
}
break;
}
case SDL_KEYUP: {
if (eventService) {
Key key = sdlScancodeToKey(event.key.keysym.scancode);
if (key != Key::None) {
int keyCode = static_cast<int>(key);
Event e = Event::createKeyRelease(keyCode, event.key.keysym.scancode,
event.key.keysym.mod);
eventService->pushEvent(e);
}
}
break;
}
case SDL_MOUSEBUTTONDOWN: {
if (eventService) {
Vec2 pos{static_cast<float>(event.button.x),
static_cast<float>(event.button.y)};
Event e = Event::createMouseButtonPress(event.button.button - 1, 0, pos);
eventService->pushEvent(e);
}
break;
}
case SDL_MOUSEBUTTONUP: {
if (eventService) {
Vec2 pos{static_cast<float>(event.button.x),
static_cast<float>(event.button.y)};
Event e =
Event::createMouseButtonRelease(event.button.button - 1, 0, pos);
eventService->pushEvent(e);
}
break;
}
case SDL_MOUSEMOTION: {
if (eventService) {
Vec2 pos{static_cast<float>(event.motion.x),
static_cast<float>(event.motion.y)};
Vec2 delta{static_cast<float>(event.motion.xrel),
static_cast<float>(event.motion.yrel)};
Event e = Event::createMouseMove(pos, delta);
eventService->pushEvent(e);
}
break;
}
case SDL_MOUSEWHEEL: {
if (eventService) {
int x, y;
SDL_GetMouseState(&x, &y);
Vec2 offset{static_cast<float>(event.wheel.x),
static_cast<float>(event.wheel.y)};
Vec2 pos{static_cast<float>(x), static_cast<float>(y)};
Event e = Event::createMouseScroll(offset, pos);
eventService->pushEvent(e);
}
break;
}
}
}
Key SDL2Window::sdlScancodeToKey(int scancode) {
switch (scancode) {
case SDL_SCANCODE_A:
return Key::A;
case SDL_SCANCODE_B:
return Key::B;
case SDL_SCANCODE_C:
return Key::C;
case SDL_SCANCODE_D:
return Key::D;
case SDL_SCANCODE_E:
return Key::E;
case SDL_SCANCODE_F:
return Key::F;
case SDL_SCANCODE_G:
return Key::G;
case SDL_SCANCODE_H:
return Key::H;
case SDL_SCANCODE_I:
return Key::I;
case SDL_SCANCODE_J:
return Key::J;
case SDL_SCANCODE_K:
return Key::K;
case SDL_SCANCODE_L:
return Key::L;
case SDL_SCANCODE_M:
return Key::M;
case SDL_SCANCODE_N:
return Key::N;
case SDL_SCANCODE_O:
return Key::O;
case SDL_SCANCODE_P:
return Key::P;
case SDL_SCANCODE_Q:
return Key::Q;
case SDL_SCANCODE_R:
return Key::R;
case SDL_SCANCODE_S:
return Key::S;
case SDL_SCANCODE_T:
return Key::T;
case SDL_SCANCODE_U:
return Key::U;
case SDL_SCANCODE_V:
return Key::V;
case SDL_SCANCODE_W:
return Key::W;
case SDL_SCANCODE_X:
return Key::X;
case SDL_SCANCODE_Y:
return Key::Y;
case SDL_SCANCODE_Z:
return Key::Z;
case SDL_SCANCODE_0:
return Key::Num0;
case SDL_SCANCODE_1:
return Key::Num1;
case SDL_SCANCODE_2:
return Key::Num2;
case SDL_SCANCODE_3:
return Key::Num3;
case SDL_SCANCODE_4:
return Key::Num4;
case SDL_SCANCODE_5:
return Key::Num5;
case SDL_SCANCODE_6:
return Key::Num6;
case SDL_SCANCODE_7:
return Key::Num7;
case SDL_SCANCODE_8:
return Key::Num8;
case SDL_SCANCODE_9:
return Key::Num9;
case SDL_SCANCODE_F1:
return Key::F1;
case SDL_SCANCODE_F2:
return Key::F2;
case SDL_SCANCODE_F3:
return Key::F3;
case SDL_SCANCODE_F4:
return Key::F4;
case SDL_SCANCODE_F5:
return Key::F5;
case SDL_SCANCODE_F6:
return Key::F6;
case SDL_SCANCODE_F7:
return Key::F7;
case SDL_SCANCODE_F8:
return Key::F8;
case SDL_SCANCODE_F9:
return Key::F9;
case SDL_SCANCODE_F10:
return Key::F10;
case SDL_SCANCODE_F11:
return Key::F11;
case SDL_SCANCODE_F12:
return Key::F12;
case SDL_SCANCODE_SPACE:
return Key::Space;
case SDL_SCANCODE_RETURN:
return Key::Enter;
case SDL_SCANCODE_ESCAPE:
return Key::Escape;
case SDL_SCANCODE_TAB:
return Key::Tab;
case SDL_SCANCODE_BACKSPACE:
return Key::Backspace;
case SDL_SCANCODE_INSERT:
return Key::Insert;
case SDL_SCANCODE_DELETE:
return Key::Delete;
case SDL_SCANCODE_HOME:
return Key::Home;
case SDL_SCANCODE_END:
return Key::End;
case SDL_SCANCODE_PAGEUP:
return Key::PageUp;
case SDL_SCANCODE_PAGEDOWN:
return Key::PageDown;
case SDL_SCANCODE_UP:
return Key::Up;
case SDL_SCANCODE_DOWN:
return Key::Down;
case SDL_SCANCODE_LEFT:
return Key::Left;
case SDL_SCANCODE_RIGHT:
return Key::Right;
case SDL_SCANCODE_LSHIFT:
return Key::LShift;
case SDL_SCANCODE_RSHIFT:
return Key::RShift;
case SDL_SCANCODE_LCTRL:
return Key::LCtrl;
case SDL_SCANCODE_RCTRL:
return Key::RCtrl;
case SDL_SCANCODE_LALT:
return Key::LAlt;
case SDL_SCANCODE_RALT:
return Key::RAlt;
case SDL_SCANCODE_CAPSLOCK:
return Key::CapsLock;
case SDL_SCANCODE_NUMLOCKCLEAR:
return Key::NumLock;
case SDL_SCANCODE_SCROLLLOCK:
return Key::ScrollLock;
default:
return Key::None;
}
}

View File

@ -1,6 +1,7 @@
#pragma once
#include <extra2d/platform/iwindow.h>
#include <extra2d/platform/keys.h>
#include <SDL.h>
namespace extra2d {
@ -71,6 +72,7 @@ private:
void deinitCursors();
void updateContentScale();
void handleEvent(const SDL_Event& event);
Key sdlScancodeToKey(int scancode);
SDL_Window* sdlWindow_ = nullptr;
SDL_GLContext glContext_ = nullptr;

View File

@ -82,6 +82,12 @@ void CameraService::setViewportConfig(const ViewportConfig &config) {
viewportAdapter_.setConfig(config);
}
void CameraService::setViewportConfig(std::function<void(ViewportConfig&)> configFn) {
ViewportConfig config = viewportAdapter_.getConfig();
configFn(config);
viewportAdapter_.setConfig(config);
}
const ViewportConfig &CameraService::getViewportConfig() const {
return viewportAdapter_.getConfig();
}

View File

@ -6,6 +6,8 @@
* OpenGL
*/
#include "extra2d/core/service_interface.h"
#include "extra2d/core/service_locator.h"
#include <extra2d/extra2d.h>
#include <iostream>
@ -97,28 +99,24 @@ int main(int argc, char *argv[]) {
app.use<WindowModule>([](auto &cfg) {
cfg.w = 1280;
cfg.h = 720;
cfg.title = "Extra2D Image Display Demo";
cfg.title = "Extra2D 图片显示示例";
cfg.priority = 0;
cfg.backend = "glfw";
cfg.backend = "sdl2";
});
app.use<RenderModule>([](auto &cfg) { cfg.priority = 10; });
app.use<RenderModule>([](auto &cfg) {
cfg.priority = 10;
cfg.backend = "opengl";
});
app.use<InputModule>([](auto &cfg) { cfg.priority = 20; });
std::cout << "Initializing application..." << std::endl;
if (!app.init()) {
std::cerr << "Failed to initialize application!" << std::endl;
return -1;
}
std::cout << "Application initialized successfully!" << std::endl;
auto *win = app.window();
if (win) {
std::cout << "Window: " << win->width() << "x" << win->height()
<< std::endl;
}
auto win = app.window();
// 设置事件监听
auto eventService = ServiceLocator::instance().getService<IEventService>();
@ -148,26 +146,20 @@ int main(int argc, char *argv[]) {
// 配置相机
auto cameraService = ServiceLocator::instance().getService<ICameraService>();
if (cameraService && win) {
ViewportConfig vpConfig;
vpConfig.logicWidth = static_cast<float>(win->width());
vpConfig.logicHeight = static_cast<float>(win->height());
vpConfig.mode = ViewportMode::AspectRatio;
cameraService->setViewportConfig(vpConfig);
cameraService->setViewportConfig([&](auto &cfg) {
cfg.logicWidth = static_cast<float>(win->width());
cfg.logicHeight = static_cast<float>(win->height());
cfg.mode = ViewportMode::AspectRatio;
});
cameraService->updateViewport(win->width(), win->height());
cameraService->applyViewportAdapter();
}
app.enterScene(scene);
std::cout << "\nControls:" << std::endl;
std::cout << " ESC - Exit" << std::endl;
std::cout << "\nRunning main loop...\n" << std::endl;
app.run();
std::cout << "Shutting down..." << std::endl;
app.shutdown();
std::cout << "Goodbye!" << std::endl;
return 0;
}