diff --git a/CMakeLists.txt b/CMakeLists.txt index e7aa8a1..0638ff8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,28 +1,20 @@ cmake_minimum_required(VERSION 3.1) -project(hello-bgfx) +project(my-game) -# set(BGFX_AMALGAMATED OFF CACHE INTERNAL "" FORCE) -# set(BGFX_BUILD_EXAMPLES OFF CACHE INTERNAL "" FORCE) -set(BGFX_CONFIG_DEBUG OFF CACHE INTERNAL "" FORCE) -set(BX_CONFIG_DEBUG OFF CACHE INTERNAL "" FORCE) -# set(BGFX_CUSTOM_TARGETS OFF CACHE INTERNAL "" FORCE) -# set(BGFX_INSTALL OFF CACHE INTERNAL "" FORCE) -# set(BGFX_INSTALL_EXAMPLES OFF CACHE INTERNAL "" FORCE) -# set(BGFX_USE_DEBUG_SUFFIX OFF CACHE INTERNAL "" FORCE) -# set(BGFX_USE_OVR OFF CACHE INTERNAL "" FORCE) -# set(BUILD_SHARED_LIBS OFF CACHE INTERNAL "" FORCE) -# set(BX_AMALGAMATED OFF CACHE INTERNAL "" FORCE) -# if (HELLO_BGFX_BUILD_SHADERS) -# set(BGFX_BUILD_TOOLS ON CACHE INTERNAL "" FORCE) -# else() -# set(BGFX_BUILD_TOOLS OFF CACHE INTERNAL "" FORCE) -# endif() +message("=== Configure SDL2 ===") +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/SDL EXCLUDE_FROM_ALL) -# needed by windows -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse4.1") -add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/bgfx.cmake) -add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/SDL) +message("=== Configure Corrade ===") +set(CORRADE_BUILD_STATIC ON CACHE BOOL "" FORCE) +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/corrade EXCLUDE_FROM_ALL) +message("=== Configure Magnum ===") +# Add Magnum as a subproject, enable Sdl2Application +set(MAGNUM_BUILD_STATIC ON CACHE BOOL "" FORCE) +set(MAGNUM_WITH_SDL2APPLICATION ON CACHE BOOL "" FORCE) +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/magnum EXCLUDE_FROM_ALL) + +message("=== Configure QuickJS ===") # add quickjs set(quickjs_version 2021-03-27) set(quickjs_sources_root ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/quickjs) @@ -48,36 +40,9 @@ target_include_directories(quickjs ${quickjs_sources_root} ) - -# Note: This was necessary to avoid a run-time error on macOS/OpenGL. Without -# this option, the NSOpenGLViewContext's setView method was called from a -# wrong (?) thread during bgfx::init and then the app crashes. This needs to be -# investigated further. -#target_compile_definitions(bgfx PUBLIC BGFX_CONFIG_MULTITHREADED=0) - -# workaraound to resolve shaderc -if (NOT TARGET bgfx::shaderc AND TARGET shaderc) - add_executable(bgfx::shaderc ALIAS shaderc) -endif() - -bgfx_compile_shader_to_header( - TYPE VERTEX - SHADERS ${CMAKE_CURRENT_SOURCE_DIR}/shaders/vs.sc - VARYING_DEF ${CMAKE_CURRENT_SOURCE_DIR}/shaders/varying.def.sc - OUTPUT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include/generated/shaders -) -bgfx_compile_shader_to_header( - TYPE FRAGMENT - SHADERS ${CMAKE_CURRENT_SOURCE_DIR}/shaders/fs.sc - VARYING_DEF ${CMAKE_CURRENT_SOURCE_DIR}/shaders/varying.def.sc - OUTPUT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include/generated/shaders -) - set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) -file(GLOB files src/*.cpp) -add_executable(hello-bgfx ${files} shaders/vs.sc shaders/fs.sc shaders/varying.def.sc) - -#target_include_directories(hello-bgfx PRIVATE ${quickjs_sources_root}) -target_include_directories(hello-bgfx PRIVATE include) -target_include_directories(hello-bgfx PRIVATE src) -target_link_libraries(hello-bgfx bgfx bx SDL2-static quickjs) \ No newline at end of file +file(GLOB files src/main.cpp) +add_executable(${CMAKE_PROJECT_NAME} ${files}) +target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE include) +target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE src) +target_link_libraries(${CMAKE_PROJECT_NAME} SDL2::SDL2-static Magnum::Sdl2Application Magnum::Magnum Magnum::GL quickjs) \ No newline at end of file diff --git a/my-game.exe b/my-game.exe new file mode 100644 index 0000000..4814c38 Binary files /dev/null and b/my-game.exe differ diff --git a/src/Sdl2Application.cpp b/src/Sdl2Application.cpp new file mode 100644 index 0000000..658aa9c --- /dev/null +++ b/src/Sdl2Application.cpp @@ -0,0 +1,1277 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 Vladimír Vondruš + Copyright © 2019 Marco Melorio + Copyright © 2022 Pablo Escobar + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include "Sdl2Application.h" + +#ifdef CORRADE_TARGET_CLANG_CL +/* SDL does #pragma pack(push,8) and #pragma pack(pop,8) in different headers + (begin_code.h and end_code.h) and clang-cl doesn't like that, even though it + is completely fine. Silence the warning. */ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpragma-pack" +#endif +#include +#ifdef CORRADE_TARGET_CLANG_CL +#pragma clang diagnostic pop +#endif +#ifndef CORRADE_TARGET_EMSCRIPTEN +#include +#else +#include +#include +#endif +#include +#include + +#include "Magnum/Image.h" +#include "Magnum/ImageView.h" +#include "Magnum/PixelFormat.h" +#include "Magnum/Math/ConfigurationValue.h" +#include "Magnum/Math/FunctionsBatch.h" +#include "Magnum/Math/Range.h" +#include "Magnum/Platform/ScreenedApplication.hpp" +#include "Magnum/Platform/Implementation/DpiScaling.h" + +#ifdef MAGNUM_TARGET_GL +#include "Magnum/GL/Version.h" +#endif + +#if defined(CORRADE_TARGET_WINDOWS) && !defined(CORRADE_TARGET_WINDOWS_RT) +/* For physical DPI scaling */ +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#endif + +namespace Magnum { namespace Platform { + +using namespace Containers::Literals; + +namespace { + +/* + * Fix up the modifiers -- we want >= operator to work properly on Shift, + * Ctrl, Alt, but SDL generates different event for left / right keys, thus + * (modifiers >= Shift) would pass only if both left and right were pressed, + * which is usually not what the developers wants. + */ +Sdl2Application::InputEvent::Modifiers fixedModifiers(Uint16 mod) { + Sdl2Application::InputEvent::Modifiers modifiers(static_cast(mod)); + if(modifiers & Sdl2Application::InputEvent::Modifier::Shift) modifiers |= Sdl2Application::InputEvent::Modifier::Shift; + if(modifiers & Sdl2Application::InputEvent::Modifier::Ctrl) modifiers |= Sdl2Application::InputEvent::Modifier::Ctrl; + if(modifiers & Sdl2Application::InputEvent::Modifier::Alt) modifiers |= Sdl2Application::InputEvent::Modifier::Alt; + if(modifiers & Sdl2Application::InputEvent::Modifier::Super) modifiers |= Sdl2Application::InputEvent::Modifier::Super; + return modifiers; +} + +} + +enum class Sdl2Application::Flag: UnsignedByte { + Redraw = 1 << 0, + VSyncEnabled = 1 << 1, + NoTickEvent = 1 << 2, + NoAnyEvent = 1 << 3, + Exit = 1 << 4, + #ifdef CORRADE_TARGET_EMSCRIPTEN + TextInputActive = 1 << 5, + Resizable = 1 << 6, + #endif + #ifdef CORRADE_TARGET_APPLE + HiDpiWarningPrinted = 1 << 7 + #endif +}; + +Sdl2Application::Sdl2Application(const Arguments& arguments): Sdl2Application{arguments, Configuration{}} {} + +Sdl2Application::Sdl2Application(const Arguments& arguments, const Configuration& configuration): Sdl2Application{arguments, NoCreate} { + create(configuration); +} + +#ifdef MAGNUM_TARGET_GL +Sdl2Application::Sdl2Application(const Arguments& arguments, const Configuration& configuration, const GLConfiguration& glConfiguration): Sdl2Application{arguments, NoCreate} { + create(configuration, glConfiguration); +} +#endif + +Sdl2Application::Sdl2Application(const Arguments& arguments, NoCreateT): + #ifndef CORRADE_TARGET_EMSCRIPTEN + _minimalLoopPeriod{0}, + #endif + _flags{Flag::Redraw} +{ + Utility::Arguments args{Implementation::windowScalingArguments()}; + #ifdef MAGNUM_TARGET_GL + _context.emplace(NoCreate, args, arguments.argc, arguments.argv); + #else + /** @todo this is duplicated here, in GlfwApplication and in + EmscriptenApplication, figure out a nice non-duplicated way to handle + this */ + args.addOption("log", "default").setHelp("log", "console logging", "default|quiet|verbose") + .setFromEnvironment("log") + .parse(arguments.argc, arguments.argv); + #endif + + /* Available since 2.0.4, disables interception of SIGINT and SIGTERM so + it's possible to Ctrl-C the application even if exitEvent() doesn't set + event.setAccepted(). */ + #ifdef SDL_HINT_NO_SIGNAL_HANDLERS + SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1"); + #endif + /* Available since 2.0.6, uses dedicated OpenGL ES drivers if EGL is used, + and desktop GLES context otherwise. */ + #if defined(MAGNUM_TARGET_GLES) && defined(MAGNUM_TARGET_EGL) && defined(SDL_HINT_OPENGL_ES_DRIVER) + SDL_SetHint(SDL_HINT_OPENGL_ES_DRIVER, "1"); + #endif + /* Available since 2.0.8, disables compositor bypass on X11, which causes + flickering on KWin as the compositor gets shut down on every startup */ + #ifdef SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR + SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0"); + #endif + /* By default, SDL behaves like if it was playing a video or whatever, + preventing the computer from turning off the screen or going to sleep. + While it sorta makes sense for games, it's useless and annoying for + regular apps. Together with the compositor disabling those two are the + most stupid defaults. */ + #ifdef SDL_HINT_VIDEO_ALLOW_SCREENSAVER + SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1"); + #endif + /* Available since 2.0.12, use EGL if desired */ + #if defined(MAGNUM_TARGET_EGL) && defined(SDL_HINT_VIDEO_X11_FORCE_EGL) + SDL_SetHint(SDL_HINT_VIDEO_X11_FORCE_EGL, "1"); + #endif + + if(SDL_Init(SDL_INIT_VIDEO) < 0) { + Error() << "Cannot initialize SDL:" << SDL_GetError(); + std::exit(1); + } + + /* Save command-line arguments */ + if(args.value("log") == "verbose") _verboseLog = true; + const Containers::StringView dpiScaling = args.value("dpi-scaling"); + if(dpiScaling == "default"_s) + _commandLineDpiScalingPolicy = Implementation::Sdl2DpiScalingPolicy::Default; + #ifdef CORRADE_TARGET_APPLE + else if(dpiScaling == "framebuffer"_s) + _commandLineDpiScalingPolicy = Implementation::Sdl2DpiScalingPolicy::Framebuffer; + #endif + #ifndef CORRADE_TARGET_APPLE + #if !defined(CORRADE_TARGET_EMSCRIPTEN) && !defined(CORRADE_TARGET_ANDROID) + else if(dpiScaling == "virtual"_s) + _commandLineDpiScalingPolicy = Implementation::Sdl2DpiScalingPolicy::Virtual; + #endif + else if(dpiScaling == "physical"_s) + _commandLineDpiScalingPolicy = Implementation::Sdl2DpiScalingPolicy::Physical; + #endif + else if(dpiScaling.containsAny(" \t\n"_s)) + _commandLineDpiScaling = args.value("dpi-scaling"); + else + _commandLineDpiScaling = Vector2{args.value("dpi-scaling")}; +} + +void Sdl2Application::create() { + create(Configuration{}); +} + +void Sdl2Application::create(const Configuration& configuration) { + if(!tryCreate(configuration)) std::exit(1); +} + +#ifdef MAGNUM_TARGET_GL +void Sdl2Application::create(const Configuration& configuration, const GLConfiguration& glConfiguration) { + if(!tryCreate(configuration, glConfiguration)) std::exit(1); +} +#endif + +Vector2 Sdl2Application::dpiScaling(const Configuration& configuration) { + std::ostream* verbose = _verboseLog ? Debug::output() : nullptr; + + /* Print a helpful warning in case some extra steps are needed for HiDPI + support */ + #ifdef CORRADE_TARGET_APPLE + if(!Implementation::isAppleBundleHiDpiEnabled() && !(_flags & Flag::HiDpiWarningPrinted)) { + Warning{} << "Platform::Sdl2Application: warning: the executable is not a HiDPI-enabled app bundle"; + _flags |= Flag::HiDpiWarningPrinted; + } + #elif defined(CORRADE_TARGET_WINDOWS) + /* Handled below, warning printed only when using virtual DPI scaling */ + #endif + + /* Use values from the configuration only if not overridden on command line + to something non-default. In any case explicit scaling has a precedence + before the policy. */ + Implementation::Sdl2DpiScalingPolicy dpiScalingPolicy{}; + if(!_commandLineDpiScaling.isZero()) { + Debug{verbose} << "Platform::Sdl2Application: user-defined DPI scaling" << _commandLineDpiScaling; + return _commandLineDpiScaling; + } else if(_commandLineDpiScalingPolicy != Implementation::Sdl2DpiScalingPolicy::Default) { + dpiScalingPolicy = _commandLineDpiScalingPolicy; + } else if(!configuration.dpiScaling().isZero()) { + Debug{verbose} << "Platform::Sdl2Application: app-defined DPI scaling" << configuration.dpiScaling(); + return configuration.dpiScaling(); + } else { + dpiScalingPolicy = configuration.dpiScalingPolicy(); + } + + /* There's no choice on Apple, it's all controlled by the plist file. So + unless someone specified custom scaling via config or command-line + above, return the default. */ + #ifdef CORRADE_TARGET_APPLE + return Vector2{1.0f}; + + /* Otherwise there's a choice between virtual and physical DPI scaling */ + #else + /* Try to get virtual DPI scaling first, if supported and requested */ + #if !defined(CORRADE_TARGET_EMSCRIPTEN) && !defined(CORRADE_TARGET_ANDROID) + if(dpiScalingPolicy == Implementation::Sdl2DpiScalingPolicy::Virtual) { + /* Use Xft.dpi on X11, because SDL_GetDisplayDPI() returns the useless + physical value on Linux, while the virtual value on Windows. */ + #ifdef _MAGNUM_PLATFORM_USE_X11 + const Vector2 dpiScaling{Implementation::x11DpiScaling()}; + if(!dpiScaling.isZero()) { + Debug{verbose} << "Platform::Sdl2Application: virtual DPI scaling" << dpiScaling.x(); + return dpiScaling; + } + + /* Check for DPI awareness on (non-RT) Windows and then ask for DPI. + SDL_GetDisplayDPI() is querying GetDpiForMonitor() -- + https://github.com/spurious/SDL-mirror/blob/17af4584cb28cdb3c2feba17e7d989a806007d9f/src/video/windows/SDL_windowsmodes.c#L266 + and GetDpiForMonitor() returns 96 if the application is DPI unaware. + So we instead check for DPI awareness first (and tell the user if + not), and only if the app is, then we use SDL_GetDisplayDPI(). */ + #elif defined(CORRADE_TARGET_WINDOWS) && !defined(CORRADE_TARGET_WINDOWS_RT) + if(!Implementation::isWindowsAppDpiAware()) { + Warning{verbose} << "Platform::Sdl2Application: your application is not set as DPI-aware, DPI scaling won't be used"; + return Vector2{1.0f}; + } + Vector2 dpi; + if(SDL_GetDisplayDPI(0, nullptr, &dpi.x(), &dpi.y()) == 0) { + const Vector2 dpiScaling{dpi/96.0f}; + Debug{verbose} << "Platform::Sdl2Application: virtual DPI scaling" << dpiScaling; + return dpiScaling; + } + + /* Otherwise ¯\_(ツ)_/¯ */ + #else + Debug{verbose} << "Platform::Sdl2Application: sorry, virtual DPI scaling not implemented on this platform yet, falling back to physical DPI scaling"; + #endif + } + #endif + + /* At this point, either the virtual DPI query failed or a physical DPI + scaling is requested */ + #if !defined(CORRADE_TARGET_EMSCRIPTEN) && !defined(CORRADE_TARGET_ANDROID) + CORRADE_INTERNAL_ASSERT(dpiScalingPolicy == Implementation::Sdl2DpiScalingPolicy::Virtual || dpiScalingPolicy == Implementation::Sdl2DpiScalingPolicy::Physical); + #else + CORRADE_INTERNAL_ASSERT(dpiScalingPolicy == Implementation::Sdl2DpiScalingPolicy::Physical); + #endif + + /* Take device pixel ratio on Emscripten */ + #ifdef CORRADE_TARGET_EMSCRIPTEN + const Vector2 dpiScaling{Implementation::emscriptenDpiScaling()}; + Debug{verbose} << "Platform::Sdl2Application: physical DPI scaling" << dpiScaling.x(); + return dpiScaling; + + /* Take a physical display DPI. On Linux it gets the (usually very off) + physical value from X11. Also only since SDL 2.0.4. */ + #elif defined(CORRADE_TARGET_UNIX) + #if SDL_VERSION_ATLEAST(2, 0, 4) + Vector2 dpi; + if(SDL_GetDisplayDPI(0, nullptr, &dpi.x(), &dpi.y()) == 0) { + const Vector2 dpiScaling{dpi/96.0f}; + Debug{verbose} << "Platform::Sdl2Application: physical DPI scaling" << dpiScaling; + return dpiScaling; + } + + Warning{} << "Platform::Sdl2Application: can't get physical display DPI, falling back to no scaling:" << SDL_GetError(); + #else + Debug{verbose} << "Platform::Sdl2Application: sorry, physical DPI scaling only available on SDL 2.0.4+"; + #endif + return Vector2{1.0f}; + + /* HOWEVER, on Windows it gets the virtual DPI scaling, which we don't + want, so we need to call Windows APIs directly instead. Consistency my + ass. Related bug report that will probably never get actually + implemented: https://bugzilla.libsdl.org/show_bug.cgi?id=2473 */ + #elif defined(CORRADE_TARGET_WINDOWS) && !defined(CORRADE_TARGET_WINDOWS_RT) + HDC hDC = GetWindowDC(nullptr); + Vector2i monitorSize{GetDeviceCaps(hDC, HORZSIZE), GetDeviceCaps(hDC, VERTSIZE)}; + SDL_DisplayMode mode; + CORRADE_INTERNAL_ASSERT(SDL_GetDesktopDisplayMode(0, &mode) == 0); + auto dpi = Vector2{Vector2i{mode.w, mode.h}*25.4f/Vector2{monitorSize}}; + const Vector2 dpiScaling{dpi/96.0f}; + Debug{verbose} << "Platform::Sdl2Application: physical DPI scaling" << dpiScaling; + return dpiScaling; + + /* Not implemented otherwise */ + #else + Debug{verbose} << "Platform::Sdl2Application: sorry, physical DPI scaling not implemented on this platform yet"; + return Vector2{1.0f}; + #endif + #endif +} + +void Sdl2Application::setWindowTitle(const Containers::StringView title) { + #ifndef CORRADE_TARGET_EMSCRIPTEN + SDL_SetWindowTitle(_window, + Containers::String::nullTerminatedGlobalView(title).data()); + #else + /* We don't have the _window because SDL_CreateWindow() doesn't exist in + the SDL1/2 hybrid. But it's not used anyway, so pass nullptr there. */ + SDL_SetWindowTitle(nullptr, + Containers::String::nullTerminatedGlobalView(title).data()); + #endif +} + +#if !defined(CORRADE_TARGET_EMSCRIPTEN) && SDL_MAJOR_VERSION*1000 + SDL_MINOR_VERSION*100 + SDL_PATCHLEVEL >= 2005 +void Sdl2Application::setWindowIcon(const ImageView2D& image) { + Uint32 format; /** @todo handle sRGB differently? */ + switch(image.format()) { + case PixelFormat::RGB8Srgb: + case PixelFormat::RGB8Unorm: + format = SDL_PIXELFORMAT_RGB24; + break; + case PixelFormat::RGBA8Srgb: + case PixelFormat::RGBA8Unorm: + format = SDL_PIXELFORMAT_RGBA32; + break; + default: + CORRADE_ASSERT_UNREACHABLE("Platform::Sdl2Application::setWindowIcon(): unexpected format" << image.format(), ); + } + + /* Images are loaded with origin at bottom left, flip it to top left. SDL + only accepted a negative stride until version 2.23.1 and commit + https://github.com/libsdl-org/SDL/commit/535fdc3adcdc08a193ab0d45540014fd536cf251 + so we need to manually flip the image now */ + /** @todo take ImageFlag::YUp into account once it exists */ + Image2D flippedImage{PixelStorage{}.setAlignment(1), image.format(), image.size(), Containers::Array{NoInit, std::size_t(image.size().product()*image.pixelSize())}}; + const Containers::StridedArrayView3D flippedPixels = flippedImage.pixels(); + Utility::copy(image.pixels().flipped<0>(), flippedPixels); + + SDL_Surface* const icon = SDL_CreateRGBSurfaceWithFormatFrom(const_cast(flippedPixels.data()) , flippedImage.size().x(), flippedImage.size().y(), 32, flippedPixels.stride()[0], format); + CORRADE_INTERNAL_ASSERT(icon); + + SDL_SetWindowIcon(_window, icon); + SDL_FreeSurface(icon); +} +#endif + +bool Sdl2Application::tryCreate(const Configuration& configuration) { + #ifdef MAGNUM_TARGET_GL + if(!(configuration.windowFlags() & Configuration::WindowFlag::Contextless)) + return tryCreate(configuration, GLConfiguration{}); + #endif + + #ifndef CORRADE_TARGET_EMSCRIPTEN + /* Scale window based on DPI */ + _dpiScaling = dpiScaling(configuration); + const Vector2i scaledWindowSize = configuration.size()*_dpiScaling; + + /* Create window */ + if(!(_window = SDL_CreateWindow( + #ifndef CORRADE_TARGET_IOS + configuration.title().data(), + #else + nullptr, + #endif + SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, + scaledWindowSize.x(), scaledWindowSize.y(), + SDL_WINDOW_ALLOW_HIGHDPI|SDL_WINDOW_OPENGL|Uint32(configuration.windowFlags() & ~Configuration::WindowFlag::Contextless)))) + { + Error() << "Platform::Sdl2Application::tryCreate(): cannot create window:" << SDL_GetError(); + return false; + } + + /* Emscripten-specific initialization */ + #else + /* Get CSS canvas size. This is used later to detect canvas resizes and + fire viewport events, because Emscripten doesn't do that. Related info: + https://github.com/kripken/emscripten/issues/1731 */ + /** @todo don't hardcode "#canvas" here, make it configurable from outside */ + { + Vector2d canvasSize; + /* Emscripten 1.38.27 changed to generic CSS selectors from element + IDs depending on -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 + being set (which we can't detect at compile time). Fortunately, + using #canvas works the same way both in the previous versions and + the current one. Unfortunately, this is also the only value that + works the same way for both. Further details at + https://github.com/emscripten-core/emscripten/pull/7977 */ + emscripten_get_element_css_size("#canvas", &canvasSize.x(), &canvasSize.y()); + _lastKnownCanvasSize = Vector2i{canvasSize}; + } + + /* By default Emscripten creates a 300x150 canvas. That's so freaking + random I'm getting mad. Use the real (CSS pixels) canvas size instead, + if the size is not hardcoded from the configuration. This is then + multiplied by the DPI scaling. */ + Vector2i windowSize; + if(!configuration.size().isZero()) { + windowSize = configuration.size(); + /* Because hardcoding canvas size for WebGL is usually a wrong thing to + do, notify about that in the verbose output */ + Debug{_verboseLog ? Debug::output() : nullptr} << "Platform::Sdl2Application::tryCreate(): hardcoded canvas size" << windowSize; + } else { + windowSize = _lastKnownCanvasSize; + Debug{_verboseLog ? Debug::output() : nullptr} << "Platform::Sdl2Application::tryCreate(): autodetected canvas size" << windowSize; + } + _dpiScaling = dpiScaling(configuration); + const Vector2i scaledWindowSize = windowSize*_dpiScaling; + + Uint32 flags = SDL_OPENGL|SDL_HWSURFACE|SDL_DOUBLEBUF; + if(configuration.windowFlags() & Configuration::WindowFlag::Resizable) { + _flags |= Flag::Resizable; + /* Actually not sure if this makes any difference: + https://github.com/kripken/emscripten/issues/1731 */ + flags |= SDL_RESIZABLE; + } + + if(!(_surface = SDL_SetVideoMode(scaledWindowSize.x(), scaledWindowSize.y(), 24, flags))) { + Error() << "Platform::Sdl2Application::tryCreate(): cannot create context:" << SDL_GetError(); + return false; + } + #endif + + return true; +} + +#ifdef MAGNUM_TARGET_GL +bool Sdl2Application::tryCreate(const Configuration& configuration, const GLConfiguration& glConfiguration) { + CORRADE_ASSERT(_context->version() == GL::Version::None, "Platform::Sdl2Application::tryCreate(): context already created", false); + + /* Enable double buffering, set up buffer sizes */ + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_GL_SetAttribute(SDL_GL_RED_SIZE, glConfiguration.colorBufferSize().r()); + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, glConfiguration.colorBufferSize().g()); + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, glConfiguration.colorBufferSize().b()); + SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, glConfiguration.colorBufferSize().a()); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, glConfiguration.depthBufferSize()); + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, glConfiguration.stencilBufferSize()); + + /* Multisampling */ + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, glConfiguration.sampleCount() > 1 ? 1 : 0); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, glConfiguration.sampleCount()); + + #ifndef CORRADE_TARGET_EMSCRIPTEN + /* sRGB */ + SDL_GL_SetAttribute(SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, glConfiguration.isSrgbCapable()); + #endif + + /** @todo Remove when Emscripten has proper SDL2 support */ + #ifndef CORRADE_TARGET_EMSCRIPTEN + /* Scale window based on DPI */ + _dpiScaling = dpiScaling(configuration); + const Vector2i scaledWindowSize = configuration.size()*_dpiScaling; + + /* Request debug context if GpuValidation is enabled either via the + configuration or via command-line */ + GLConfiguration::Flags glFlags = glConfiguration.flags(); + if((glFlags & GLConfiguration::Flag::GpuValidation) || (_context->configurationFlags() & GL::Context::Configuration::Flag::GpuValidation)) + glFlags |= GLConfiguration::Flag::Debug; + #if SDL_MAJOR_VERSION*1000 + SDL_MINOR_VERSION*100 + SDL_PATCHLEVEL >= 2006 + else if((glFlags & GLConfiguration::Flag::GpuValidationNoError) || (_context->configurationFlags() & GL::Context::Configuration::Flag::GpuValidationNoError)) + glFlags |= GLConfiguration::Flag::NoError; + #endif + + #if SDL_MAJOR_VERSION*1000 + SDL_MINOR_VERSION*100 + SDL_PATCHLEVEL >= 2006 + SDL_GL_SetAttribute(SDL_GL_CONTEXT_NO_ERROR, glFlags >= GLConfiguration::Flag::NoError); + #endif + + /* Set context version, if user-specified */ + if(glConfiguration.version() != GL::Version::None) { + Int major, minor; + std::tie(major, minor) = version(glConfiguration.version()); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, major); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, minor); + + #ifndef MAGNUM_TARGET_GLES + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, glConfiguration.version() >= GL::Version::GL310 ? + SDL_GL_CONTEXT_PROFILE_CORE : SDL_GL_CONTEXT_PROFILE_COMPATIBILITY); + #else + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); + #endif + + /* Mask out the upper 32bits used for other flags */ + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, int(UnsignedLong(glFlags) & 0xffffffffu)); + + /* Request usable version otherwise */ + } else { + #ifndef MAGNUM_TARGET_GLES + /* First try to create core context. This is needed mainly on macOS and + Mesa, as support for recent OpenGL versions isn't implemented in + compatibility contexts (which are the default). At least GL 3.2 is + needed on macOS, at least GL 3.1 is needed on Mesa. Bite the bullet + and try 3.1 also elsewhere. */ + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + #ifdef CORRADE_TARGET_APPLE + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); + #else + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); + #endif + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + /* Mask out the upper 32bits used for other flags */ + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, int(UnsignedLong(glFlags) & 0xffffffffu)); + #else + /* For ES the major context version is compile-time constant */ + #ifdef MAGNUM_TARGET_GLES3 + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + #elif defined(MAGNUM_TARGET_GLES2) + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); + #else + #error unsupported OpenGL ES version + #endif + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); + #endif + } + + /* Create window. Hide it by default so we don't have distracting window + blinking in case we have to destroy it again right away */ + if(!(_window = SDL_CreateWindow( + #ifndef CORRADE_TARGET_IOS + configuration.title().data(), + #else + nullptr, + #endif + SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, + scaledWindowSize.x(), scaledWindowSize.y(), + SDL_WINDOW_OPENGL|SDL_WINDOW_HIDDEN|SDL_WINDOW_ALLOW_HIGHDPI|Uint32(configuration.windowFlags())))) + { + Error() << "Platform::Sdl2Application::tryCreate(): cannot create window:" << SDL_GetError(); + return false; + } + + /* Create context */ + _glContext = SDL_GL_CreateContext(_window); + + #ifndef MAGNUM_TARGET_GLES + /* Fall back to (forward compatible) GL 2.1, if version is not + user-specified and either core context creation fails or we are on + binary NVidia/AMD drivers on Linux/Windows or Intel Windows drivers. + Instead of creating forward-compatible context with highest available + version, they force the version to the one specified, which is + completely useless behavior. */ + #ifndef CORRADE_TARGET_APPLE + Containers::StringView vendorString; + #endif + if(glConfiguration.version() == GL::Version::None && (!_glContext + #ifndef CORRADE_TARGET_APPLE + /* Sorry about the UGLY code, HOPEFULLY THERE WON'T BE MORE WORKAROUNDS */ + || (vendorString = reinterpret_cast(glGetString(GL_VENDOR)), + (vendorString == "NVIDIA Corporation"_s || + #ifdef CORRADE_TARGET_WINDOWS + vendorString == "Intel"_s || + #endif + vendorString == "ATI Technologies Inc."_s) + && !_context->isDriverWorkaroundDisabled("no-forward-compatible-core-context"_s)) + #endif + )) { + /* Don't print any warning when doing the workaround, because the bug + will be there probably forever */ + if(!_glContext) Warning() + << "Platform::Sdl2Application::tryCreate(): cannot create core context:" + << SDL_GetError() << "(falling back to compatibility context)"; + else SDL_GL_DeleteContext(_glContext); + + SDL_DestroyWindow(_window); + + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY); + /* Discard the ForwardCompatible flag for the fallback. Having it set + makes the fallback context creation fail on Mesa's Zink (which is + just 2.1) and I assume on others as well. + + Also mask out the upper 32bits used for other flags. */ + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, int(UnsignedLong(glFlags & ~GLConfiguration::Flag::ForwardCompatible) & 0xffffffffu)); + + if(!(_window = SDL_CreateWindow(configuration.title().data(), + SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, + scaledWindowSize.x(), scaledWindowSize.y(), + SDL_WINDOW_OPENGL|SDL_WINDOW_HIDDEN|SDL_WINDOW_ALLOW_HIGHDPI|Uint32(configuration.windowFlags()&~Configuration::WindowFlag::Contextless)))) + { + Error() << "Platform::Sdl2Application::tryCreate(): cannot create window:" << SDL_GetError(); + return false; + } + + /* Create compatibility context */ + _glContext = SDL_GL_CreateContext(_window); + } + #endif + + /* Cannot create context (or fallback compatibility context on desktop) */ + if(!_glContext) { + Error() << "Platform::Sdl2Application::tryCreate(): cannot create context:" << SDL_GetError(); + SDL_DestroyWindow(_window); + _window = nullptr; + return false; + } + + #ifdef CORRADE_TARGET_IOS + /* iOS has zero initial GL_VIEWPORT size, get drawable size and put it back + in so all other code can assume that the viewport is set to sane values. + Fortunately on iOS we also don't have to load any function pointers so + it's safe to do the glViewport() call as it is linked statically. */ + { + const Vector2i viewport = framebufferSize(); + glViewport(0, 0, viewport.x(), viewport.y()); + } + #endif + + /* Emscripten-specific initialization */ + #else + /* Get CSS canvas size. This is used later to detect canvas resizes and + fire viewport events, because Emscripten doesn't do that. Related info: + https://github.com/kripken/emscripten/issues/1731 */ + /** @todo don't hardcode "#canvas" here, make it configurable from outside */ + { + Vector2d canvasSize; + /* Emscripten 1.38.27 changed to generic CSS selectors from element + IDs depending on -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 + being set (which we can't detect at compile time). Fortunately, + using #canvas works the same way both in the previous versions and + the current one. Unfortunately, this is also the only value that + works the same way for both. Further details at + https://github.com/emscripten-core/emscripten/pull/7977 */ + emscripten_get_element_css_size("#canvas", &canvasSize.x(), &canvasSize.y()); + _lastKnownCanvasSize = Vector2i{canvasSize}; + } + + /* By default Emscripten creates a 300x150 canvas. That's so freaking + random I'm getting mad. Use the real (CSS pixels) canvas size instead, + if the size is not hardcoded from the configuration. This is then + multiplied by the DPI scaling. */ + Vector2i windowSize; + if(!configuration.size().isZero()) { + windowSize = configuration.size(); + /* Because hardcoding canvas size for WebGL is usually a wrong thing to + do, notify about that in the verbose output */ + Debug{_verboseLog ? Debug::output() : nullptr} << "Platform::Sdl2Application::tryCreate(): hardcoded canvas size" << windowSize; + } else { + windowSize = _lastKnownCanvasSize; + Debug{_verboseLog ? Debug::output() : nullptr} << "Platform::Sdl2Application::tryCreate(): autodetected canvas size" << windowSize; + } + _dpiScaling = dpiScaling(configuration); + const Vector2i scaledWindowSize = windowSize*_dpiScaling; + + Uint32 flags = SDL_OPENGL|SDL_HWSURFACE|SDL_DOUBLEBUF; + if(configuration.windowFlags() & Configuration::WindowFlag::Resizable) { + _flags |= Flag::Resizable; + /* Actually not sure if this makes any difference: + https://github.com/kripken/emscripten/issues/1731 */ + flags |= SDL_RESIZABLE; + } + + if(!(_surface = SDL_SetVideoMode(scaledWindowSize.x(), scaledWindowSize.y(), 24, flags))) { + Error() << "Platform::Sdl2Application::tryCreate(): cannot create context:" << SDL_GetError(); + return false; + } + #endif + + /* Destroy everything also when the Magnum context creation fails */ + if(!_context->tryCreate(glConfiguration)) { + #ifndef CORRADE_TARGET_EMSCRIPTEN + SDL_GL_DeleteContext(_glContext); + SDL_DestroyWindow(_window); + _window = nullptr; + #else + SDL_FreeSurface(_surface); + #endif + return false; + } + + #ifndef CORRADE_TARGET_EMSCRIPTEN + /* Show the window once we are sure that everything is okay */ + if(!(configuration.windowFlags() & Configuration::WindowFlag::Hidden)) + SDL_ShowWindow(_window); + #endif + + /* Return true if the initialization succeeds */ + return true; +} +#endif + +Vector2i Sdl2Application::windowSize() const { + Vector2i size; + #ifndef CORRADE_TARGET_EMSCRIPTEN + CORRADE_ASSERT(_window, "Platform::Sdl2Application::windowSize(): no window opened", {}); + SDL_GetWindowSize(_window, &size.x(), &size.y()); + #else + CORRADE_ASSERT(_surface, "Platform::Sdl2Application::windowSize(): no window opened", {}); + emscripten_get_canvas_element_size("#canvas", &size.x(), &size.y()); + #endif + return size; +} + +#ifndef CORRADE_TARGET_EMSCRIPTEN +void Sdl2Application::setWindowSize(const Vector2i& size) { + CORRADE_ASSERT(_window, "Platform::Sdl2Application::setWindowSize(): no window opened", ); + + const Vector2i newSize = _dpiScaling*size; + SDL_SetWindowSize(_window, newSize.x(), newSize.y()); +} + +void Sdl2Application::setMinWindowSize(const Vector2i& size) { + CORRADE_ASSERT(_window, "Platform::Sdl2Application::setMinWindowSize(): no window opened", ); + + const Vector2i newSize = _dpiScaling*size; + SDL_SetWindowMinimumSize(_window, newSize.x(), newSize.y()); +} + +void Sdl2Application::setMaxWindowSize(const Vector2i& size) { + CORRADE_ASSERT(_window, "Platform::Sdl2Application::setMaxWindowSize(): no window opened", ); + + const Vector2i newSize = _dpiScaling*size; + SDL_SetWindowMaximumSize(_window, newSize.x(), newSize.y()); +} +#endif + +#ifdef MAGNUM_TARGET_GL +Vector2i Sdl2Application::framebufferSize() const { + Vector2i size; + #ifndef CORRADE_TARGET_EMSCRIPTEN + CORRADE_ASSERT(_window, "Platform::Sdl2Application::framebufferSize(): no window opened", {}); + SDL_GL_GetDrawableSize(_window, &size.x(), &size.y()); + #else + CORRADE_ASSERT(_surface, "Platform::Sdl2Application::framebufferSize(): no window opened", {}); + emscripten_get_canvas_element_size("#canvas", &size.x(), &size.y()); + #endif + return size; +} +#endif + +#ifdef CORRADE_TARGET_EMSCRIPTEN +void Sdl2Application::setContainerCssClass(const Containers::StringView cssClass) { + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdollar-in-identifier-extension" + EM_ASM_({ + /* Handle also the classic #container for backwards compatibility. We + also need to preserve the mn-container otherwise next time we'd have + no way to look for it anymore. + + Using UTF8ToString() instead of AsciiToString() as it has an + explicit size parameter and thus doesn't need a null-terminated + input, which would potentially require yet another allocation. */ + (Module['canvas'].closest('.mn-container') || + document.getElementById('container')).className = (['mn-container', UTF8ToString($0, $1)]).join(' '); + }, cssClass.data(), cssClass.size()); + #pragma GCC diagnostic pop +} +#endif + +void Sdl2Application::swapBuffers() { + #ifndef CORRADE_TARGET_EMSCRIPTEN + SDL_GL_SwapWindow(_window); + #else + SDL_Flip(_surface); + #endif +} + +Int Sdl2Application::swapInterval() const { + return SDL_GL_GetSwapInterval(); +} + +bool Sdl2Application::setSwapInterval(const Int interval) { + if(SDL_GL_SetSwapInterval(interval) == -1) { + Error() << "Platform::Sdl2Application::setSwapInterval(): cannot set swap interval:" << SDL_GetError(); + _flags &= ~Flag::VSyncEnabled; + return false; + } + + /* Setting interval to 1 may cause SDL_GL_GetSwapInterval() to return -1, + which is a valid case */ + const Int actualInterval = SDL_GL_GetSwapInterval(); + if(actualInterval != interval && !(interval == 1 && actualInterval == -1)) { + Error() << "Platform::Sdl2Application::setSwapInterval(): swap interval setting ignored by the driver:" << SDL_GetError(); + _flags &= ~Flag::VSyncEnabled; + return false; + } + + if(interval) _flags |= Flag::VSyncEnabled; + else _flags &= ~Flag::VSyncEnabled; + return true; +} + +void Sdl2Application::redraw() { _flags |= Flag::Redraw; } + +Sdl2Application::~Sdl2Application() { + /* SDL_DestroyWindow(_window) crashes on windows when _window is nullptr + (it doesn't seem to crash on Linux). Because this seems to be yet + another pointless platform difference, to be safe do the same check with + all. */ + + #ifdef MAGNUM_TARGET_GL + /* Destroy Magnum context first to avoid it potentially accessing the + now-destroyed GL context after */ + _context = Containers::NullOpt; + + #ifndef CORRADE_TARGET_EMSCRIPTEN + if(_glContext) SDL_GL_DeleteContext(_glContext); + #else + if(_surface) SDL_FreeSurface(_surface); + #endif + #endif + + #ifndef CORRADE_TARGET_EMSCRIPTEN + for(auto& cursor: _cursors) + SDL_FreeCursor(cursor); + #endif + + #ifndef CORRADE_TARGET_EMSCRIPTEN + if(_window) SDL_DestroyWindow(_window); + #endif + SDL_Quit(); +} + +int Sdl2Application::exec() { + #ifndef CORRADE_TARGET_EMSCRIPTEN + while(mainLoopIteration()) {} + #else + emscripten_set_main_loop_arg([](void* arg) { + static_cast(arg)->mainLoopIteration(); + }, this, 0, true); + #endif + return _exitCode; +} + +void Sdl2Application::exit(const int exitCode) { + /* On Emscripten this flag is used only to indicate a desire to exit from + mainLoopIteration() */ + _flags |= Flag::Exit; + #ifdef CORRADE_TARGET_EMSCRIPTEN + emscripten_cancel_main_loop(); + #endif + _exitCode = exitCode; +} + +bool Sdl2Application::mainLoopIteration() { + /* If exit was requested directly in the constructor, exit immediately + without calling anything else */ + if(_flags & Flag::Exit) return false; + + #ifndef CORRADE_TARGET_EMSCRIPTEN + CORRADE_ASSERT(_window, "Platform::Sdl2Application::mainLoopIteration(): no window opened", {}); + #else + CORRADE_ASSERT(_surface, "Platform::Sdl2Application::mainLoopIteration(): no window opened", {}); + #endif + + #ifndef CORRADE_TARGET_EMSCRIPTEN + const UnsignedInt timeBefore = _minimalLoopPeriod ? SDL_GetTicks() : 0; + #endif + + #ifdef CORRADE_TARGET_EMSCRIPTEN + /* The resize event is not fired on window resize, so poll for the canvas + size here. But only if the window was requested to be resizable, to + avoid resizing the canvas when the user doesn't want that. Related + issue: https://github.com/kripken/emscripten/issues/1731 */ + if(_flags & Flag::Resizable) { + Vector2d canvasSize; + /* Emscripten 1.38.27 changed to generic CSS selectors from element + IDs depending on -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 + being set (which we can't detect at compile time). See above for the + reason why we hardcode #canvas here. */ + emscripten_get_element_css_size("#canvas", &canvasSize.x(), &canvasSize.y()); + + const Vector2i canvasSizei{canvasSize}; + if(canvasSizei != _lastKnownCanvasSize) { + _lastKnownCanvasSize = canvasSizei; + const Vector2i size = _dpiScaling*canvasSizei; + emscripten_set_canvas_element_size("#canvas", size.x(), size.y()); + ViewportEvent e{ + #ifdef MAGNUM_TARGET_GL + size, + #endif + size, _dpiScaling}; + viewportEvent(e); + _flags |= Flag::Redraw; + } + } + #endif + + SDL_Event event; + while(SDL_PollEvent(&event)) { + switch(event.type) { + case SDL_WINDOWEVENT: + switch(event.window.event) { + /* Not using SDL_WINDOWEVENT_RESIZED, because that doesn't + get fired when the window is resized programmatically + (such as through setMaxWindowSize()) */ + case SDL_WINDOWEVENT_SIZE_CHANGED: { + #ifdef CORRADE_TARGET_EMSCRIPTEN + /* If anybody sees this assert, then emscripten finally + implemented resize events. Praise them for that. + https://github.com/kripken/emscripten/issues/1731 */ + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + #else + /* {event.window.data1, event.window.data2} seems to be + framebuffer size and not window size on macOS, which + is weird. Query the values directly instead to be + really sure. */ + ViewportEvent e{event, windowSize(), + #ifdef MAGNUM_TARGET_GL + framebufferSize(), + #endif + _dpiScaling}; + /** @todo handle also WM_DPICHANGED events when a window is moved between displays with different DPI */ + viewportEvent(e); + _flags |= Flag::Redraw; + #endif + } break; + /* Direct everything that wasn't exposed via a callback to + anyEvent(), so users can implement event handling for + things not present in the Application APIs */ + case SDL_WINDOWEVENT_EXPOSED: + _flags |= Flag::Redraw; + if(!(_flags & Flag::NoAnyEvent)) anyEvent(event); + break; + default: + if(!(_flags & Flag::NoAnyEvent)) anyEvent(event); + } break; + + case SDL_KEYDOWN: + case SDL_KEYUP: { + KeyEvent e{event, static_cast(event.key.keysym.sym), fixedModifiers(event.key.keysym.mod), event.key.repeat != 0}; + event.type == SDL_KEYDOWN ? keyPressEvent(e) : keyReleaseEvent(e); + } break; + + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: { + MouseEvent e{event, static_cast(event.button.button), {event.button.x, event.button.y} + #ifndef CORRADE_TARGET_EMSCRIPTEN + , event.button.clicks + #endif + }; + event.type == SDL_MOUSEBUTTONDOWN ? mousePressEvent(e) : mouseReleaseEvent(e); + } break; + + case SDL_MOUSEWHEEL: { + MouseScrollEvent e{event, {Float(event.wheel.x), Float(event.wheel.y)}}; + mouseScrollEvent(e); + } break; + + case SDL_MOUSEMOTION: { + MouseMoveEvent e{event, {event.motion.x, event.motion.y}, {event.motion.xrel, event.motion.yrel}, static_cast(event.motion.state)}; + mouseMoveEvent(e); + break; + } + + case SDL_MULTIGESTURE: { + MultiGestureEvent e{event, {event.mgesture.x, event.mgesture.y}, event.mgesture.dTheta, event.mgesture.dDist, event.mgesture.numFingers}; + multiGestureEvent(e); + break; + } + + case SDL_TEXTINPUT: { + TextInputEvent e{event, event.text.text}; + textInputEvent(e); + } break; + + case SDL_TEXTEDITING: { + TextEditingEvent e{event, event.edit.text, event.edit.start, event.edit.length}; + textEditingEvent(e); + } break; + + case SDL_QUIT: { + ExitEvent e{event}; + exitEvent(e); + if(e.isAccepted()) { + /* On Emscripten this flag is used only to indicate a + desire to exit from mainLoopIteration() */ + _flags |= Flag::Exit; + #ifdef CORRADE_TARGET_EMSCRIPTEN + emscripten_cancel_main_loop(); + #endif + return !(_flags & Flag::Exit); + } + } break; + + /* Direct everything else to anyEvent(), so users can implement + event handling for things not present in the Application APIs */ + default: if(!(_flags & Flag::NoAnyEvent)) anyEvent(event); + } + } + + /* Tick event */ + if(!(_flags & Flag::NoTickEvent)) tickEvent(); + + /* Draw event */ + if(_flags & Flag::Redraw) { + _flags &= ~Flag::Redraw; + drawEvent(); + + #ifndef CORRADE_TARGET_EMSCRIPTEN + /* If VSync is not enabled, delay to prevent CPU hogging (if set) */ + if(!(_flags & Flag::VSyncEnabled) && _minimalLoopPeriod) { + const UnsignedInt loopTime = SDL_GetTicks() - timeBefore; + if(loopTime < _minimalLoopPeriod) + SDL_Delay(_minimalLoopPeriod - loopTime); + } + #endif + + return !(_flags & Flag::Exit); + } + + #ifndef CORRADE_TARGET_EMSCRIPTEN + /* If not drawing anything, delay to prevent CPU hogging (if set) */ + if(_minimalLoopPeriod) { + const UnsignedInt loopTime = SDL_GetTicks() - timeBefore; + if(loopTime < _minimalLoopPeriod) + SDL_Delay(_minimalLoopPeriod - loopTime); + } + + /* Then, if the tick event doesn't need to be called periodically, wait + indefinitely for next input event */ + if(_flags & Flag::NoTickEvent) SDL_WaitEvent(nullptr); + #endif + return !(_flags & Flag::Exit); +} + +namespace { + +#ifndef CORRADE_TARGET_EMSCRIPTEN +constexpr SDL_SystemCursor CursorMap[] { + SDL_SYSTEM_CURSOR_ARROW, + SDL_SYSTEM_CURSOR_IBEAM, + SDL_SYSTEM_CURSOR_WAIT, + SDL_SYSTEM_CURSOR_CROSSHAIR, + SDL_SYSTEM_CURSOR_WAITARROW, + SDL_SYSTEM_CURSOR_SIZENWSE, + SDL_SYSTEM_CURSOR_SIZENESW, + SDL_SYSTEM_CURSOR_SIZEWE, + SDL_SYSTEM_CURSOR_SIZENS, + SDL_SYSTEM_CURSOR_SIZEALL, + SDL_SYSTEM_CURSOR_NO, + SDL_SYSTEM_CURSOR_HAND +}; +#else +constexpr const char* CursorMap[] { + "default", + "text", + "wait", + "crosshair", + "progress", + "nwse-resize", + "nesw-resize", + "ew-resize", + "ns-resize", + "move", + "not-allowed", + "pointer", + "none" + /* Hidden & locked not supported yet */ +}; +#endif + +} + +void Sdl2Application::setCursor(Cursor cursor) { + #ifndef CORRADE_TARGET_EMSCRIPTEN + CORRADE_ASSERT(_window, "Platform::Sdl2Application::setCursor(): no window opened", ); + + if(cursor == Cursor::Hidden) { + SDL_ShowCursor(SDL_DISABLE); + SDL_SetWindowGrab(_window, SDL_FALSE); + SDL_SetRelativeMouseMode(SDL_FALSE); + return; + } else if(cursor == Cursor::HiddenLocked) { + SDL_SetWindowGrab(_window, SDL_TRUE); + SDL_SetRelativeMouseMode(SDL_TRUE); + return; + } else { + SDL_ShowCursor(SDL_ENABLE); + SDL_SetWindowGrab(_window, SDL_FALSE); + SDL_SetRelativeMouseMode(SDL_FALSE); + } + + /* The second condition could be a static assert but it doesn't let me + because "this pointer only accessible in a constexpr function". Thanks + for nothing, C++. */ + CORRADE_INTERNAL_ASSERT(UnsignedInt(cursor) < Containers::arraySize(_cursors) && Containers::arraySize(_cursors) == Containers::arraySize(CursorMap)); + + if(!_cursors[UnsignedInt(cursor)]) + _cursors[UnsignedInt(cursor)] = SDL_CreateSystemCursor(CursorMap[UnsignedInt(cursor)]); + + SDL_SetCursor(_cursors[UnsignedInt(cursor)]); + #else + CORRADE_ASSERT(_surface, "Platform::Sdl2Application::setCursor(): no window opened", ); + _cursor = cursor; + CORRADE_INTERNAL_ASSERT(UnsignedInt(cursor) < Containers::arraySize(CursorMap)); + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdollar-in-identifier-extension" + EM_ASM_({Module['canvas'].style.cursor = AsciiToString($0);}, CursorMap[UnsignedInt(cursor)]); + #pragma GCC diagnostic pop + #endif +} + +Sdl2Application::Cursor Sdl2Application::cursor() { + #ifndef CORRADE_TARGET_EMSCRIPTEN + if(SDL_GetRelativeMouseMode()) + return Cursor::HiddenLocked; + else if(!SDL_ShowCursor(SDL_QUERY)) + return Cursor::Hidden; + + SDL_Cursor* cursor = SDL_GetCursor(); + + if(cursor) for(UnsignedInt i = 0; i < sizeof(_cursors); i++) + if(_cursors[i] == cursor) return Cursor(i); + + return Cursor::Arrow; + #else + return _cursor; + #endif +} + +#ifdef MAGNUM_BUILD_DEPRECATED +void Sdl2Application::setMouseLocked(bool enabled) { + /** @todo Implement this in Emscripten */ + #ifndef CORRADE_TARGET_EMSCRIPTEN + SDL_SetWindowGrab(_window, enabled ? SDL_TRUE : SDL_FALSE); + SDL_SetRelativeMouseMode(enabled ? SDL_TRUE : SDL_FALSE); + #else + CORRADE_ASSERT_UNREACHABLE("Sdl2Application::setMouseLocked(): not implemented", ); + static_cast(enabled); + #endif +} +#endif + +bool Sdl2Application::isTextInputActive() { + #ifndef CORRADE_TARGET_EMSCRIPTEN + return SDL_IsTextInputActive(); + #else + return !!(_flags & Flag::TextInputActive); + #endif +} + +void Sdl2Application::startTextInput() { + SDL_StartTextInput(); + #ifdef CORRADE_TARGET_EMSCRIPTEN + _flags |= Flag::TextInputActive; + #endif +} + +void Sdl2Application::stopTextInput() { + SDL_StopTextInput(); + #ifdef CORRADE_TARGET_EMSCRIPTEN + _flags &= ~Flag::TextInputActive; + #endif +} + +void Sdl2Application::setTextInputRect(const Range2Di& rect) { + SDL_Rect r{rect.min().x(), rect.min().y(), rect.sizeX(), rect.sizeY()}; + SDL_SetTextInputRect(&r); +} + +void Sdl2Application::exitEvent(ExitEvent& event) { + event.setAccepted(); +} + +void Sdl2Application::tickEvent() { + /* If this got called, the tick event is not implemented by user and thus + we don't need to call it ever again */ + _flags |= Flag::NoTickEvent; +} + +void Sdl2Application::anyEvent(SDL_Event&) { + /* If this got called, the any event is not implemented by user and thus + we don't need to call it ever again */ + _flags |= Flag::NoAnyEvent; +} + +void Sdl2Application::viewportEvent(ViewportEvent&) {} +void Sdl2Application::keyPressEvent(KeyEvent&) {} +void Sdl2Application::keyReleaseEvent(KeyEvent&) {} +void Sdl2Application::mousePressEvent(MouseEvent&) {} +void Sdl2Application::mouseReleaseEvent(MouseEvent&) {} +void Sdl2Application::mouseMoveEvent(MouseMoveEvent&) {} +void Sdl2Application::mouseScrollEvent(MouseScrollEvent&) {} +void Sdl2Application::multiGestureEvent(MultiGestureEvent&) {} +void Sdl2Application::textInputEvent(TextInputEvent&) {} +void Sdl2Application::textEditingEvent(TextEditingEvent&) {} + +#ifdef MAGNUM_TARGET_GL +Sdl2Application::GLConfiguration::GLConfiguration(): + _colorBufferSize{8, 8, 8, 8}, _depthBufferSize{24}, _stencilBufferSize{0}, + _sampleCount(0) + #ifndef CORRADE_TARGET_EMSCRIPTEN + , _version{GL::Version::None}, _srgbCapable{false} + #endif +{ + #ifndef MAGNUM_TARGET_GLES + addFlags(Flag::ForwardCompatible); + #endif +} + +Sdl2Application::GLConfiguration::~GLConfiguration() = default; +#endif + +Sdl2Application::Configuration::Configuration(): + #if !defined(CORRADE_TARGET_EMSCRIPTEN) && !defined(CORRADE_TARGET_IOS) + _title(Containers::String::nullTerminatedGlobalView("Magnum SDL2 Application"_s)), + #endif + #if !defined(CORRADE_TARGET_IOS) && !defined(CORRADE_TARGET_EMSCRIPTEN) + _size{800, 600}, + #else + _size{}, /* SDL2 detects someting for us */ + #endif + _dpiScalingPolicy{DpiScalingPolicy::Default} {} + +Sdl2Application::Configuration::~Configuration() = default; + +Containers::StringView Sdl2Application::KeyEvent::keyName(const Key key) { + return SDL_GetKeyName(SDL_Keycode(key)); +} + +Containers::StringView Sdl2Application::KeyEvent::keyName() const { + return keyName(_key); +} + +Sdl2Application::InputEvent::Modifiers Sdl2Application::MouseEvent::modifiers() { + if(_modifiers) return *_modifiers; + return *(_modifiers = fixedModifiers(Uint16(SDL_GetModState()))); +} + +Sdl2Application::InputEvent::Modifiers Sdl2Application::MouseMoveEvent::modifiers() { + if(_modifiers) return *_modifiers; + return *(_modifiers = fixedModifiers(Uint16(SDL_GetModState()))); +} + +Vector2i Sdl2Application::MouseScrollEvent::position() { + if(_position) return *_position; + _position = Vector2i{}; + SDL_GetMouseState(&_position->x(), &_position->y()); + return *_position; +} + +Sdl2Application::InputEvent::Modifiers Sdl2Application::MouseScrollEvent::modifiers() { + if(_modifiers) return *_modifiers; + return *(_modifiers = fixedModifiers(Uint16(SDL_GetModState()))); +} + +template class BasicScreen; +template class BasicScreenedApplication; + +}} diff --git a/src/Sdl2Application.h b/src/Sdl2Application.h new file mode 100644 index 0000000..aac12d8 --- /dev/null +++ b/src/Sdl2Application.h @@ -0,0 +1,2967 @@ +#ifndef Magnum_Platform_Sdl2Application_h +#define Magnum_Platform_Sdl2Application_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 Vladimír Vondruš + Copyright © 2019 Marco Melorio + Copyright © 2022 Pablo Escobar + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +/** @file + * @brief Class @ref Magnum::Platform::Sdl2Application, macro @ref MAGNUM_SDL2APPLICATION_MAIN() + */ + +#include +#include +#include /** @todo PIMPL Configuration instead? */ + +#include "Magnum/Magnum.h" +#include "Magnum/Tags.h" +#include "Magnum/Math/Vector4.h" +#include "Magnum/Platform/Platform.h" + +#ifdef MAGNUM_TARGET_GL +#include "Magnum/Platform/GLContext.h" +#endif + +#ifdef CORRADE_TARGET_WINDOWS /* Windows version of SDL2 redefines main(), we don't want that */ +#define SDL_MAIN_HANDLED +#endif + +#ifdef CORRADE_TARGET_CLANG_CL +/* SDL does #pragma pack(push,8) and #pragma pack(pop,8) in different headers + (begin_code.h and end_code.h) and clang-cl doesn't like that, even though it + is completely fine. Silence the warning. */ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpragma-pack" +#endif +/* SDL.h includes the world, adding 50k LOC. We don't want that either. */ +#include +#include +#include /* huh, why is this not pulled in implicitly?! */ +#include +#include + +#ifdef CORRADE_TARGET_IOS +/* Including SDL_main.h unconditionally would mean it'd override Corrade::Main + on Windows (both are parsing wargv and converting them to UTF-8, but ours + does that better and additionally enables ANSI colors and UTF-8 console + output). Right now (SDL 2.0.11) it's only needed for WinRT (which is done + below), Android (which we don't support for SDL) and iOS, so whitelist it + only for iOS. */ +#include +#endif + +#ifdef CORRADE_TARGET_WINDOWS_RT +#include /* For SDL_WinRTRunApp */ +#include /* For the WinMain entrypoint */ +#endif +#ifdef CORRADE_TARGET_CLANG_CL +#pragma clang diagnostic pop +#endif + +#ifdef MAGNUM_BUILD_DEPRECATED +/* Some APIs used to take or return a std::string before */ +#include +#endif + +#ifndef DOXYGEN_GENERATING_OUTPUT +union SDL_Event; /* for anyEvent() */ +#endif + +namespace Magnum { namespace Platform { + +namespace Implementation { + enum class Sdl2DpiScalingPolicy: UnsignedByte; +} + +/** @nosubgrouping +@brief SDL2 application + +@m_keywords{Application} + +Application using [Simple DirectMedia Layer](http://www.libsdl.org/) toolkit. +Supports keyboard and mouse handling. This application library is available for +all platforms for which SDL2 is ported except Android (thus also +@ref CORRADE_TARGET_EMSCRIPTEN "Emscripten", see +respective sections in @ref building-corrade-cross-emscripten "Corrade's" and +@ref building-cross-emscripten "Magnum's" building documentation). + +@m_class{m-block m-success} + +@thirdparty This plugin makes use of the [SDL2](https://www.libsdl.org/) + library, released under the @m_class{m-label m-success} **zlib license** + ([license text](http://www.gzip.org/zlib/zlib_license.html), + [choosealicense.com](https://choosealicense.com/licenses/zlib/)). + Attribution is appreciated but not required. + +@section Platform-Sdl2Application-bootstrap Bootstrap application + +Fully contained base application using @ref Sdl2Application along with +CMake setup is available in `base` branch of +[Magnum Bootstrap](https://github.com/mosra/magnum-bootstrap) repository, +download it as [tar.gz](https://github.com/mosra/magnum-bootstrap/archive/base.tar.gz) +or [zip](https://github.com/mosra/magnum-bootstrap/archive/base.zip) file. +After extracting the downloaded archive you can build and run the application +with these four commands: + +@code{.sh} +mkdir build && cd build +cmake .. +cmake --build . +./src/MyApplication # or ./src/Debug/MyApplication +@endcode + +See @ref cmake for more information. + +@section Platform-Sdl2Application-bootstrap-emscripten Bootstrap application for Emscripten + +The dedicated application implementation for Emscripten is +@ref EmscriptenApplication, which also provides a bootstrap project along with +full HTML markup and CMake setup. @ref Sdl2Application however supports +Emscripten as well --- set up the bootstrap application as +@ref Platform-EmscriptenApplication-bootstrap "described in the EmscriptenApplication docs" +and then change `src/CMakeLists.txt` and the @cpp #include @ce to use +@ref Sdl2Application for both the native and the web build. + +@section Platform-Sdl2Application-bootstrap-ios Bootstrap application for iOS + +Fully contained base application using @ref Sdl2Application for both desktop +and iOS build along with pre-filled `*.plist` is available in `base-ios` branch +of [Magnum Bootstrap](https://github.com/mosra/magnum-bootstrap) repository, +download it as [tar.gz](https://github.com/mosra/magnum-bootstrap/archive/base-ios.tar.gz) +or [zip](https://github.com/mosra/magnum-bootstrap/archive/base-ios.zip) file. +After extracting the downloaded archive, you can do the desktop build in +the same way as above. For the iOS build you also need to put the contents of +toolchains repository from https://github.com/mosra/toolchains in `toolchains/` +subdirectory. + +Then create build directory and run `cmake` to generate the Xcode project. Set +`CMAKE_OSX_ROOT` to SDK you want to target and enable all desired architectures +in `CMAKE_OSX_ARCHITECTURES`. Set `CMAKE_PREFIX_PATH` to the directory where +you have all the dependencies. + +@code{.sh} +mkdir build-ios && cd build-ios +cmake .. \ + -DCMAKE_TOOLCHAIN_FILE=path/to/toolchains/generic/iOS.cmake \ + -DCMAKE_OSX_SYSROOT=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk \ + -DCMAKE_OSX_ARCHITECTURES="arm64;armv7;armv7s" \ + -DCMAKE_PREFIX_PATH=~/ios-libs \ + -G Xcode +@endcode + +You can then open the generated project file in Xcode and build/deploy it from +there. + +@section Platform-Sdl2Application-bootstrap-winrt Bootstrap application for Windows RT + +Fully contained base application using @ref Sdl2Application for both desktop +and Windows Phone / Windows Store build along with all required plumbing is +available in `base-winrt` branch of [Magnum Bootstrap](https://github.com/mosra/magnum-bootstrap) +repository, download it as [zip](https://github.com/mosra/magnum-bootstrap/archive/base-winrt.zip) +file. After extracting the downloaded archive, you can do the desktop build in +the same way as above. + +For the Windows RT build you need to provide [your own *.pfx certificate file](https://msdn.microsoft.com/en-us/library/windows/desktop/jj835832.aspx) and +pass it to CMake in a `SIGNING_CERTIFICATE` variable. The bootstrap application +assumes that SDL2 and ANGLE is built as DLL and both Corrade and Magnum are +built statically. Assuming the native Corrade installation is in `C:/Sys` and +all WinRT dependencies are in `C:/Sys-winrt`, the build can be done similarly +to the following: + +@code{.bat} +mkdir build-winrt && cd build-winrt +cmake .. ^ + -DCORRADE_RC_EXECUTABLE="C:/Sys/bin/corrade-rc.exe" ^ + -DCMAKE_PREFIX_PATH="C:/Sys-winrt" ^ + -DCMAKE_SYSTEM_NAME=WindowsStore ^ + -DCMAKE_SYSTEM_VERSION=8.1 ^ + -G "Visual Studio 14 2015" ^ + -DSIGNING_CERTIFICATE= +cmake --build . +@endcode + +Change `WindowsStore` to `WindowsPhone` if you want to build for Windows Phone +instead. The `build-winrt/src/AppPackages` directory will then contain the +final package along with a PowerShell script for easy local installation. + +@section Platform-Sdl2Application-usage General usage + +This application library depends on the [SDL2](http://www.libsdl.org) library +(Emscripten has it built in) and is built if `MAGNUM_WITH_SDL2APPLICATION` is +enabled when building Magnum. To use this library with CMake, put +[FindSDL2.cmake](https://github.com/mosra/magnum/blob/master/modules/FindSDL2.cmake) +into your `modules/` directory, request the `Sdl2Application` component of +the `Magnum` package and link to the `Magnum::Sdl2Application` target: + +@code{.cmake} +find_package(Magnum REQUIRED Sdl2Application) + +# ... +target_link_libraries(your-app PRIVATE Magnum::Sdl2Application) +@endcode + +Additionally, if you're using Magnum as a CMake subproject, bundle the +[SDL repository](https://github.com/libsdl-org/SDL) and do the following +* *before* calling @cmake find_package() @ce to ensure it's enabled, as the +library is not built by default. If you want to use system-installed SDL2, +omit the first part and point `CMAKE_PREFIX_PATH` to its installation dir if +necessary. + +@code{.cmake} +# This is the most minimal set of features which still make Sdl2Application +# work. If you need something from these, remove the setting. The SDL_AUDIO and +# SDL_EVENT options should not be needed either as Magnum doesn't use them, but +# if they're disabled they causes compiler or linker errors. Either SDL_DLOPEN +# or SDL_LOADSO needs to be enabled depending on the system to allow linking +# dependencies at runtime, so it's better to just leave them both on. The +# SDL_TIMERS option is important for rendering performance. +set(SDL_ATOMIC OFF CACHE BOOL "" FORCE) +set(SDL_CPUINFO OFF CACHE BOOL "" FORCE) +set(SDL_FILE OFF CACHE BOOL "" FORCE) +set(SDL_FILESYSTEM OFF CACHE BOOL "" FORCE) +set(SDL_HAPTIC OFF CACHE BOOL "" FORCE) +set(SDL_LOCALE OFF CACHE BOOL "" FORCE) +set(SDL_POWER OFF CACHE BOOL "" FORCE) +set(SDL_RENDER OFF CACHE BOOL "" FORCE) +set(SDL_SENSOR OFF CACHE BOOL "" FORCE) +set(SDL_THREADS OFF CACHE BOOL "" FORCE) +# This assumes you want to have SDL as a static library. If not, set SDL_STATIC +# to OFF instead. +set(SDL_SHARED OFF CACHE BOOL "" FORCE) +add_subdirectory(SDL EXCLUDE_FROM_ALL) + +set(MAGNUM_WITH_SDL2APPLICATION ON CACHE BOOL "" FORCE) +add_subdirectory(magnum EXCLUDE_FROM_ALL) +@endcode + + + +@m_class{m-note m-warning} + +@par + While SDL itself, being a C project, builds quite fast, when using it as a + CMake subproject be prepared that it will *significantly* increase the + CMake configure time due to excessive platform checks, and pollute the + CMake option list with a lot of unprefixed SDL-specific options. + +If no other application is requested, you can also use the generic +`Magnum::Application` alias to simplify porting. Again, see @ref building and +@ref cmake for more information. + +In C++ code you need to implement at least @ref drawEvent() to be able to draw +on the screen. The subclass can be then used directly in `main()` --- see +convenience macro @ref MAGNUM_SDL2APPLICATION_MAIN(). See @ref platform for +more information. + +@code{.cpp} +class MyApplication: public Platform::Sdl2Application { + // implement required methods... +}; +MAGNUM_SDL2APPLICATION_MAIN(MyApplication) +@endcode + +If no other application header is included, this class is also aliased to +@cpp Platform::Application @ce and the macro is aliased to @cpp MAGNUM_APPLICATION_MAIN() @ce +to simplify porting. + +@subsection Platform-Sdl2Application-usage-power Power management + +SDL by default prevents the computer from powering off the or screen going to +sleep. While possibly useful for game-like use cases, it's generally +undesirable for regular applications. @ref Sdl2Application turns this behavior +off. You can restore SDL's default behavior by disabling the +[corresponding SDL hint](https://wiki.libsdl.org/CategoryHints) through an +environment variable or through @cpp SDL_SetHint() @ce from your application. + +@code{.sh} +SDL_VIDEO_ALLOW_SCREENSAVER=0 ./your-app +@endcode + +@subsection Platform-Sdl2Application-usage-posix POSIX specifics + +On POSIX systems, SDL by default intercepts the `SIGTERM` signal and generates +an exit event for it, instead of doing the usual application exit. This would +mean that if the application fails to set @ref ExitEvent::setAccepted() in an +@ref exitEvent() override for some reason, pressing +@m_class{m-label m-warning} **Ctrl** @m_class{m-label m-default} **C** would +not terminate it either and you'd have to forcibly kill it instead. When using +SDL >= 2.0.4, @ref Sdl2Application turns this behavior off, making +@ref exitEvent() behave consistently with other application implementations +such as @ref GlfwApplication. You can turn this behavior back on by enabling +the [corresponding SDL hint](https://wiki.libsdl.org/SDL_HINT_NO_SIGNAL_HANDLERS) +through an environment variable: + +@code{.sh} +SDL_NO_SIGNAL_HANDLERS=1 ./your-app +@endcode + +See also the [SDL Wiki](https://wiki.libsdl.org/SDL_EventType#SDL_QUIT) for +details. + +@subsection Platform-Sdl2Application-usage-linux Linux specifics + +SDL by default attempts to disable compositing, which may cause ugly flickering +for non-fullscreen apps (KWin, among others, is known to respect this setting). +When using SDL >= 2.0.8, @ref Sdl2Application turns this behavior off, keeping +the compositor running to avoid the flicker. You can turn this behavior back on +by enabling the [corresponding SDL hint](https://wiki.libsdl.org/CategoryHints) +through an environment variable or through @cpp SDL_SetHint() @ce from your +application. + +@code{.sh} +SDL_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR=1 ./your-app +@endcode + +If you're running an older version of SDL, you can disallow apps from bypassing +the compositor in system-wide KWin settings. + +@subsection Platform-Sdl2Application-usage-ios iOS specifics + +Leaving a default (zero) window size in @ref Configuration will cause the app +to autodetect it based on the actual device screen size. This also depends on +@ref Platform-Sdl2Application-dpi "DPI awareness", see below for details. + +As noted in the @ref platforms-ios-bundle "iOS platform guide", a lot of +options needs to be set via a `*.plist` file. Some options can be configured +from runtime when creating the SDL2 application window, see documentation of +a particular value for details: + +- @ref Configuration::WindowFlag::Borderless hides the menu bar +- @ref Configuration::WindowFlag::Resizable makes the application respond to + device orientation changes + +@subsection Platform-Sdl2Application-usage-emscripten Emscripten specifics + +Leaving a default (zero) window size in @ref Configuration will cause the app +to use a window size that corresponds to *CSS pixel size* of the +@cb{.html} @ce element. The size is then multiplied by DPI scaling +value, see @ref Platform-Sdl2Application-dpi "DPI awareness" below for details. + +If you enable @ref Configuration::WindowFlag::Resizable, the canvas will be +resized when size of the canvas changes and you get @ref viewportEvent(). If +the flag is not enabled, no canvas resizing is performed. + +@note While this implementation supports Esmcripten and is going to continue + supporting it for the foreseeable future, @ref EmscriptenApplication is now + the preferred application implementation for the web. It offers a broader + range of features, more efficient idle behavior and smaller code size. + +@subsection Platform-Sdl2Application-usage-gles OpenGL ES specifics + +For OpenGL ES, SDL2 defaults to a "desktop GLES" context of the system driver. +Because Magnum has the opposite default behavior, if @ref MAGNUM_TARGET_GLES is +not defined and SDL >= 2.0.6 is used, @ref Sdl2Application sets the +`SDL_HINT_OPENGL_ES_DRIVER` hint to 1, forcing it to load symbols from a +dedicated libGLES library instead, making SDL and Magnum consistently use the +same OpenGL entrypoints. This change also allows @ref platforms-gl-es-angle "ANGLE" +to be used on Windows simply by placing the corresponding `libEGL.dll` and +`libGLESv2.dll` files next to the application executable. + +@section Platform-Sdl2Application-dpi DPI awareness + +On displays that match the platform default DPI (96 or 72), +@ref Configuration::setSize() will create the window in exactly the requested +size and the framebuffer pixels will match display pixels 1:1. On displays that +have different DPI, there are three possible scenarios, listed below. It's +possible to fine tune the behavior either using extra parameters passed to +@ref Configuration::setSize() or via the `--magnum-dpi-scaling` command-line +option (or the equivalent @cb{.sh} $MAGNUM_DPI_SCALING @ce environment +variable). + +- Framebuffer DPI scaling. The window is created with exactly the requested + size and all event coordinates are reported also relative to that size. + However, the window backing framebuffer has a different size. This is only + supported on macOS and iOS. See @ref platforms-macos-hidpi for details how + to enable it. Equivalent to passing + @ref Configuration::DpiScalingPolicy::Framebuffer to + @ref Configuration::setSize() or `framebuffer` via command line / + environment. +- Virtual DPI scaling. Scales the window based on DPI scaling setting in the + system. For example if a 800x600 window is requested and DPI scaling is set + to 200%, the resulting window will have 1600x1200 pixels. The backing + framebuffer will have the same size. This is supported on Linux and + Windows; on Windows the application is first checked for DPI awareness + as described in @ref platforms-windows-hidpi and if the application is not + DPI-aware, 1:1 scaling is used. Equivalent to passing + @ref Configuration::DpiScalingPolicy::Virtual to + @ref Configuration::setSize() or `virtual` on command line. +- Physical DPI scaling. Takes the requested window size as a physical size + that a window would have on platform's default DPI and scales it to have + the same physical size on given display physical DPI. So, for example on a + display with 240 DPI the window size will be 2000x1500 in pixels, but it + will be 21 centimeters wide, the same as a 800x600 window would be on a 96 + DPI display. On platforms that don't have a concept of a window (such + as mobile platforms or @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten"), it + causes the framebuffer to match display pixels 1:1 without any scaling. + This is supported on Linux and all mobile platforms (except iOS) and + Emscripten. On Windows this is equivalent to virtual DPI scaling but + without doing an explicit check for DPI awareness first. Equivalent to + passing @ref Configuration::DpiScalingPolicy::Physical to + @ref Configuration::setSize() or `physical` via command line / environment. + +Besides the above, it's possible to supply a custom DPI scaling value to +@ref Configuration::setSize() or the `--magnum-dpi-scaling` command-line +option (or environment variable). Using `--magnum-dpi-scaling <number>` +will make the scaling same in both directions, with +`--magnum-dpi-scaling " "` the scaling will be different +in each direction. On desktop systems custom DPI scaling value will affect +physical window size (with the content being scaled), on mobile and web it will +affect sharpness of the contents. + +The default is depending on the platform: + +- On macOS and iOS, the default and only supported option is + @ref Configuration::DpiScalingPolicy::Framebuffer. On this platform, + @ref windowSize() and @ref framebufferSize() will differ depending on + whether `NSHighResolutionCapable` is enabled in the `*.plist` file or not. + By default, @ref dpiScaling() is @cpp 1.0f @ce in both dimensions but it + can be overridden using custom DPI scaling. +- On Windows, the default is @ref Configuration::DpiScalingPolicy::Framebuffer. + The @ref windowSize() and @ref framebufferSize() is always the same. + Depending on whether the DPI awareness was enabled in the manifest file or + set by the `SetProcessDpiAwareness()` API, @ref dpiScaling() is either + @cpp 1.0f @ce in both dimensions, indicating a low-DPI screen or a + non-DPI-aware app, or some other value for HiDPI screens. In both cases the + value can be overridden using custom DPI scaling. +- On Linux, the default is @ref Configuration::DpiScalingPolicy::Virtual, + taken from the `Xft.dpi` property. If the property is not available, it + falls back to @ref Configuration::DpiScalingPolicy::Physical, querying the + monitor DPI value. The @ref windowSize() and @ref framebufferSize() is + always the same, @ref dpiScaling() contains the queried DPI scaling value. + The value can be overridden using custom DPI scaling. +- On @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten", the default is physical DPI + scaling, taken from [Window.getDevicePixelRatio()](https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio). The + @ref windowSize() and @ref framebufferSize() is always the same, + @ref dpiScaling() contains the queried DPI scaling value. The value can be + overridden using custom DPI scaling. Note that this is different from the + behavior in @ref EmscriptenApplication --- Emscripten's SDL implementation + has some additional emulation code that reports event coordinates in + framebuffer pixels instead of CSS pixels. See + @ref Platform-EmscriptenApplication-dpi "EmscriptenApplication DPI awareness docs" + for more information. + +With @ref windowSize(), @ref framebufferSize() and @ref dpiScaling() having a +different relation on each platform, the way to calculate context scaling +consistently everywhere is using this expression: + +@snippet MagnumPlatform.cpp Sdl2Application-dpi-scaling + +If your application is saving and restoring window size, it's advisable to take +@ref dpiScaling() into account: + +- Either divide the window size by the DPI scaling value and use that to + restore the window next time --- but note this might accumulate slight + differences in window sizes over time, especially if fractional scaling is + involved. +- Or save the scaled size and use @ref Configuration::setSize(const Vector2i&, const Vector2&) + with @cpp 1.0f @ce as custom DPI scaling next time --- but this doesn't + properly handle cases where the window is opened on a display with + different DPI. +*/ +class Sdl2Application { + public: + /** @brief Application arguments */ + struct Arguments { + /** @brief Constructor */ + /*implicit*/ constexpr Arguments(int& argc, char** argv) noexcept: argc{argc}, argv{argv} {} + + int& argc; /**< @brief Argument count */ + char** argv; /**< @brief Argument values */ + }; + + class Configuration; + #ifdef MAGNUM_TARGET_GL + class GLConfiguration; + #endif + class ExitEvent; + class ViewportEvent; + class InputEvent; + class KeyEvent; + class MouseEvent; + class MouseMoveEvent; + class MouseScrollEvent; + class MultiGestureEvent; + class TextInputEvent; + class TextEditingEvent; + + #ifdef MAGNUM_TARGET_GL + /** + * @brief Construct with given configuration for OpenGL context + * @param arguments Application arguments + * @param configuration Application configuration + * @param glConfiguration OpenGL context configuration + * + * Creates application with default or user-specified configuration. + * See @ref Configuration for more information. The program exits if + * the context cannot be created, see @ref tryCreate() for an + * alternative. + * + * @note This function is available only if Magnum is compiled with + * @ref MAGNUM_TARGET_GL enabled (done by default). See + * @ref building-features for more information. + */ + explicit Sdl2Application(const Arguments& arguments, const Configuration& configuration, const GLConfiguration& glConfiguration); + #endif + + /** + * @brief Construct with given configuration + * + * If @ref Configuration::WindowFlag::Contextless is present or Magnum + * was not built with @ref MAGNUM_TARGET_GL, this creates a window + * without any GPU context attached, leaving that part on the user. + * + * If none of the flags is present and Magnum was built with + * @ref MAGNUM_TARGET_GL, this is equivalent to calling + * @ref Sdl2Application(const Arguments&, const Configuration&, const GLConfiguration&) + * with default-constructed @ref GLConfiguration. + * + * See also @ref building-features for more information. + */ + explicit Sdl2Application(const Arguments& arguments, const Configuration& configuration); + + /** + * @brief Construct with default configuration + * + * Equivalent to calling @ref Sdl2Application(const Arguments&, const Configuration&) + * with default-constructed @ref Configuration. + */ + explicit Sdl2Application(const Arguments& arguments); + + /** + * @brief Construct without creating a window + * @param arguments Application arguments + * + * Unlike above, the window is not created and must be created later + * with @ref create() or @ref tryCreate(). + */ + explicit Sdl2Application(const Arguments& arguments, NoCreateT); + + /** @brief Copying is not allowed */ + Sdl2Application(const Sdl2Application&) = delete; + + /** @brief Moving is not allowed */ + Sdl2Application(Sdl2Application&&) = delete; + + /** @brief Copying is not allowed */ + Sdl2Application& operator=(const Sdl2Application&) = delete; + + /** @brief Moving is not allowed */ + Sdl2Application& operator=(Sdl2Application&&) = delete; + + /** + * @brief Execute application main loop + * @return Value for returning from @cpp main() @ce + * + * Calls @ref mainLoopIteration() in a loop until @ref exit() is + * called. See @ref MAGNUM_SDL2APPLICATION_MAIN() for usage + * information. + */ + int exec(); + + /** + * @brief Run one iteration of application main loop + * @return @cpp false @ce if @ref exit() was called and the application + * should exit, @cpp true @ce otherwise + * + * Called internally from @ref exec(). If you want to have better + * control over how the main loop behaves, you can call this function + * yourself from your own `main()` function instead of it being called + * automatically from @ref exec() / @ref MAGNUM_SDL2APPLICATION_MAIN(). + */ + bool mainLoopIteration(); + + /** + * @brief Exit application + * @param exitCode The exit code the application should return + * + * When called from application constructor, it will cause the + * application to exit immediately after constructor ends, without + * entering the event loop. When called from within an event handler, + * it will cause it to exit at the start of next event loop iteration. + * Compared to requesting an application exit using the window close + * button or the @m_class{m-label m-default} **Alt** + * @m_class{m-label m-default} **F4** / + * @m_class{m-label m-default} **Cmd** + * @m_class{m-label m-default} **Q** keyboard shortcut, the + * @ref exitEvent() *isn't* called when using this function. + * + * Calling this function from an application constructor is recommended + * over @ref std::exit() or @ref Corrade::Utility::Fatal "Fatal", which + * exit without calling destructors on local scope. Note that, however, + * you need to explicitly @cpp return @ce after calling it, as it can't + * exit the constructor on its own: + * + * @snippet MagnumPlatform.cpp exit-from-constructor + */ + void exit(int exitCode = 0); + + #ifndef CORRADE_TARGET_EMSCRIPTEN + /** + * @brief Underlying window handle + * + * Use in case you need to call SDL functionality directly. Returns + * @cpp nullptr @ce in case the window was not created yet. + * @note Not available in @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". + */ + SDL_Window* window() { return _window; } + #endif + + #if defined(MAGNUM_TARGET_GL) && !defined(CORRADE_TARGET_EMSCRIPTEN) + /** + * @brief Underlying OpenGL context + * @m_since{2019,10} + * + * Use in case you need to call SDL functionality directly. Returns + * @cpp nullptr @ce in case the context was not created yet. + * @note This function is available only if Magnum is compiled with + * @ref MAGNUM_TARGET_GL enabled (done by default). See + * @ref building-features for more information. Not available in + * @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". + */ + SDL_GLContext glContext() { return _glContext; } + #endif + + protected: + /* Nobody will need to have (and delete) Sdl2Application*, thus this is + faster than public pure virtual destructor */ + ~Sdl2Application(); + + #ifdef MAGNUM_TARGET_GL + /** + * @brief Create a window with given configuration for OpenGL context + * @param configuration Application configuration + * @param glConfiguration OpenGL context configuration + * + * Must be called only if the context wasn't created by the constructor + * itself, i.e. when passing @ref NoCreate to it. Error message is + * printed and the program exits if the context cannot be created, see + * @ref tryCreate() for an alternative. + * + * On desktop GL, if version is not specified in @p glConfiguration, + * the application first tries to create core context (OpenGL 3.2+ on + * macOS, OpenGL 3.1+ elsewhere) and if that fails, falls back to + * compatibility OpenGL 2.1 context. + * + * @note This function is available only if Magnum is compiled with + * @ref MAGNUM_TARGET_GL enabled (done by default). See + * @ref building-features for more information. + */ + void create(const Configuration& configuration, const GLConfiguration& glConfiguration); + #endif + + /** + * @brief Create a window with given configuration + * + * If @ref Configuration::WindowFlag::Contextless is present or Magnum + * was not built with @ref MAGNUM_TARGET_GL, this creates a window + * without any GPU context attached, leaving that part on the user. + * + * If none of the flags is present and Magnum was built with + * @ref MAGNUM_TARGET_GL, this is equivalent to calling + * @ref create(const Configuration&, const GLConfiguration&) with + * default-constructed @ref GLConfiguration. + * + * See also @ref building-features for more information. + */ + void create(const Configuration& configuration); + + /** + * @brief Create a window with default configuration and OpenGL context + * + * Equivalent to calling @ref create(const Configuration&) with + * default-constructed @ref Configuration. + */ + void create(); + + #ifdef MAGNUM_TARGET_GL + /** + * @brief Try to create context with given configuration for OpenGL context + * + * Unlike @ref create(const Configuration&, const GLConfiguration&) + * returns @cpp false @ce if the context cannot be created, + * @cpp true @ce otherwise. + * + * @note This function is available only if Magnum is compiled with + * @ref MAGNUM_TARGET_GL enabled (done by default). See + * @ref building-features for more information. + */ + bool tryCreate(const Configuration& configuration, const GLConfiguration& glConfiguration); + #endif + + /** + * @brief Try to create context with given configuration + * + * Unlike @ref create(const Configuration&) returns @cpp false @ce if + * the context cannot be created, @cpp true @ce otherwise. + */ + bool tryCreate(const Configuration& configuration); + + /** @{ @name Screen handling */ + + public: + /** + * @brief Window size + * + * Window size to which all input event coordinates can be related. + * Note that, especially on HiDPI systems, it may be different from + * @ref framebufferSize(). Expects that a window is already created. + * See @ref Platform-Sdl2Application-dpi for more information. + * @see @ref dpiScaling() + */ + Vector2i windowSize() const; + + #if !defined(CORRADE_TARGET_EMSCRIPTEN) || defined(DOXYGEN_GENERATING_OUTPUT) + /** + * @brief Set window size + * @param size The size, in screen coordinates + * @m_since{2020,06} + * + * To make the sizing work independently of the display DPI, @p size is + * internally multiplied with @ref dpiScaling() before getting applied. + * Expects that a window is already created. + * @note Not available in @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". + * @see @ref setMinWindowSize(), @ref setMaxWindowSize() + */ + void setWindowSize(const Vector2i& size); + + /** + * @brief Set minimum window size + * @param size The minimum size, in screen coordinates + * @m_since{2019,10} + * + * Note that, unlike in @ref GlfwApplication, SDL2 doesn't have a way + * to disable/remove a size limit. To make the sizing work + * independently of the display DPI, @p size is internally multiplied + * with @ref dpiScaling() before getting applied. Expects that a window + * is already created. + * @note Not available in @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". + * @see @ref setMaxWindowSize(), @ref setWindowSize() + */ + void setMinWindowSize(const Vector2i& size); + + /** + * @brief Set maximal window size + * @param size The maximum size, in screen coordinates + * @m_since{2019,10} + * + * Note that, unlike in @ref GlfwApplication, SDL2 doesn't have a way + * to disable/remove a size limit. To make the sizing work + * independently of the display DPI, @p size is internally multiplied + * with @ref dpiScaling() before getting applied. Expects that a window + * is already created. + * @note Not available in @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". + * @see @ref setMinWindowSize(), @ref setMaxWindowSize() + */ + void setMaxWindowSize(const Vector2i& size); + #endif + + #if defined(MAGNUM_TARGET_GL) || defined(DOXYGEN_GENERATING_OUTPUT) + /** + * @brief Framebuffer size + * + * Size of the default framebuffer. Note that, especially on HiDPI + * systems, it may be different from @ref windowSize(). Expects that a + * window is already created. See @ref Platform-Sdl2Application-dpi for + * more information. + * + * @note This function is available only if Magnum is compiled with + * @ref MAGNUM_TARGET_GL enabled (done by default). See + * @ref building-features for more information. + * + * @see @ref Sdl2Application::framebufferSize(), @ref dpiScaling() + */ + Vector2i framebufferSize() const; + #endif + + /** + * @brief DPI scaling + * + * How the content should be scaled relative to system defaults for + * given @ref windowSize(). If a window is not created yet, returns + * zero vector, use @ref dpiScaling(const Configuration&) for + * calculating a value independently. See @ref Platform-Sdl2Application-dpi + * for more information. + * @see @ref framebufferSize() + */ + Vector2 dpiScaling() const { return _dpiScaling; } + + /** + * @brief DPI scaling for given configuration + * + * Calculates DPI scaling that would be used when creating a window + * with given @p configuration. Takes into account DPI scaling policy + * and custom scaling specified on the command-line. See + * @ref Platform-Sdl2Application-dpi for more information. + */ + Vector2 dpiScaling(const Configuration& configuration); + + /** + * @brief Set window title + * @m_since{2019,10} + * + * The @p title is expected to be encoded in UTF-8. + */ + void setWindowTitle(Containers::StringView title); + + #if !defined(CORRADE_TARGET_EMSCRIPTEN) && (SDL_MAJOR_VERSION*1000 + SDL_MINOR_VERSION*100 + SDL_PATCHLEVEL >= 2005 || defined(DOXYGEN_GENERATING_OUTPUT)) + /** + * @brief Set window icon + * @m_since{2020,06} + * + * The @p image is expected to be with origin at bottom left (which is + * the default for imported images) and in one of + * @ref PixelFormat::RGB8Unorm, @ref PixelFormat::RGB8Srgb, + * @ref PixelFormat::RGBA8Unorm or @ref PixelFormat::RGBA8Srgb formats. + * Unlike @ref GlfwApplication::setWindowIcon(), SDL doesn't provide a + * way to supply multiple images in different sizes. + * @note Available since SDL 2.0.5. Not available on + * @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten", use + * @cb{.html} @ce in your HTML markup instead. + * Although it's not documented in SDL itself, the function might + * have no effect on macOS / Wayland, similarly to how + * @ref GlfwApplication::setWindowIcon() behaves on those + * platforms. + * @see @ref platform-windows-icon "Excecutable icon on Windows", + * @ref Trade::IcoImporter "IcoImporter" + */ + void setWindowIcon(const ImageView2D& image); + #endif + + #if defined(CORRADE_TARGET_EMSCRIPTEN) || defined(DOXYGEN_GENERATING_OUTPUT) + /** + * @brief Set container CSS class + * + * Assigns given CSS class to the @cb{.html}
@ce + * enclosing the application @cb{.html} @ce. Useful for + * example to change aspect ratio of the view or stretch it to cover + * the full page. See @ref platforms-html5-layout for more information + * about possible values. Note that this replaces any existing class + * (except for @cb{.css} .mn-container @ce, which is kept), to set + * multiple classes separate them with whitespace. + * + * @note Only available on @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". + * + * @m_class{m-note m-danger} + * + * @par + * For backwards compatibility purposes the function will look for + * *any* @cb{.html}
@ce in case the + * @cb{.html}
@ce is not found. This + * compatibility is scheduled to be removed in the future. + */ + void setContainerCssClass(Containers::StringView cssClass); + #endif + + /** + * @brief Swap buffers + * + * Paints currently rendered framebuffer on screen. + * @see @ref setSwapInterval() + */ + void swapBuffers(); + + /** @brief Swap interval */ + Int swapInterval() const; + + /** + * @brief Set swap interval + * + * Set @cpp 0 @ce for no VSync, @cpp 1 @ce for enabled VSync. Some + * platforms support @cpp -1 @ce for late swap tearing. Prints error + * message and returns @cpp false @ce if swap interval cannot be set, + * @cpp true @ce otherwise. Default is driver-dependent, you can query + * the value with @ref swapInterval(). + * @see @ref setMinimalLoopPeriod() + */ + bool setSwapInterval(Int interval); + + #ifndef CORRADE_TARGET_EMSCRIPTEN + /** + * @brief Set minimal loop period + * + * This setting reduces the main loop frequency in case VSync is + * not/cannot be enabled or no drawing is done. Default is @cpp 0 @ce + * (i.e. looping at maximum frequency). If the application is drawing + * on the screen and VSync is enabled, this setting is ignored. + * @note Not available in @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten", + * the browser is managing the frequency instead. + * @see @ref setSwapInterval() + */ + void setMinimalLoopPeriod(UnsignedInt milliseconds) { + _minimalLoopPeriod = milliseconds; + } + #endif + + /** + * @brief Redraw immediately + * + * Marks the window for redrawing, resulting in call to @ref drawEvent() + * in the next iteration. You can call it from @ref drawEvent() itself + * to redraw immediately without waiting for user input. + */ + void redraw(); + + private: + /** + * @brief Viewport event + * + * Called when window size changes. The default implementation does + * nothing. If you want to respond to size changes, you should pass the + * new *framebuffer* size to @ref GL::DefaultFramebuffer::setViewport() + * (if using OpenGL) and possibly elsewhere (to + * @ref SceneGraph::Camera::setViewport(), other framebuffers...) and + * the new *window* size and DPI scaling to APIs that respond to user + * events or scale UI elements. + * + * Note that this function might not get called at all if the window + * size doesn't change. You should configure the initial state of your + * cameras, framebuffers etc. in application constructor rather than + * relying on this function to be called. Size of the window can be + * retrieved using @ref windowSize(), size of the backing framebuffer + * via @ref framebufferSize() and DPI scaling using @ref dpiScaling(). + * See @ref Platform-Sdl2Application-dpi for detailed info about these + * values. + */ + virtual void viewportEvent(ViewportEvent& event); + + /** + * @brief Draw event + * + * Called when the screen is redrawn. You should clean the framebuffer + * using @ref GL::DefaultFramebuffer::clear() (if using OpenGL) and + * then add your own drawing functions. After drawing is finished, call + * @ref swapBuffers(). If you want to draw immediately again, call also + * @ref redraw(). + */ + virtual void drawEvent() = 0; + + /* Since 1.8.17, the original short-hand group closing doesn't work + anymore. FFS. */ + /** + * @} + */ + + /** @{ @name Keyboard handling */ + + /** + * @brief Key press event + * + * Called when an key is pressed. Default implementation does nothing. + */ + virtual void keyPressEvent(KeyEvent& event); + + /** + * @brief Key release event + * + * Called when an key is released. Default implementation does nothing. + */ + virtual void keyReleaseEvent(KeyEvent& event); + + /* Since 1.8.17, the original short-hand group closing doesn't work + anymore. FFS. */ + /** + * @} + */ + + /** @{ @name Mouse handling */ + + public: + /** + * @brief Cursor type + * @m_since{2020,06} + * + * @see @ref setCursor() + */ + enum class Cursor: UnsignedInt { + Arrow, /**< Arrow */ + TextInput, /**< Text input */ + Wait, /**< Wait */ + Crosshair, /**< Crosshair */ + WaitArrow, /**< Small wait cursor */ + ResizeNWSE, /**< Double arrow pointing northwest and southeast */ + ResizeNESW, /**< Double arrow pointing northeast and southwest */ + ResizeWE, /**< Double arrow pointing west and east */ + ResizeNS, /**< Double arrow pointing north and south */ + ResizeAll, /**< Four pointed arrow pointing north, south, east, and west */ + No, /**< Slashed circle or crossbones */ + Hand, /**< Hand */ + Hidden, /**< Hidden */ + + #ifndef CORRADE_TARGET_EMSCRIPTEN + /** + * Hidden and locked. When the mouse is locked, only + * @ref MouseMoveEvent::relativePosition() is changing, absolute + * position stays the same. + * @note Not available in @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". + */ + HiddenLocked + #endif + }; + + /** + * @brief Set cursor type + * @m_since{2020,06} + * + * Expects that a window is already created. Default is + * @ref Cursor::Arrow. + */ + void setCursor(Cursor cursor); + + /** + * @brief Get current cursor type + * @m_since{2020,06} + */ + Cursor cursor(); + + #ifndef CORRADE_TARGET_EMSCRIPTEN + /** + * @brief Warp mouse cursor to given coordinates + * + * @note Not available in @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". + */ + void warpCursor(const Vector2i& position) { + SDL_WarpMouseInWindow(_window, position.x(), position.y()); + } + #endif + + #ifdef MAGNUM_BUILD_DEPRECATED + /** + * @brief Whether mouse is locked + * + * @m_deprecated_since{2020,06} Use @ref cursor() together with + * @ref Cursor::HiddenLocked instead. + */ + CORRADE_DEPRECATED("use cursor() together with Cursor::HiddenLocked instead") bool isMouseLocked() const { return SDL_GetRelativeMouseMode(); } + + /** + * @brief Enable or disable mouse locking + * + * @m_deprecated_since{2020,06} Use @ref setCursor() together with + * @ref Cursor::HiddenLocked instead. + */ + CORRADE_DEPRECATED("use setCursor() together with Cursor::HiddenLocked instead") void setMouseLocked(bool enabled); + #endif + + private: + /** + * @brief Mouse press event + * + * Called when mouse button is pressed. Default implementation does + * nothing. + */ + virtual void mousePressEvent(MouseEvent& event); + + /** + * @brief Mouse release event + * + * Called when mouse button is released. Default implementation does + * nothing. + */ + virtual void mouseReleaseEvent(MouseEvent& event); + + /** + * @brief Mouse move event + * + * Called when mouse is moved. Default implementation does nothing. + */ + virtual void mouseMoveEvent(MouseMoveEvent& event); + + /** + * @brief Mouse scroll event + * + * Called when a scrolling device is used (mouse wheel or scrolling + * area on a touchpad). Default implementation does nothing. + */ + virtual void mouseScrollEvent(MouseScrollEvent& event); + + /* Since 1.8.17, the original short-hand group closing doesn't work + anymore. FFS. */ + /** + * @} + */ + + /** @{ @name Touch gesture handling */ + + /** + * @brief Multi gesture event + * + * Called when the user performs a gesture using multiple fingers. + * Default implementation does nothing. + * @experimental + */ + virtual void multiGestureEvent(MultiGestureEvent& event); + + /* Since 1.8.17, the original short-hand group closing doesn't work + anymore. FFS. */ + /** + * @} + */ + + /** @{ @name Text input handling */ + public: + /** + * @brief Whether text input is active + * + * If text input is active, text input events go to @ref textInputEvent() + * and @ref textEditingEvent(). + * @note Note that in @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten" the + * value is emulated and might not reflect external events like + * closing on-screen keyboard. + * @see @ref startTextInput(), @ref stopTextInput() + */ + bool isTextInputActive(); + + /** + * @brief Start text input + * + * Starts text input that will go to @ref textInputEvent() and + * @ref textEditingEvent(). + * @see @ref stopTextInput(), @ref isTextInputActive(), + * @ref setTextInputRect() + */ + void startTextInput(); + + /** + * @brief Stop text input + * + * Stops text input that went to @ref textInputEvent() and + * @ref textEditingEvent(). + * @see @ref startTextInput(), @ref isTextInputActive(), @ref textInputEvent() + * @ref textEditingEvent() + */ + void stopTextInput(); + + /** + * @brief Set text input rectangle + * + * The @p rect defines an area where the text is being displayed, for + * example to hint the system where to place on-screen keyboard. + */ + void setTextInputRect(const Range2Di& rect); + + private: + /** + * @brief Text input event + * + * Called when text input is active and the text is being input. + * @see @ref isTextInputActive() + */ + virtual void textInputEvent(TextInputEvent& event); + + /** + * @brief Text editing event + * + * Called when text input is active and the text is being edited. + */ + virtual void textEditingEvent(TextEditingEvent& event); + + /* Since 1.8.17, the original short-hand group closing doesn't work + anymore. FFS. */ + /** + * @} + */ + + /** @{ @name Special events */ + + /** + * @brief Exit event + * + * If implemented, it allows the application to react to an application + * exit (for example to save its internal state) and suppress it as + * well (for example to show a exit confirmation dialog). The default + * implementation calls @ref ExitEvent::setAccepted() on @p event, + * which tells the application that it's safe to exit. + * + * SDL has special behavior on POSIX systems regarding `SIGINT` and + * `SIGTERM` handling, see @ref Platform-Sdl2Application-usage-posix + * for more information. + */ + virtual void exitEvent(ExitEvent& event); + + protected: + /** + * @brief Tick event + * + * If implemented, this function is called periodically after + * processing all input events and before draw event even though there + * might be no input events and redraw is not requested. Useful e.g. + * for asynchronous task polling. Use @ref setMinimalLoopPeriod()/ + * @ref setSwapInterval() to control main loop frequency. + * + * If this implementation gets called from its @cpp override @ce, it + * will effectively stop the tick event from being fired and the app + * returns back to waiting for input events. This can be used to + * disable the tick event when not needed. + */ + virtual void tickEvent(); + + private: + /** + * @brief Any event + * + * Called in case a SDL event is not handled by any other event + * functions above. + * @see @ref ViewportEvent::event(), @ref InputEvent::event(), + * @ref MultiGestureEvent::event(), @ref TextInputEvent::event(), + * @ref TextEditingEvent::event(), @ref ExitEvent::event() + */ + virtual void anyEvent(SDL_Event& event); + + /* Since 1.8.17, the original short-hand group closing doesn't work + anymore. FFS. */ + /** + * @} + */ + + private: + enum class Flag: UnsignedByte; + typedef Containers::EnumSet Flags; + CORRADE_ENUMSET_FRIEND_OPERATORS(Flags) + + #ifndef CORRADE_TARGET_EMSCRIPTEN + SDL_Cursor* _cursors[12]{}; + #else + Cursor _cursor; + #endif + + /* These are saved from command-line arguments */ + bool _verboseLog{}; + Implementation::Sdl2DpiScalingPolicy _commandLineDpiScalingPolicy{}; + Vector2 _commandLineDpiScaling; + + Vector2 _dpiScaling; + + #ifndef CORRADE_TARGET_EMSCRIPTEN + SDL_Window* _window{}; + UnsignedInt _minimalLoopPeriod; + #else + SDL_Surface* _surface{}; + Vector2i _lastKnownCanvasSize; + #endif + + #ifdef MAGNUM_TARGET_GL + #ifndef CORRADE_TARGET_EMSCRIPTEN + SDL_GLContext _glContext{}; + #endif + /* Has to be in an Optional because we delay-create it in a constructor + with populated Arguments and it gets explicitly destroyed before the + GL context */ + Containers::Optional _context; + #endif + + Flags _flags; + + int _exitCode = 0; +}; + +#ifdef MAGNUM_TARGET_GL +/** +@brief OpenGL context configuration + +The created window is always with a double-buffered OpenGL context. + +@note This function is available only if Magnum is compiled with + @ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features + for more information. + +@see @ref Sdl2Application(), @ref create(), @ref tryCreate() +*/ +class Sdl2Application::GLConfiguration: public GL::Context::Configuration { + public: + /** + * @brief Context flag + * + * Includes also everything from @ref GL::Context::Configuration::Flag + * except for @relativeref{GL::Context::Configuration,Flag::Windowless}, + * which is not meant to be enabled for windowed apps. + * @see @ref Flags, @ref setFlags(), @ref GL::Context::Flag + */ + enum class Flag: UnsignedLong { + #ifndef CORRADE_TARGET_EMSCRIPTEN + #ifndef MAGNUM_TARGET_GLES + /** + * Forward compatible context. + * @requires_gl Core/compatibility profile distinction and forward + * compatibility applies only to desktop GL. + */ + ForwardCompatible = SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG, + #endif + + /** + * Debug context. Enabled automatically if supported by the driver + * and the @ref Flag::GpuValidation flag is set or if the + * `--magnum-gpu-validation` @ref GL-Context-usage-command-line "command-line option" + * is set to `on`. + * @requires_gles Context flags are not available in WebGL. + */ + Debug = SDL_GL_CONTEXT_DEBUG_FLAG, + + /** + * Context with robust access. + * @requires_gles Context flags are not available in WebGL. + */ + RobustAccess = SDL_GL_CONTEXT_ROBUST_ACCESS_FLAG, + + /** + * Context with reset isolation. + * @requires_gles Context flags are not available in WebGL. + */ + ResetIsolation = SDL_GL_CONTEXT_RESET_ISOLATION_FLAG, + + #if SDL_MAJOR_VERSION*1000 + SDL_MINOR_VERSION*100 + SDL_PATCHLEVEL >= 2006 || defined(DOXYGEN_GENERATING_OUTPUT) + /** + * Context without error reporting. Might result in better + * performance, but situations that would have generated errors + * instead cause undefined behavior. Enabled automatically if + * supported by the driver and the @ref Flag::GpuValidationNoError + * flag is set or if the `--magnum-gpu-validation` @ref GL-Context-usage-command-line "command-line option" + * is set to `no-error`. + * + * @note Available since SDL 2.0.6. + * @requires_gles Context flags are not available in WebGL. + * @m_since_latest + */ + /* Treated as a separate attribute and not a flag in SDL, thus + handling manually. */ + NoError = 1ull << 32, + #endif + #endif + + /** + * @copydoc GL::Context::Configuration::Flag::QuietLog + * @m_since_latest + */ + QuietLog = UnsignedLong(GL::Context::Configuration::Flag::QuietLog), + + /** + * @copydoc GL::Context::Configuration::Flag::VerboseLog + * @m_since_latest + */ + VerboseLog = UnsignedLong(GL::Context::Configuration::Flag::VerboseLog), + + /** + * @copydoc GL::Context::Configuration::Flag::GpuValidation + * @m_since_latest + */ + GpuValidation = UnsignedLong(GL::Context::Configuration::Flag::GpuValidation), + + /** + * @copydoc GL::Context::Configuration::Flag::GpuValidationNoError + * @m_since_latest + */ + GpuValidationNoError = UnsignedLong(GL::Context::Configuration::Flag::GpuValidationNoError) + }; + + /** + * @brief Context flags + * + * @see @ref setFlags(), @ref GL::Context::Flags + */ + typedef Containers::EnumSet Flags; + + explicit GLConfiguration(); + ~GLConfiguration(); + + /** @brief Context flags */ + Flags flags() const { + return Flag(UnsignedLong(GL::Context::Configuration::flags())); + } + + /** + * @brief Set context flags + * @return Reference to self (for method chaining) + * + * Default is @ref Flag::ForwardCompatible on desktop GL and no flags + * on OpenGL ES. To avoid clearing default flags by accident, prefer to + * use @ref addFlags() and @ref clearFlags() instead. + * @see @ref GL::Context::flags() + */ + GLConfiguration& setFlags(Flags flags) { + GL::Context::Configuration::setFlags(GL::Context::Configuration::Flag(UnsignedLong(flags))); + return *this; + } + + /** + * @brief Add context flags + * @return Reference to self (for method chaining) + * + * Unlike @ref setFlags(), ORs the flags with existing instead of + * replacing them. Useful for preserving the defaults. + * @see @ref clearFlags() + */ + GLConfiguration& addFlags(Flags flags) { + GL::Context::Configuration::addFlags(GL::Context::Configuration::Flag(UnsignedLong(flags))); + return *this; + } + + /** + * @brief Clear context flags + * @return Reference to self (for method chaining) + * + * Unlike @ref setFlags(), ANDs the inverse of @p flags with existing + * instead of replacing them. Useful for removing default flags. + * @see @ref addFlags() + */ + GLConfiguration& clearFlags(Flags flags) { + GL::Context::Configuration::clearFlags(GL::Context::Configuration::Flag(UnsignedLong(flags))); + return *this; + } + + #ifndef CORRADE_TARGET_EMSCRIPTEN + /** + * @brief Context version + * + * @note Not available in @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". + */ + GL::Version version() const { return _version; } + #endif + + /** + * @brief Set context version + * + * If requesting version greater or equal to OpenGL 3.1, core profile + * is used. The created context will then have any version which is + * backwards-compatible with requested one. Default is + * @ref GL::Version::None, i.e. any provided version is used. + * @note In @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten" this function + * does nothing (@ref GL::Version::GLES200 or + * @ref GL::Version::GLES300 is used implicitly based on the + * target). + */ + GLConfiguration& setVersion(GL::Version version) { + #ifndef CORRADE_TARGET_EMSCRIPTEN + _version = version; + #else + static_cast(version); + #endif + return *this; + } + + /** @brief Color buffer size */ + Vector4i colorBufferSize() const { return _colorBufferSize; } + + /** + * @brief Set color buffer size + * + * Default is @cpp {8, 8, 8, 8} @ce (8-bit-per-channel RGBA). + * @see @ref setDepthBufferSize(), @ref setStencilBufferSize() + */ + GLConfiguration& setColorBufferSize(const Vector4i& size) { + _colorBufferSize = size; + return *this; + } + + /** @brief Depth buffer size */ + Int depthBufferSize() const { return _depthBufferSize; } + + /** + * @brief Set depth buffer size + * + * Default is @cpp 24 @ce bits. + * @see @ref setColorBufferSize(), @ref setStencilBufferSize() + */ + GLConfiguration& setDepthBufferSize(Int size) { + _depthBufferSize = size; + return *this; + } + + /** @brief Stencil buffer size */ + Int stencilBufferSize() const { return _stencilBufferSize; } + + /** + * @brief Set stencil buffer size + * + * Default is @cpp 0 @ce bits (i.e., no stencil buffer). + * @see @ref setColorBufferSize(), @ref setDepthBufferSize() + */ + GLConfiguration& setStencilBufferSize(Int size) { + _stencilBufferSize = size; + return *this; + } + + /** @brief Sample count */ + Int sampleCount() const { return _sampleCount; } + + /** + * @brief Set sample count + * @return Reference to self (for method chaining) + * + * Default is @cpp 0 @ce, thus no multisampling. See also + * @ref GL::Renderer::Feature::Multisampling. + */ + GLConfiguration& setSampleCount(Int count) { + _sampleCount = count; + return *this; + } + + #ifndef CORRADE_TARGET_EMSCRIPTEN + /** + * @brief sRGB-capable default framebuffer + * + * @note Not available in @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". + */ + bool isSrgbCapable() const { return _srgbCapable; } + + /** + * @brief Set sRGB-capable default framebuffer + * @return Reference to self (for method chaining) + * + * Default is @cpp false @ce. See also + * @ref GL::Renderer::Feature::FramebufferSrgb. + * @note Not available in @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". + */ + GLConfiguration& setSrgbCapable(bool enabled) { + _srgbCapable = enabled; + return *this; + } + #endif + + /* Overloads to remove WTF-factor from method chaining order */ + #ifndef DOXYGEN_GENERATING_OUTPUT + MAGNUM_GL_CONTEXT_CONFIGURATION_SUBCLASS_IMPLEMENTATION(GLConfiguration) + #endif + + private: + Vector4i _colorBufferSize; + Int _depthBufferSize, _stencilBufferSize; + Int _sampleCount; + #ifndef CORRADE_TARGET_EMSCRIPTEN + GL::Version _version; + bool _srgbCapable; + #endif +}; + +#ifndef CORRADE_TARGET_EMSCRIPTEN +CORRADE_ENUMSET_OPERATORS(Sdl2Application::GLConfiguration::Flags) +#endif +#endif + +namespace Implementation { + enum class Sdl2DpiScalingPolicy: UnsignedByte { + /* Using 0 for an "unset" value */ + + #ifdef CORRADE_TARGET_APPLE + Framebuffer = 1, + #endif + + #ifndef CORRADE_TARGET_APPLE + #if !defined(CORRADE_TARGET_EMSCRIPTEN) && !defined(CORRADE_TARGET_ANDROID) + Virtual = 2, + #endif + + Physical = 3, + #endif + + Default + #ifdef CORRADE_TARGET_APPLE + = Framebuffer + #elif !defined(CORRADE_TARGET_EMSCRIPTEN) && !defined(CORRADE_TARGET_ANDROID) + = Virtual + #else + = Physical + #endif + }; +} + +/** +@brief Configuration + +@see @ref Sdl2Application(), @ref GLConfiguration, @ref create(), + @ref tryCreate() +*/ +class Sdl2Application::Configuration { + public: + /** + * @brief Window flag + * + * @see @ref WindowFlags, @ref setWindowFlags() + */ + enum class WindowFlag: Uint32 { + /** + * Resizable window. On iOS this allows the application to respond + * to display orientation changes, on + * @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten" this causes the + * framebuffer to be resized when the @cb{.html} @ce size + * changes. + * + * Implement @ref viewportEvent() to react to the resizing events. + */ + Resizable = SDL_WINDOW_RESIZABLE, + + #ifndef CORRADE_TARGET_EMSCRIPTEN + /** + * Fullscreen window + * + * @note Not available on @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". + */ + Fullscreen = SDL_WINDOW_FULLSCREEN, + + /** + * Fullscreen window at the current desktop resolution + * @m_since{2020,06} + * + * @note Not available on @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". + */ + FullscreenDesktop = SDL_WINDOW_FULLSCREEN_DESKTOP, + + /** + * No window decoration. On iOS this hides the menu bar. + * + * @note Not available on @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". + */ + Borderless = SDL_WINDOW_BORDERLESS, + #endif + + #ifndef CORRADE_TARGET_EMSCRIPTEN + /** + * Hidden window + * + * @note Not available on @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". + */ + Hidden = SDL_WINDOW_HIDDEN, + + /** + * Maximized window + * + * @note Not available on @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". + */ + Maximized = SDL_WINDOW_MAXIMIZED, + + /** + * Minimized window + * + * @note Not available on @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". + */ + Minimized = SDL_WINDOW_MINIMIZED, + + /** + * Window with mouse locked + * + * @note Not available on @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". + * @todo SDL_WINDOW_MOUSE_CAPTURE, also what all those do? isn't it + * redundant / better handled with cursor APIs? + */ + MouseLocked = SDL_WINDOW_INPUT_GRABBED, + + /** @todo SDL_WINDOW_INPUT_FOCUS, SDL_WINDOW_MOUSE_FOCUS, GLFW has + GLFW_FOCUSED (exposed as Focused) and GLFW_FOCUS_ON_SHOW (not + exposed) -- what's the relation? How to make these compatible? */ + + #if SDL_MAJOR_VERSION*1000 + SDL_MINOR_VERSION*100 + SDL_PATCHLEVEL >= 2005 || defined(DOXYGEN_GENERATING_OUTPUT) + /** + * Always on top + * @m_since{2020,06} + * + * @note Available since SDL 2.0.5, not available on + * @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". According to + * SDL docs works only on X11. + */ + AlwaysOnTop = SDL_WINDOW_ALWAYS_ON_TOP, + + /** + * Don't add the window to taskbar + * @m_since{2020,06} + * + * @note Available since SDL 2.0.5, not available on + * @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". According to + * SDL docs works only on X11. + */ + SkipTaskbar = SDL_WINDOW_SKIP_TASKBAR, + + /** + * Window should be treated as a utility window + * @m_since{2020,06} + * + * @note Available since SDL 2.0.5, not available on + * @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". According to + * SDL docs works only on X11. + */ + Utility = SDL_WINDOW_UTILITY, + + /** + * Window should be treated as a tooltip + * @m_since{2020,06} + * + * @note Available since SDL 2.0.5, not available on + * @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". According to + * SDL docs works only on X11. + */ + Tooltip = SDL_WINDOW_TOOLTIP, + + /** + * Window should be treated as a popup menu + * @m_since{2020,06} + * + * @note Available since SDL 2.0.5, not available on + * @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". According to + * SDL docs works only on X11. + */ + PopupMenu = SDL_WINDOW_POPUP_MENU, + #endif + #endif + + /** + * Do not create any GPU context. Use together with + * @ref Sdl2Application(const Arguments&, const Configuration&), + * @ref create(const Configuration&) or + * @ref tryCreate(const Configuration&) to prevent implicit + * creation of an OpenGL context. + */ + Contextless = 1u << 31, /* Hope this won't ever conflict with anything */ + + /** + * Request a window for use with OpenGL. Useful in combination with + * @ref WindowFlag::Contextless, otherwise enabled implicitly when + * creating an OpenGL context using @ref Sdl2Application(const Arguments&), + * @ref Sdl2Application(const Arguments&, const Configuration&, const GLConfiguration&), + * @ref create(const Configuration&, const GLConfiguration&) or + * @ref tryCreate(const Configuration&, const GLConfiguration&). + * @m_since{2019,10} + */ + OpenGL = SDL_WINDOW_OPENGL, + + #if !defined(CORRADE_TARGET_EMSCRIPTEN) && (SDL_MAJOR_VERSION*1000 + SDL_MINOR_VERSION*100 + SDL_PATCHLEVEL >= 2006 || defined(DOXYGEN_GENERATING_OUTPUT)) + /** + * Request a window for use with Vulkan. Useful in combination with + * @ref WindowFlag::Contextless. + * @note Available since SDL 2.0.6, not available on + * @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". + * @m_since{2019,10} + */ + Vulkan = SDL_WINDOW_VULKAN + #endif + }; + + /** + * @brief Window flags + * + * @see @ref setWindowFlags() + */ + #ifndef DOXYGEN_GENERATING_OUTPUT + typedef Containers::EnumSet= 2006 + |SDL_WINDOW_VULKAN + #endif + > WindowFlags; + #else + typedef Containers::EnumSet WindowFlags; + #endif + + /** + * @brief DPI scaling policy + * + * DPI scaling policy when requesting a particular window size. Can + * be overridden on command-line using `--magnum-dpi-scaling` or via + * the `MAGNUM_DPI_SCALING` environment variable. + * @see @ref setSize(), @ref Platform-Sdl2Application-dpi + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + enum class DpiScalingPolicy: UnsignedByte { + /** + * Framebuffer DPI scaling. The window will have the same size as + * requested, but the framebuffer size will be different. Supported + * only on macOS and iOS and is also the only supported value + * there. + */ + Framebuffer, + + /** + * Virtual DPI scaling. Scales the window based on UI scaling + * setting in the system. Falls back to + * @ref DpiScalingPolicy::Physical on platforms that don't support + * it. Supported only on desktop platforms (except macOS) and it's + * the default there. + * + * Equivalent to `--magnum-dpi-scaling virtual` passed on + * command-line. + */ + Virtual, + + /** + * Physical DPI scaling. Takes the requested window size as a + * physical size that a window would have on platform's default DPI + * and scales it to have the same size on given display physical + * DPI. On platforms that don't have a concept of a window it + * causes the framebuffer to match screen pixels 1:1 without any + * scaling. Supported on desktop platforms except macOS and on + * mobile and web. Default on mobile and web. + * + * Equivalent to `--magnum-dpi-scaling physical` passed on + * command-line. + */ + Physical, + + /** + * Default policy for current platform. Alias to one of + * @ref DpiScalingPolicy::Framebuffer, @ref DpiScalingPolicy::Virtual + * or @ref DpiScalingPolicy::Physical depending on platform. See + * @ref Platform-Sdl2Application-dpi for details. + */ + Default + }; + #else + typedef Implementation::Sdl2DpiScalingPolicy DpiScalingPolicy; + #endif + + /*implicit*/ Configuration(); + ~Configuration(); + + #if !defined(CORRADE_TARGET_EMSCRIPTEN) && !defined(CORRADE_TARGET_IOS) + /** + * @brief Window title + * + * The returned string view is + * @relativeref{Corrade,Containers::StringViewFlag::NullTerminated} and + * is valid until the next call to @ref setTitle(). + * @note Not available in @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten" + * and @ref CORRADE_TARGET_IOS "iOS". + */ + Containers::StringView title() const { return _title; } + #endif + + /** + * @brief Set window title + * @return Reference to self (for method chaining) + * + * Default is @cpp "Magnum SDL2 Application" @ce. + * @note On @ref CORRADE_TARGET_IOS "iOS" this function does nothing + * and is included only for compatibility. You need to set the + * title separately in platform-specific configuration file. + * @note Similarly, on @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten" this + * function is only for compatibility, as the page title is + * expected to be set by the HTML markup. However, it's possible + * to change the page title later (for example in response to + * application state change) using @ref setWindowTitle(). + */ + #if !defined(CORRADE_TARGET_EMSCRIPTEN) && !defined(CORRADE_TARGET_IOS) + Configuration& setTitle(const Containers::StringView title) { + _title = Containers::String::nullTerminatedGlobalView(title); + return *this; + } + #else + template Configuration& setTitle(const T&) { return *this; } + #endif + + /** @brief Window size */ + Vector2i size() const { return _size; } + + /** + * @brief DPI scaling policy + * + * If @ref dpiScaling() is non-zero, it has a priority over this value. + * The `--magnum-dpi-scaling` command-line option has a priority over + * any application-set value. + * @see @ref setSize(const Vector2i&, DpiScalingPolicy) + */ + DpiScalingPolicy dpiScalingPolicy() const { return _dpiScalingPolicy; } + + /** + * @brief Custom DPI scaling + * + * If zero, then @ref dpiScalingPolicy() has a priority over this + * value. The `--magnum-dpi-scaling` command-line option has a priority + * over any application-set value. + * @see @ref setSize(const Vector2i&, const Vector2&) + */ + Vector2 dpiScaling() const { return _dpiScaling; } + + /** + * @brief Set window size + * @param size Desired window size + * @param dpiScalingPolicy Policy based on which DPI scaling will be set + * @return Reference to self (for method chaining) + * + * Default is @cpp {800, 600} @ce on desktop platforms. On + * @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten" and iOS the default is a + * zero vector, meaning a value that matches the display or canvas size + * is autodetected. See @ref Platform-Sdl2Application-dpi for more + * information. + * @see @ref setSize(const Vector2i&, const Vector2&) + */ + Configuration& setSize(const Vector2i& size, DpiScalingPolicy dpiScalingPolicy = DpiScalingPolicy::Default) { + _size = size; + _dpiScalingPolicy = dpiScalingPolicy; + return *this; + } + + /** + * @brief Set window size with custom DPI scaling + * @param size Desired window size + * @param dpiScaling Custom DPI scaling value + * + * Compared to @ref setSize(const Vector2i&, DpiScalingPolicy) which + * autodetects the DPI scaling value according to given policy, this + * function sets the DPI scaling directly. The resulting + * @ref Sdl2Application::windowSize() is @cpp size*dpiScaling @ce and + * @ref Sdl2Application::dpiScaling() is @p dpiScaling. + */ + Configuration& setSize(const Vector2i& size, const Vector2& dpiScaling) { + _size = size; + _dpiScaling = dpiScaling; + return *this; + } + + /** @brief Window flags */ + WindowFlags windowFlags() const { return _windowFlags; } + + /** + * @brief Set window flags + * @return Reference to self (for method chaining) + * + * Default are none. To avoid clearing default flags by accident, + * prefer to use @ref addWindowFlags() and @ref clearWindowFlags() + * instead. + */ + Configuration& setWindowFlags(WindowFlags flags) { + _windowFlags = flags; + return *this; + } + + /** + * @brief Add window flags + * @return Reference to self (for method chaining) + * @m_since{2020,06} + * + * Unlike @ref setWindowFlags(), ORs the flags with existing instead of + * replacing them. Useful for preserving the defaults. + * @see @ref clearWindowFlags() + */ + Configuration& addWindowFlags(WindowFlags flags) { + _windowFlags |= flags; + return *this; + } + + /** + * @brief Clear window flags + * @return Reference to self (for method chaining) + * @m_since{2020,06} + * + * Unlike @ref setWindowFlags(), ANDs the inverse of @p flags with + * existing instead of replacing them. Useful for removing default + * flags. + * @see @ref addWindowFlags() + */ + Configuration& clearWindowFlags(WindowFlags flags) { + _windowFlags &= ~flags; + return *this; + } + + private: + #if !defined(CORRADE_TARGET_EMSCRIPTEN) && !defined(CORRADE_TARGET_IOS) + Containers::String _title; + #endif + Vector2i _size; + DpiScalingPolicy _dpiScalingPolicy; + WindowFlags _windowFlags; + Vector2 _dpiScaling; +}; + +/** +@brief Exit event + +@see @ref exitEvent() +*/ +class Sdl2Application::ExitEvent { + public: + /** @brief Copying is not allowed */ + ExitEvent(const ExitEvent&) = delete; + + /** @brief Moving is not allowed */ + ExitEvent(ExitEvent&&) = delete; + + /** @brief Copying is not allowed */ + ExitEvent& operator=(const ExitEvent&) = delete; + + /** @brief Moving is not allowed */ + ExitEvent& operator=(ExitEvent&&) = delete; + + /** @brief Whether the event is accepted */ + bool isAccepted() const { return _accepted; } + + /** + * @brief Set event as accepted + * + * If the event is ignored (i.e., not set as accepted) in + * @ref exitEvent(), the application won't exit. Default implementation + * of @ref exitEvent() accepts the event. + */ + void setAccepted(bool accepted = true) { _accepted = accepted; } + + /** + * @brief Underlying SDL event + * + * Of type `SDL_QUIT`. + * @see @ref Sdl2Application::anyEvent() + */ + const SDL_Event& event() const { return _event; } + + private: + friend Sdl2Application; + + explicit ExitEvent(const SDL_Event& event): _event(event), _accepted(false) {} + + const SDL_Event& _event; + bool _accepted; +}; + +/** +@brief Viewport event + +@see @ref viewportEvent() +*/ +class Sdl2Application::ViewportEvent { + public: + /** @brief Copying is not allowed */ + ViewportEvent(const ViewportEvent&) = delete; + + /** @brief Moving is not allowed */ + ViewportEvent(ViewportEvent&&) = delete; + + /** @brief Copying is not allowed */ + ViewportEvent& operator=(const ViewportEvent&) = delete; + + /** @brief Moving is not allowed */ + ViewportEvent& operator=(ViewportEvent&&) = delete; + + /** + * @brief Window size + * + * On some platforms with HiDPI displays, window size can be different + * from @ref framebufferSize(). See @ref Platform-Sdl2Application-dpi + * for more information. + * @see @ref Sdl2Application::windowSize() + */ + Vector2i windowSize() const { return _windowSize; } + + #if defined(MAGNUM_TARGET_GL) || defined(DOXYGEN_GENERATING_OUTPUT) + /** + * @brief Framebuffer size + * + * On some platforms with HiDPI displays, framebuffer size can be + * different from @ref windowSize(). See + * @ref Platform-Sdl2Application-dpi for more information. + * + * @note This function is available only if Magnum is compiled with + * @ref MAGNUM_TARGET_GL enabled (done by default). See + * @ref building-features for more information. + * + * @see @ref Sdl2Application::framebufferSize() + */ + Vector2i framebufferSize() const { return _framebufferSize; } + #endif + + /** + * @brief DPI scaling + * + * On some platforms moving an app between displays can result in DPI + * scaling value being changed in tandem with a window/framebuffer + * size. Simply resizing a window doesn't change the DPI scaling value. + * See @ref Platform-Sdl2Application-dpi for more information. + * @see @ref Sdl2Application::dpiScaling() + */ + Vector2 dpiScaling() const { return _dpiScaling; } + + #ifndef CORRADE_TARGET_EMSCRIPTEN + /** + * @brief Underlying SDL event + * + * Of type `SDL_WINDOWEVENT`. + * @note Not available in @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". + * @see @ref Sdl2Application::anyEvent() + */ + const SDL_Event& event() const { return _event; } + #endif + + private: + friend Sdl2Application; + + explicit ViewportEvent( + #ifndef CORRADE_TARGET_EMSCRIPTEN + const SDL_Event& event, + #endif + const Vector2i& windowSize, + #ifdef MAGNUM_TARGET_GL + const Vector2i& framebufferSize, + #endif + const Vector2& dpiScaling): + #ifndef CORRADE_TARGET_EMSCRIPTEN + _event(event), + #endif + _windowSize{windowSize}, + #ifdef MAGNUM_TARGET_GL + _framebufferSize{framebufferSize}, + #endif + _dpiScaling{dpiScaling} {} + + #ifndef CORRADE_TARGET_EMSCRIPTEN + const SDL_Event& _event; + #endif + const Vector2i _windowSize; + #ifdef MAGNUM_TARGET_GL + const Vector2i _framebufferSize; + #endif + const Vector2 _dpiScaling; +}; + +/** +@brief Base for input events + +@see @ref KeyEvent, @ref MouseEvent, @ref MouseMoveEvent, @ref keyPressEvent(), + @ref keyReleaseEvent(), @ref mousePressEvent(), @ref mouseReleaseEvent(), + @ref mouseMoveEvent() +*/ +class Sdl2Application::InputEvent { + public: + /** + * @brief Modifier + * + * @see @ref Modifiers, @ref KeyEvent::modifiers(), + * @ref MouseEvent::modifiers(), @ref MouseMoveEvent::modifiers() + */ + enum class Modifier: Uint16 { + /** + * Shift + * + * @see @ref KeyEvent::Key::LeftShift, @ref KeyEvent::Key::RightShift + */ + Shift = KMOD_SHIFT, + + /** + * Ctrl + * + * @see @ref KeyEvent::Key::LeftCtrl, @ref KeyEvent::Key::RightCtrl + */ + Ctrl = KMOD_CTRL, + + /** + * Alt + * + * @see @ref KeyEvent::Key::LeftAlt, @ref KeyEvent::Key::RightAlt + */ + Alt = KMOD_ALT, + + /** + * Super key (Windows/⌘) + * + * @see @ref KeyEvent::Key::LeftSuper, @ref KeyEvent::Key::RightSuper + */ + Super = KMOD_GUI, + + /** + * AltGr + * + * @see @ref KeyEvent::Key::AltGr + */ + AltGr = KMOD_MODE, + + CapsLock = KMOD_CAPS, /**< Caps lock */ + NumLock = KMOD_NUM /**< Num lock */ + }; + + /** + * @brief Set of modifiers + * + * @see @ref KeyEvent::modifiers(), @ref MouseEvent::modifiers(), + * @ref MouseMoveEvent::modifiers() + */ + typedef Containers::EnumSet Modifiers; + + /** @brief Copying is not allowed */ + InputEvent(const InputEvent&) = delete; + + /** @brief Moving is not allowed */ + InputEvent(InputEvent&&) = delete; + + /** @brief Copying is not allowed */ + InputEvent& operator=(const InputEvent&) = delete; + + /** @brief Moving is not allowed */ + InputEvent& operator=(InputEvent&&) = delete; + + /** @brief Whether the event is accepted */ + bool isAccepted() const { return _accepted; } + + /** + * @brief Set event as accepted + * + * If the event is ignored (i.e., not set as accepted), it might be + * propagated elsewhere, for example to another screen when using + * @ref BasicScreenedApplication "ScreenedApplication". By default is + * each event ignored and thus propagated. + */ + void setAccepted(bool accepted = true) { _accepted = accepted; } + + /** + * @brief Underlying SDL event + * + * Of type `SDL_KEYDOWN` / `SDL_KEYUP` for @ref KeyEvent, + * `SDL_MOUSEBUTTONUP` / `SDL_MOUSEBUTTONDOWN` for @ref MouseEvent, + * `SDL_MOUSEWHEEL` for @ref MouseScrollEvent and `SDL_MOUSEMOTION` for + * @ref MouseMoveEvent. + * @see @ref Sdl2Application::anyEvent() + */ + const SDL_Event& event() const { return _event; } + + #ifndef DOXYGEN_GENERATING_OUTPUT + protected: + explicit InputEvent(const SDL_Event& event): _event(event), _accepted(false) {} + + ~InputEvent() = default; + #endif + + private: + const SDL_Event& _event; + bool _accepted; +}; + +/** +@brief Key event + +@see @ref keyPressEvent(), @ref keyReleaseEvent() +*/ +class Sdl2Application::KeyEvent: public Sdl2Application::InputEvent { + public: + /** + * @brief Key + * + * @see @ref key() + */ + enum class Key: SDL_Keycode { + Unknown = SDLK_UNKNOWN, /**< Unknown key */ + + /** + * Left Shift + * + * @see @ref InputEvent::Modifier::Shift + */ + LeftShift = SDLK_LSHIFT, + + /** + * Right Shift + * + * @see @ref InputEvent::Modifier::Shift + */ + RightShift = SDLK_RSHIFT, + + /** + * Left Ctrl + * + * @see @ref InputEvent::Modifier::Ctrl + */ + LeftCtrl = SDLK_LCTRL, + + /** + * Right Ctrl + * + * @see @ref InputEvent::Modifier::Ctrl + */ + RightCtrl = SDLK_RCTRL, + + /** + * Left Alt + * + * @see @ref InputEvent::Modifier::Alt + */ + LeftAlt = SDLK_LALT, + + /** + * Right Alt + * + * @see @ref InputEvent::Modifier::Alt + */ + RightAlt = SDLK_RALT, + + /** + * Left Super key (Windows/⌘) + * + * @see @ref InputEvent::Modifier::Super + */ + LeftSuper = SDLK_LGUI, + + /** + * Right Super key (Windows/⌘) + * + * @see @ref InputEvent::Modifier::Super + */ + RightSuper = SDLK_RGUI, + + /** + * AltGr + * + * @see @ref InputEvent::Modifier::AltGr + */ + AltGr = SDLK_MODE, + + Enter = SDLK_RETURN, /**< Enter */ + Esc = SDLK_ESCAPE, /**< Escape */ + + Up = SDLK_UP, /**< Up arrow */ + Down = SDLK_DOWN, /**< Down arrow */ + Left = SDLK_LEFT, /**< Left arrow */ + Right = SDLK_RIGHT, /**< Right arrow */ + Home = SDLK_HOME, /**< Home */ + End = SDLK_END, /**< End */ + PageUp = SDLK_PAGEUP, /**< Page up */ + PageDown = SDLK_PAGEDOWN, /**< Page down */ + Backspace = SDLK_BACKSPACE, /**< Backspace */ + Insert = SDLK_INSERT, /**< Insert */ + Delete = SDLK_DELETE, /**< Delete */ + + F1 = SDLK_F1, /**< F1 */ + F2 = SDLK_F2, /**< F2 */ + F3 = SDLK_F3, /**< F3 */ + F4 = SDLK_F4, /**< F4 */ + F5 = SDLK_F5, /**< F5 */ + F6 = SDLK_F6, /**< F6 */ + F7 = SDLK_F7, /**< F7 */ + F8 = SDLK_F8, /**< F8 */ + F9 = SDLK_F9, /**< F9 */ + F10 = SDLK_F10, /**< F10 */ + F11 = SDLK_F11, /**< F11 */ + F12 = SDLK_F12, /**< F12 */ + + Space = SDLK_SPACE, /**< Space */ + Tab = SDLK_TAB, /**< Tab */ + + /** + * Quote (') + * @m_since{2020,06} + */ + Quote = SDLK_QUOTE, + + Comma = SDLK_COMMA, /**< Comma */ + Period = SDLK_PERIOD, /**< Period */ + Minus = SDLK_MINUS, /**< Minus */ + Plus = SDLK_PLUS, /**< Plus */ + Slash = SDLK_SLASH, /**< Slash */ + Percent = SDLK_PERCENT, /**< Percent */ + Semicolon = SDLK_SEMICOLON, /**< Semicolon (`;`) */ + Equal = SDLK_EQUALS, /**< Equal */ + + /** + * Left bracket (`[`) + * @m_since{2020,06} + */ + LeftBracket = SDLK_LEFTBRACKET, + + /** + * Right bracket (`]`) + * @m_since{2020,06} + */ + RightBracket = SDLK_RIGHTBRACKET, + + /** + * Backslash (`\`) + * @m_since{2020,06} + */ + Backslash = SDLK_BACKSLASH, + + /** + * Backquote (`) + * @m_since{2020,06} + */ + Backquote = SDLK_BACKQUOTE, + + /* no equivalent for GlfwApplication's World1 / World2 */ + + Zero = SDLK_0, /**< Zero */ + One = SDLK_1, /**< One */ + Two = SDLK_2, /**< Two */ + Three = SDLK_3, /**< Three */ + Four = SDLK_4, /**< Four */ + Five = SDLK_5, /**< Five */ + Six = SDLK_6, /**< Six */ + Seven = SDLK_7, /**< Seven */ + Eight = SDLK_8, /**< Eight */ + Nine = SDLK_9, /**< Nine */ + + A = SDLK_a, /**< Letter A */ + B = SDLK_b, /**< Letter B */ + C = SDLK_c, /**< Letter C */ + D = SDLK_d, /**< Letter D */ + E = SDLK_e, /**< Letter E */ + F = SDLK_f, /**< Letter F */ + G = SDLK_g, /**< Letter G */ + H = SDLK_h, /**< Letter H */ + I = SDLK_i, /**< Letter I */ + J = SDLK_j, /**< Letter J */ + K = SDLK_k, /**< Letter K */ + L = SDLK_l, /**< Letter L */ + M = SDLK_m, /**< Letter M */ + N = SDLK_n, /**< Letter N */ + O = SDLK_o, /**< Letter O */ + P = SDLK_p, /**< Letter P */ + Q = SDLK_q, /**< Letter Q */ + R = SDLK_r, /**< Letter R */ + S = SDLK_s, /**< Letter S */ + T = SDLK_t, /**< Letter T */ + U = SDLK_u, /**< Letter U */ + V = SDLK_v, /**< Letter V */ + W = SDLK_w, /**< Letter W */ + X = SDLK_x, /**< Letter X */ + Y = SDLK_y, /**< Letter Y */ + Z = SDLK_z, /**< Letter Z */ + + /** + * Caps lock + * @m_since_latest + */ + CapsLock = SDLK_CAPSLOCK, + + /** + * Scroll lock + * @m_since_latest + */ + ScrollLock = SDLK_SCROLLLOCK, + + /** + * Num lock + * @m_since_latest + */ + NumLock = SDLK_NUMLOCKCLEAR, + + /** + * Print screen + * @m_since_latest + */ + PrintScreen = SDLK_PRINTSCREEN, + + /** + * Pause + * @m_since_latest + */ + Pause = SDLK_PAUSE, + + /** + * Menu + * @m_since_latest + */ + Menu = SDLK_APPLICATION, + + NumZero = SDLK_KP_0, /**< Numpad zero */ + NumOne = SDLK_KP_1, /**< Numpad one */ + NumTwo = SDLK_KP_2, /**< Numpad two */ + NumThree = SDLK_KP_3, /**< Numpad three */ + NumFour = SDLK_KP_4, /**< Numpad four */ + NumFive = SDLK_KP_5, /**< Numpad five */ + NumSix = SDLK_KP_6, /**< Numpad six */ + NumSeven = SDLK_KP_7, /**< Numpad seven */ + NumEight = SDLK_KP_8, /**< Numpad eight */ + NumNine = SDLK_KP_9, /**< Numpad nine */ + NumDecimal = SDLK_KP_DECIMAL, /**< Numpad decimal */ + NumDivide = SDLK_KP_DIVIDE, /**< Numpad divide */ + NumMultiply = SDLK_KP_MULTIPLY, /**< Numpad multiply */ + NumSubtract = SDLK_KP_MINUS, /**< Numpad subtract */ + NumAdd = SDLK_KP_PLUS, /**< Numpad add */ + NumEnter = SDLK_KP_ENTER, /**< Numpad enter */ + NumEqual = SDLK_KP_EQUALS /**< Numpad equal */ + }; + + /** + * @brief Name for given key + * + * Human-readable localized UTF-8 name for given @p key, intended for + * displaying to the user in e.g. key binding configuration. If there + * is no name for given key, empty string is returned. The returned + * view is always @relativeref{Corrade,Containers::StringViewFlag::NullTerminated} + * and is valid at least until the next call to this function, to + * @ref keyName() const or to the underlying @cpp SDL_GetKeyName() @ce + * API. + */ + static Containers::StringView keyName(Key key); + + /** + * @brief Key + * + * @see @ref keyName() + */ + Key key() const { return _key; } + + /** + * @brief Key name + * + * Human-readable localized UTF-8 name for the key returned by + * @ref key(), intended for displaying to the user in e.g. + * key binding configuration. If there is no name for that key, empty + * string is returned. The returned string is always @relativeref{Corrade,Containers::StringViewFlag::NullTerminated} + * and is valid at least until the next call to this function, to + * @ref keyName(Key) or to the underlying @cpp SDL_GetKeyName() @ce + * API. + */ + Containers::StringView keyName() const; + + /** @brief Modifiers */ + Modifiers modifiers() const { return _modifiers; } + + /** + * @brief Whether the key press is repeated + * + * Returns @cpp true @ce if the key press event is repeated, + * @cpp false @ce if not or if this was key release event. + */ + bool isRepeated() const { return _repeated; } + + private: + friend Sdl2Application; + + explicit KeyEvent(const SDL_Event& event, Key key, Modifiers modifiers, bool repeated): InputEvent{event}, _key{key}, _modifiers{modifiers}, _repeated{repeated} {} + + const Key _key; + const Modifiers _modifiers; + const bool _repeated; +}; + +/** +@brief Mouse event + +@see @ref MouseMoveEvent, @ref MouseScrollEvent, @ref mousePressEvent(), + @ref mouseReleaseEvent() +*/ +class Sdl2Application::MouseEvent: public Sdl2Application::InputEvent { + public: + /** + * @brief Mouse button + * + * @see @ref button() + */ + enum class Button: Uint8 { + Left = SDL_BUTTON_LEFT, /**< Left button */ + Middle = SDL_BUTTON_MIDDLE, /**< Middle button */ + Right = SDL_BUTTON_RIGHT, /**< Right button */ + + /** First extra button (e.g. wheel left) */ + X1 = SDL_BUTTON_X1, + + /** Second extra button (e.g. wheel right) */ + X2 = SDL_BUTTON_X2, + }; + + /** @brief Button */ + Button button() const { return _button; } + + /** @brief Position */ + Vector2i position() const { return _position; } + + #ifndef CORRADE_TARGET_EMSCRIPTEN + /** + * @brief Click count + * + * @note Not available in @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". + */ + Int clickCount() const { return _clickCount; } + #endif + + /** + * @brief Modifiers + * + * Lazily populated on first request. + */ + Modifiers modifiers(); + + private: + friend Sdl2Application; + + explicit MouseEvent(const SDL_Event& event, Button button, const Vector2i& position + #ifndef CORRADE_TARGET_EMSCRIPTEN + , Int clickCount + #endif + ): InputEvent{event}, _button{button}, _position{position} + #ifndef CORRADE_TARGET_EMSCRIPTEN + , _clickCount{clickCount} + #endif + {} + + const Button _button; + const Vector2i _position; + #ifndef CORRADE_TARGET_EMSCRIPTEN + const Int _clickCount; + #endif + Containers::Optional _modifiers; +}; + +/** +@brief Mouse move event + +@see @ref MouseEvent, @ref MouseScrollEvent, @ref mouseMoveEvent() +*/ +class Sdl2Application::MouseMoveEvent: public Sdl2Application::InputEvent { + public: + /** + * @brief Mouse button + * + * @see @ref Buttons, @ref buttons() + */ + enum class Button: Uint32 { + Left = SDL_BUTTON_LMASK, /**< Left button */ + Middle = SDL_BUTTON_MMASK, /**< Middle button */ + Right = SDL_BUTTON_RMASK, /**< Right button */ + + /** First extra button (e.g. wheel left) */ + X1 = SDL_BUTTON_X1MASK, + + /** Second extra button (e.g. wheel right) */ + X2 = SDL_BUTTON_X2MASK + }; + + /** + * @brief Set of mouse buttons + * + * @see @ref buttons() + */ + typedef Containers::EnumSet