NazaraEngine/src/Nazara/Platform/SDL2/WindowImpl.cpp

655 lines
16 KiB
C++

// Copyright (C) 2023 Jérôme "Lynix" Leclercq (lynix680@gmail.com)
// This file is part of the "Nazara Engine - Platform module"
// For conditions of distribution and use, see copyright notice in Config.hpp
#include <Nazara/Platform/SDL2/WindowImpl.hpp>
#include <Nazara/Core/Error.hpp>
#include <Nazara/Core/ErrorFlags.hpp>
#include <Nazara/Platform/Config.hpp>
#include <Nazara/Platform/Cursor.hpp>
#include <Nazara/Platform/Icon.hpp>
#include <Nazara/Platform/SDL2/CursorImpl.hpp>
#include <Nazara/Platform/SDL2/IconImpl.hpp>
#include <Nazara/Platform/SDL2/SDLHelper.hpp>
#include <Nazara/Utility/Image.hpp>
#include <SDL.h>
#ifdef NAZARA_PLATFORM_MACOS
// I'm not sure why, but SDL_VIDEO_DRIVER_X11 is automatically defined here by SDL_config.h
// This is problematic as it requires X11/X.h which is not present (adding libxext/libx11/xorgproto packages didn't help)
#undef SDL_VIDEO_DRIVER_X11
#endif
#include <SDL_syswm.h>
#include <Utfcpp/utf8.h>
#include <cstdio>
#include <memory>
#include <Nazara/Platform/Debug.hpp>
namespace Nz
{
namespace
{
Mouse::Button SDLToNazaraButton(Uint8 sdlButton)
{
switch (sdlButton)
{
case SDL_BUTTON_LEFT: return Mouse::Left;
case SDL_BUTTON_MIDDLE: return Mouse::Middle;
case SDL_BUTTON_RIGHT: return Mouse::Right;
case SDL_BUTTON_X1: return Mouse::XButton1;
case SDL_BUTTON_X2: return Mouse::XButton2;
default:
NazaraAssert(false, "Unkown mouse button");
return Mouse::Left;
}
}
}
WindowImpl::WindowImpl(Window* parent) :
m_cursor(nullptr),
m_handle(nullptr),
m_parent(parent),
m_eventListener(false),
m_ignoreNextMouseMove(false),
m_lastEditEventLength(0)
{
m_cursor = SDL_GetDefaultCursor();
}
bool WindowImpl::Create(const VideoMode& mode, const std::string& title, WindowStyleFlags style)
{
bool fullscreen = (style & WindowStyle::Fullscreen) != 0;
Uint32 winStyle = 0;
unsigned int x, y;
unsigned int width = mode.width;
unsigned int height = mode.height;
if (fullscreen)
winStyle |= SDL_WINDOW_FULLSCREEN;
if (fullscreen)
{
x = 0;
y = 0;
}
else
{
if (!(style & WindowStyle::Titlebar))
winStyle |= SDL_WINDOW_BORDERLESS;
x = SDL_WINDOWPOS_CENTERED;
y = SDL_WINDOWPOS_CENTERED;
}
if (style & WindowStyle::Resizable)
winStyle |= SDL_WINDOW_RESIZABLE;
m_ownsWindow = true;
m_handle = SDL_CreateWindow(title.c_str(), x, y, width, height, winStyle);
if (!m_handle)
{
NazaraError("Failed to create window: " + Error::GetLastSystemError());
return false;
}
m_windowId = SDL_GetWindowID(m_handle);
SetEventListener(true);
return true;
}
bool WindowImpl::Create(WindowHandle handle)
{
void* systemHandle = nullptr;
switch (handle.type)
{
case WindowBackend::Invalid:
{
NazaraError("unsupported creation from a Wayland handle");
return false;
}
case WindowBackend::Wayland:
{
NazaraError("unsupported creation from a Wayland handle");
return false;
}
case WindowBackend::Cocoa: systemHandle = handle.cocoa.window; break;
case WindowBackend::X11: systemHandle = (void*) handle.x11.window; break;
case WindowBackend::Windows: systemHandle = handle.windows.window; break;
}
m_ownsWindow = false;
m_handle = SDL_CreateWindowFrom(systemHandle);
if (!m_handle)
{
NazaraError("Invalid handle");
return false;
}
m_windowId = SDL_GetWindowID(m_handle);
SetEventListener(true);
return true;
}
void WindowImpl::Destroy()
{
SetEventListener(false);
if (m_handle)
{
if (m_ownsWindow)
SDL_DestroyWindow(m_handle);
m_handle = nullptr;
}
}
Vector2i WindowImpl::FetchPosition() const
{
int x, y;
SDL_GetWindowPosition(m_handle, &x, &y);
return { x, y };
}
Vector2ui WindowImpl::FetchSize() const
{
int width, height;
SDL_GetWindowSize(m_handle, &width, &height);
return { SafeCast<unsigned int>(width), SafeCast<unsigned int>(height) };
}
WindowStyleFlags WindowImpl::FetchStyle() const
{
UInt32 windowFlags = SDL_GetWindowFlags(m_handle);
WindowStyleFlags styleFlags;
if (windowFlags & SDL_WINDOW_RESIZABLE)
styleFlags |= WindowStyle::Resizable;
if ((windowFlags & SDL_WINDOW_BORDERLESS) == 0)
styleFlags |= WindowStyle::Titlebar | WindowStyle::Closable;
if (windowFlags & SDL_WINDOW_FULLSCREEN)
styleFlags |= WindowStyle::Fullscreen;
return styleFlags;
}
std::string WindowImpl::FetchTitle() const
{
return SDL_GetWindowTitle(m_handle);
}
SDL_Window* WindowImpl::GetHandle() const
{
return m_handle;
}
WindowHandle WindowImpl::GetSystemHandle() const
{
#ifdef NAZARA_PLATFORM_WEB
WindowHandle handle;
handle.type = WindowBackend::Web;
return handle;
#else
SDL_SysWMinfo wmInfo;
SDL_VERSION(&wmInfo.version);
if (SDL_GetWindowWMInfo(m_handle, &wmInfo) != SDL_TRUE)
{
#ifndef NAZARA_PLATFORM_WEB
ErrorFlags flags(ErrorMode::ThrowException, true);
NazaraError(std::string("failed to retrieve window manager info: ") + SDL_GetError());
#endif
}
WindowHandle handle;
switch (wmInfo.subsystem)
{
#if defined(SDL_VIDEO_DRIVER_COCOA)
case SDL_SYSWM_COCOA:
{
handle.type = WindowBackend::Cocoa;
handle.cocoa.window = wmInfo.info.cocoa.window;
break;
}
#endif
#if defined(SDL_VIDEO_DRIVER_X11)
case SDL_SYSWM_X11:
{
handle.type = WindowBackend::X11;
handle.x11.display = wmInfo.info.x11.display;
handle.x11.window = wmInfo.info.x11.window;
break;
}
#endif
#if defined(SDL_VIDEO_DRIVER_WAYLAND)
case SDL_SYSWM_WAYLAND:
{
handle.type = WindowBackend::Wayland;
handle.wayland.display = wmInfo.info.wl.display;
handle.wayland.surface = wmInfo.info.wl.surface;
handle.wayland.shellSurface = wmInfo.info.wl.shell_surface;
break;
}
#endif
#if defined(SDL_VIDEO_DRIVER_WINDOWS)
case SDL_SYSWM_WINDOWS:
{
handle.type = WindowBackend::Windows;
handle.windows.window = wmInfo.info.win.window;
break;
}
#endif
default:
{
#if defined(NAZARA_PLATFORM_WEB)
handle.type = WindowBackend::Web;
#else
ErrorFlags flags(ErrorMode::ThrowException, true);
NazaraError("unhandled window subsystem");
#endif
}
}
return handle;
#endif
}
bool WindowImpl::HasFocus() const
{
return (SDL_GetWindowFlags(m_handle) & SDL_WINDOW_INPUT_FOCUS) != 0;
}
void WindowImpl::IgnoreNextMouseEvent(int /*mouseX*/, int /*mouseY*/)
{
m_ignoreNextMouseMove = true;
}
bool WindowImpl::IsMinimized() const
{
return (SDL_GetWindowFlags(m_handle) & SDL_WINDOW_MINIMIZED) != 0;
}
bool WindowImpl::IsVisible() const
{
return (SDL_GetWindowFlags(m_handle) & SDL_WINDOW_SHOWN) != 0;
}
void WindowImpl::RefreshCursor()
{
if (!m_cursor)
{
if (SDL_ShowCursor(SDL_DISABLE) < 0)
NazaraWarning(SDL_GetError());
}
else
{
if (SDL_ShowCursor(SDL_ENABLE) < 0)
NazaraWarning(SDL_GetError());
SDL_SetCursor(m_cursor);
}
}
int WindowImpl::HandleEvent(void* userdata, SDL_Event* event)
{
try
{
WindowImpl* window = static_cast<WindowImpl*>(userdata);
switch (event->type)
{
case SDL_WINDOWEVENT:
{
if (window->m_windowId != event->window.windowID)
return 0;
WindowEvent windowEvent;
switch (event->window.event)
{
case SDL_WINDOWEVENT_CLOSE:
windowEvent.type = WindowEventType::Quit;
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
windowEvent.type = WindowEventType::Resized;
windowEvent.size.width = static_cast<unsigned int>(std::max(0, event->window.data1));
windowEvent.size.height = static_cast<unsigned int>(std::max(0, event->window.data2));
break;
case SDL_WINDOWEVENT_MOVED:
windowEvent.type = WindowEventType::Moved;
windowEvent.position.x = event->window.data1;
windowEvent.position.y = event->window.data2;
break;
case SDL_WINDOWEVENT_FOCUS_GAINED:
windowEvent.type = WindowEventType::GainedFocus;
window->RefreshCursor();
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
windowEvent.type = WindowEventType::LostFocus;
break;
case SDL_WINDOWEVENT_ENTER:
windowEvent.type = WindowEventType::MouseEntered;
window->RefreshCursor();
break;
case SDL_WINDOWEVENT_LEAVE:
windowEvent.type = WindowEventType::MouseLeft;
break;
case SDL_WINDOWEVENT_MINIMIZED:
windowEvent.type = WindowEventType::Minimized;
break;
case SDL_WINDOWEVENT_RESTORED:
windowEvent.type = WindowEventType::Restored;
break;
default:
return 0;
}
window->m_parent->HandleEvent(windowEvent);
break;
}
case SDL_MOUSEMOTION:
{
if (window->m_windowId != event->motion.windowID)
return 0;
if (window->m_ignoreNextMouseMove)
{
window->m_ignoreNextMouseMove = false;
return 0;
}
WindowEvent windowEvent;
windowEvent.type = WindowEventType::MouseMoved;
windowEvent.mouseMove.x = event->motion.x;
windowEvent.mouseMove.y = event->motion.y;
windowEvent.mouseMove.deltaX = event->motion.xrel;
windowEvent.mouseMove.deltaY = event->motion.yrel;
window->m_parent->HandleEvent(windowEvent);
break;
}
case SDL_MOUSEBUTTONDOWN:
{
if (window->m_windowId != event->button.windowID)
return 0;
WindowEvent windowEvent;
windowEvent.type = WindowEventType::MouseButtonPressed;
windowEvent.mouseButton.button = SDLToNazaraButton(event->button.button);
windowEvent.mouseButton.x = event->button.x;
windowEvent.mouseButton.y = event->button.y;
windowEvent.mouseButton.clickCount = event->button.clicks;
window->m_parent->HandleEvent(windowEvent);
break;
}
case SDL_MOUSEBUTTONUP:
{
if (window->m_windowId != event->button.windowID)
return 0;
WindowEvent windowEvent;
windowEvent.type = WindowEventType::MouseButtonReleased;
windowEvent.mouseButton.button = SDLToNazaraButton(event->button.button);
windowEvent.mouseButton.x = event->button.x;
windowEvent.mouseButton.y = event->button.y;
window->m_parent->HandleEvent(windowEvent);
break;
}
case SDL_MOUSEWHEEL:
{
if (window->m_windowId != event->wheel.windowID)
return 0;
WindowEvent windowEvent;
windowEvent.type = WindowEventType::MouseWheelMoved;
windowEvent.mouseWheel.delta = event->wheel.preciseY;
windowEvent.mouseWheel.x = event->wheel.mouseX;
windowEvent.mouseWheel.y = event->wheel.mouseY;
window->m_parent->HandleEvent(windowEvent);
break;
}
case SDL_KEYDOWN:
{
if (window->m_windowId != event->key.windowID)
return 0;
WindowEvent windowEvent;
windowEvent.type = WindowEventType::KeyPressed;
windowEvent.key.alt = (event->key.keysym.mod & KMOD_ALT) != 0;
windowEvent.key.control = (event->key.keysym.mod & KMOD_CTRL) != 0;
windowEvent.key.repeated = event->key.repeat != 0;
windowEvent.key.scancode = SDLHelper::FromSDL(event->key.keysym.scancode);
windowEvent.key.shift = (event->key.keysym.mod & KMOD_SHIFT) != 0;
windowEvent.key.system = (event->key.keysym.mod & KMOD_GUI) != 0;
windowEvent.key.virtualKey = SDLHelper::FromSDL(event->key.keysym.sym);
window->m_parent->HandleEvent(windowEvent);
// implements X11/Win32 APIs behavior for Enter and Backspace
switch (windowEvent.key.virtualKey)
{
case Nz::Keyboard::VKey::NumpadReturn:
case Nz::Keyboard::VKey::Return:
{
if (window->m_lastEditEventLength != 0)
break;
windowEvent.type = WindowEventType::TextEntered;
windowEvent.text.character = U'\n';
windowEvent.text.repeated = event->key.repeat != 0;
window->m_parent->HandleEvent(windowEvent);
break;
}
case Nz::Keyboard::VKey::Backspace:
windowEvent.type = WindowEventType::TextEntered;
windowEvent.text.character = U'\b';
windowEvent.text.repeated = event->key.repeat != 0;
window->m_parent->HandleEvent(windowEvent);
break;
default:
break;
}
break;
}
case SDL_KEYUP:
{
if (window->m_windowId != event->key.windowID)
return 0;
WindowEvent windowEvent;
windowEvent.type = WindowEventType::KeyReleased;
windowEvent.key.alt = (event->key.keysym.mod & KMOD_ALT) != 0;
windowEvent.key.control = (event->key.keysym.mod & KMOD_CTRL) != 0;
windowEvent.key.repeated = event->key.repeat != 0;
windowEvent.key.scancode = SDLHelper::FromSDL(event->key.keysym.scancode);
windowEvent.key.shift = (event->key.keysym.mod & KMOD_SHIFT) != 0;
windowEvent.key.system = (event->key.keysym.mod & KMOD_GUI) != 0;
windowEvent.key.virtualKey = SDLHelper::FromSDL(event->key.keysym.sym);
window->m_parent->HandleEvent(windowEvent);
break;
}
case SDL_TEXTINPUT:
{
if (window->m_windowId != event->text.windowID)
return 0;
WindowEvent windowEvent;
windowEvent.type = WindowEventType::TextEntered;
windowEvent.text.repeated = false;
utf8::unchecked::iterator<const char*> it(event->text.text);
do
{
windowEvent.text.character = *it;
window->m_parent->HandleEvent(windowEvent);
}
while (*it++);
break;
}
case SDL_TEXTEDITING:
{
if (window->m_windowId != event->edit.windowID)
return 0;
WindowEvent windowEvent;
windowEvent.type = WindowEventType::TextEdited;
windowEvent.edit.length = event->edit.length;
window->m_lastEditEventLength = windowEvent.edit.length;
for (std::size_t i = 0; i < 32; i++)
{
windowEvent.edit.text[i] = event->edit.text[i];
}
window->m_parent->HandleEvent(windowEvent);
break;
}
}
}
catch (const std::exception& e)
{
NazaraError(e.what());
}
catch (...) // Don't let any exceptions go through C calls
{
NazaraError("An unknown error happened");
}
return 0;
}
void WindowImpl::UpdateCursor(const Cursor& cursor)
{
m_cursor = cursor.m_impl->GetCursor();
if (HasFocus())
RefreshCursor();
}
void WindowImpl::RaiseFocus()
{
SDL_RaiseWindow(m_handle);
}
void WindowImpl::UpdateIcon(const Icon& icon)
{
SDL_SetWindowIcon(m_handle, icon.m_impl->GetIcon());
}
void WindowImpl::UpdateMaximumSize(int width, int height)
{
SDL_SetWindowMaximumSize(m_handle, width, height);
}
void WindowImpl::UpdateMinimumSize(int width, int height)
{
SDL_SetWindowMinimumSize(m_handle, width, height);
}
void WindowImpl::UpdatePosition(int x, int y)
{
SDL_SetWindowPosition(m_handle, x, y);
}
void WindowImpl::UpdateSize(unsigned int width, unsigned int height)
{
SDL_SetWindowSize(m_handle, width, height);
}
void WindowImpl::UpdateStayOnTop(bool stayOnTop)
{
SDL_SetWindowAlwaysOnTop(m_handle, (stayOnTop) ? SDL_TRUE : SDL_FALSE);
}
void WindowImpl::UpdateTitle(const std::string& title)
{
SDL_SetWindowTitle(m_handle, title.c_str());
}
void WindowImpl::Show(bool visible)
{
if (visible)
SDL_ShowWindow(m_handle);
else
SDL_HideWindow(m_handle);
}
void WindowImpl::ProcessEvents()
{
SDL_PumpEvents();
}
bool WindowImpl::Initialize()
{
if (SDL_Init(SDL_INIT_VIDEO) < 0)
{
NazaraError(SDL_GetError());
return false;
}
return true;
}
void WindowImpl::Uninitialize()
{
SDL_Quit();
}
void WindowImpl::SetEventListener(bool listener)
{
if (m_eventListener == listener)
return;
if (listener)
SDL_AddEventWatch(HandleEvent, this);
else
SDL_DelEventWatch(HandleEvent, this);
m_eventListener = listener;
}
}
#if defined(NAZARA_PLATFORM_WINDOWS)
#include <Nazara/Core/AntiWindows.hpp>
#elif defined(NAZARA_PLATFORM_LINUX)
#include <Nazara/Core/AntiX11.hpp>
#endif