#include "iwa/window.hpp" #include #include "iwa/log.hpp" #include "iwa/instance.hpp" namespace iwa { inline constexpr const char* WINDOW_DATA_NAME = "iwa_window"; IWA_REGISTER_CLASS(Window) namespace { Window* gLastEventWindow = nullptr; Window* getWindowFromEvent(Uint32 windowID) noexcept { SDL_Window* sdlWindow = SDL_GetWindowFromID(windowID); Window* iwaWindow = static_cast(SDL_GetWindowData(sdlWindow, WINDOW_DATA_NAME)); if (iwaWindow != nullptr) { gLastEventWindow = iwaWindow; } return gLastEventWindow; } void handleWindowEvent(const SDL_Event& event) { Window* iwaWindow = getWindowFromEvent(event.window.windowID); if (iwaWindow == nullptr) { return; } switch (event.window.event) { case SDL_WINDOWEVENT_FOCUS_GAINED: iwaWindow->focusGained.emit(); break; case SDL_WINDOWEVENT_FOCUS_LOST: iwaWindow->focusLost.emit(); break; case SDL_WINDOWEVENT_ENTER: iwaWindow->mouseEntered.emit(); break; case SDL_WINDOWEVENT_LEAVE: iwaWindow->mouseLeft.emit(); break; case SDL_WINDOWEVENT_CLOSE: iwaWindow->closeRequested.emit(); break; } } void handleKeyEvent(const SDL_Event& sdlEvent) { Window* iwaWindow = getWindowFromEvent(sdlEvent.key.windowID); if (iwaWindow == nullptr) { return; } const KeyEvent event = { .keyCode = static_cast(sdlEvent.key.keysym.sym), .scanCode = static_cast(sdlEvent.key.keysym.scancode), .modifiers = { .leftShift = (sdlEvent.key.keysym.mod & KMOD_LSHIFT) != 0, .rightShift = (sdlEvent.key.keysym.mod & KMOD_RSHIFT) != 0, .leftCtrl = (sdlEvent.key.keysym.mod & KMOD_LCTRL) != 0, .rightCtrl = (sdlEvent.key.keysym.mod & KMOD_RCTRL) != 0, .leftAlt = (sdlEvent.key.keysym.mod & KMOD_LALT) != 0, .rightAlt = (sdlEvent.key.keysym.mod & KMOD_RALT) != 0, .leftMeta = (sdlEvent.key.keysym.mod & KMOD_LGUI) != 0, .rightMeta = (sdlEvent.key.keysym.mod & KMOD_RGUI) != 0, }, .down = sdlEvent.type == SDL_KEYDOWN, .repeat = sdlEvent.key.repeat > 0 }; iwaWindow->keyChanged.emit(event); } void handleMouseMotion(const SDL_Event& sdlEvent) { Window* iwaWindow = getWindowFromEvent(sdlEvent.motion.windowID); if (iwaWindow == nullptr) { return; } const MouseMoveEvent event = { .relativeX = sdlEvent.motion.xrel, .relativeY = sdlEvent.motion.yrel, .absoluteX = sdlEvent.motion.x, .absoluteY = sdlEvent.motion.y, .warped = false // TODO? }; iwaWindow->mouseMoved.emit(event); } void handleMouseButtonEvent(const SDL_Event& sdlEvent) { Window* iwaWindow = getWindowFromEvent(sdlEvent.button.windowID); if (iwaWindow == nullptr) { return; } const MouseButtonEvent event = { .button = static_cast(sdlEvent.button.button), .clicks = sdlEvent.button.clicks, .down = sdlEvent.type == SDL_MOUSEBUTTONDOWN }; iwaWindow->mouseButtonChanged.emit(event); } void handleMouseWheelEvent(const SDL_Event& sdlEvent) { Window* iwaWindow = getWindowFromEvent(sdlEvent.wheel.windowID); if (iwaWindow == nullptr) { return; } const MouseWheelEvent event = { .relativeX = sdlEvent.wheel.x, .relativeY = sdlEvent.wheel.y }; iwaWindow->mouseScrolled.emit(event); } void handleTextInputEvent(const SDL_Event& sdlEvent) { Window* iwaWindow = getWindowFromEvent(sdlEvent.text.windowID); if (iwaWindow == nullptr) { return; } const TextInputEvent event = { .text = sdlEvent.text.text }; iwaWindow->textEntered.emit(event); } void handleEvent(const SDL_Event& event) { switch (event.type) { case SDL_WINDOWEVENT: handleWindowEvent(event); break; case SDL_KEYDOWN: case SDL_KEYUP: handleKeyEvent(event); break; case SDL_MOUSEMOTION: handleMouseMotion(event); break; case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: handleMouseButtonEvent(event); break; case SDL_MOUSEWHEEL: handleMouseWheelEvent(event); break; case SDL_TEXTINPUT: handleTextInputEvent(event); break; } } mijin::Task<> c_sdlLoop(ObjectPtr instance) { while (!instance->isQuitRequested()) { SDL_Event event; while (SDL_PollEvent(&event)) { handleEvent(event); } co_await mijin::c_suspend(); } // SDL_Quit(); co_return; } void initSDL(Instance& instance) { static bool sdlInited = false; if (sdlInited) { return; } sdlInited = true; if (SDL_Init(0) != 0) { logAndDie("Error initializing SDL: {}.", SDL_GetError()); } SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, "0"); instance.getMainTaskLoop().addTask(c_sdlLoop(instance.getPointer())); } } Window::Window(ObjectPtr owner, const WindowCreationArgs& args) : super_t(std::move(owner)) { initSDL(*getOwner()); const Uint32 flags = SDL_WINDOW_VULKAN | (args.flags.hidden ? SDL_WINDOW_HIDDEN : 0) | (args.flags.resizable ? SDL_WINDOW_RESIZABLE : 0) | (args.flags.borderless ? SDL_WINDOW_BORDERLESS : 0) | (args.flags.alwayOnTop ? SDL_WINDOW_ALWAYS_ON_TOP : 0) | (args.flags.skipTaskbar ? SDL_WINDOW_UTILITY : 0); mHandle = SDL_CreateWindow( /* title = */ args.title.c_str(), /* x = */ SDL_WINDOWPOS_CENTERED, /* y = */ SDL_WINDOWPOS_CENTERED, /* w = */ args.width, /* h = */ args.height, /* flags = */ flags ); if (mHandle == nullptr) { logAndDie("Error creating SDL window: {}.", SDL_GetError()); } SDL_SetWindowData(mHandle, WINDOW_DATA_NAME, this); VkSurfaceKHR surface = VK_NULL_HANDLE; if (!SDL_Vulkan_CreateSurface(mHandle, getOwner()->getVkHandle(), &surface)) { logAndDie("Error creating Vulkan surface for SDL window: {}", SDL_GetError()); } mSurface = surface; getOwner()->windowCreated.emit(*this); } Window::~Window() noexcept { getOwner()->queueDelete([surface=mSurface, handle=mHandle, instance= getOwner()] { if (surface) { instance->getVkHandle().destroySurfaceKHR(surface); } if (handle) { SDL_DestroyWindow(handle); } }); } bool Window::isVisible() const noexcept { return (SDL_GetWindowFlags(mHandle) & SDL_WINDOW_HIDDEN) == 0; } void Window::setVisible(bool visible) noexcept { if (visible) { SDL_ShowWindow(mHandle); } else { SDL_HideWindow(mHandle); } } std::pair Window::getSize() const noexcept { std::pair size; SDL_GetWindowSize(mHandle, &size.first, &size.second); return size; } void Window::setSize(int width, int height) noexcept { SDL_SetWindowSize(mHandle, std::max(width, 1), std::max(height, 1)); } std::pair Window::getPosition() const noexcept { std::pair position; SDL_GetWindowPosition(mHandle, &position.first, &position.second); return position; } void Window::setPosition(int xPos, int yPos) noexcept { SDL_SetWindowPosition(mHandle, xPos, yPos); } WindowBorder Window::getWindowBorder() const noexcept { WindowBorder windowBorder; SDL_GetWindowBordersSize(mHandle, &windowBorder.top, &windowBorder.left, &windowBorder.bottom, &windowBorder.right); return windowBorder; } bool Window::isFocused() const noexcept { return (SDL_GetWindowFlags(mHandle) & SDL_WINDOW_INPUT_FOCUS) != 0; } void Window::focus() noexcept { SDL_RaiseWindow(mHandle); } void Window::setMouseMode(MouseMode mouseMode) noexcept { switch (mouseMode) { case MouseMode::NORMAL: SDL_SetRelativeMouseMode(SDL_FALSE); SDL_SetWindowMouseGrab(mHandle, SDL_FALSE); break; case MouseMode::CAPTURED: SDL_SetRelativeMouseMode(SDL_TRUE); SDL_SetWindowMouseGrab(mHandle, SDL_TRUE); break; } } void Window::setModalFor(mijin::Optional parent) noexcept { SDL_SetWindowModalFor(mHandle, parent.empty() ? nullptr : parent->getSDLWindow()); } }