diff --git a/doc/changelog-old.dox b/doc/changelog-old.dox index 1282fe35d..00ba64a62 100644 --- a/doc/changelog-old.dox +++ b/doc/changelog-old.dox @@ -108,7 +108,7 @@ Released 2018-05-01, tagged as - Ability to create @ref Platform::Sdl2Application and @ref Platform::GlfwApplication classes without implicitly created OpenGL - context by passing @ref Platform::Sdl2Application::Configuration::WindowFlag::Contextless "Configuration::WindowFlag::Contextless" + context by passing @cpp Configuration::WindowFlag::Contextless @ce to them. These two can be now also built completely without the GL library. - Added @ref Platform::AndroidApplication::windowSize() - Added @ref Platform::AndroidApplication::nativeActivity() to access @@ -157,10 +157,9 @@ Released 2018-05-01, tagged as @subsubsection changelog-2018-04-changes-platform Platform libraries -- Separated @ref Platform::Sdl2Application::Configuration "Platform::*Application::Configuration" - into @ref Platform::Sdl2Application::Configuration "Configuration" and - @ref Platform::Sdl2Application::GLConfiguration "GLConfiguration" to allow - creation of non-GL application instances in the future +- Separated @cpp Platform::*Application::Configuration @ce + into @cpp Configuration @ce and @cpp GLConfiguration @ce to allow creation + of non-GL application instances in the future @subsubsection changelog-2018-04-changes-plugins Plugins @@ -753,10 +752,10 @@ a high-level overview. possible - Ported @ref magnum-gl-info "magnum-info" to Emscripten - First-class support for scroll events in - @ref Platform::Sdl2Application::MouseScrollEvent (see - [mosra/magnum#157](https://github.com/mosra/magnum/pull/157)) -- Added @ref Platform::Sdl2Application::MouseEvent::clickCount() -- Added @ref Platform::Sdl2Application::multiGestureEvent() + @cpp Platform::Sdl2Application::MouseScrollEvent @ce + (see [mosra/magnum#157](https://github.com/mosra/magnum/pull/157)) +- Added @cpp Platform::Sdl2Application::MouseEvent::clickCount() @ce +- Added @cpp Platform::Sdl2Application::multiGestureEvent() @ce - Exposing key repeat in @ref Platform::Sdl2Application::KeyEvent::isRepeated() "Platform::*Application::KeyEvent::isRepeated()" (see [mosra/magnum#161](https://github.com/mosra/magnum/issues/161), @@ -773,8 +772,8 @@ a high-level overview. version is not what the application wants (as opposed to just aborting the application) (see [mosra/magnum#105](https://github.com/mosra/magnum/issues/105)) - Added @cpp Platform::Sdl2Application::Configuration::setSRGBCapable() @ce -- Added @ref Platform::Sdl2Application::Configuration::WindowFlag::Borderless - and `Platform::Sdl2Application::Configuration::WindowFlag::AllowHighDpi` +- Added @cpp Platform::Sdl2Application::Configuration::WindowFlag::Borderless @ce + and @cpp Platform::Sdl2Application::Configuration::WindowFlag::AllowHighDpi @ce for iOS and macOS - Added @ref Platform::WindowlessGlxApplication::Configuration::setFlags() "Platform::Windowless*Application::Configuration::setFlags()" with @ref Platform::WindowlessGlxApplication::Configuration::Flag::Debug "Flag::Debug" @@ -786,7 +785,7 @@ a high-level overview. with @ref Platform::GlfwApplication - Added modifier keys to @ref Platform::Sdl2Application::KeyEvent::Key "Platform::*Application::KeyEvent::Key" -- Added @ref Platform::Sdl2Application::InputEvent::Modifier::Super to be +- Added @cpp Platform::Sdl2Application::InputEvent::Modifier::Super @ce to be consistent with @ref Platform::GlfwApplication (see [mosra/magnum#159](https://github.com/mosra/magnum/pull/159)) - Added @ref Platform::Sdl2Application::KeyEvent::keyName() "Platform::*Application::KeyEvent::keyName()" @@ -1102,10 +1101,10 @@ a high-level overview. `Platform::GlfwApplication::MouseEvent::Button::WheelDown` mouse events are deprecated, use @ref Platform::Sdl2Application::mouseScrollEvent() / @ref Platform::GlfwApplication::mouseScrollEvent() and - @ref Platform::Sdl2Application::MouseScrollEvent / + @cpp Platform::Sdl2Application::MouseScrollEvent @ce / @ref Platform::GlfwApplication::MouseScrollEvent instead -- @ref Platform::Sdl2Application::Sdl2Application() "Platform::*Application::*Application()" - and @ref Platform::WindowlessGlxApplication::WindowlessGlxApplication() "Platform::Windowless*Application::Windowless*Application()" +- @cpp Platform::*Application::*Application() @ce and + @ref Platform::WindowlessGlxApplication::WindowlessGlxApplication() "Platform::Windowless*Application::Windowless*Application()" constructors taking @cpp nullptr @ce are deprecated, use constructors taking @ref NoCreateT instead to create an application without creating OpenGL context @@ -1844,7 +1843,7 @@ for a high-level overview. purely integral coordinates, useful e.g. for UI or 2D platformers. - Detailed collision queries and new `InvertedSphere` shape in Shapes library - Texture support in @cpp Shaders::Flat @ce -- Mouse button queries in @ref Platform::Sdl2Application::MouseMoveEvent "Platform::*Application::MouseMoveEvent" +- Mouse button queries in @cpp Platform::*Application::MouseMoveEvent @ce @subsection changelog-2013-10-changes Changes diff --git a/doc/changelog.dox b/doc/changelog.dox index f77184d27..c48d986ba 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -762,13 +762,13 @@ See also: - Added @ref Platform::EmscriptenApplication::Configuration::addWindowFlags() and @ref Platform::EmscriptenApplication::Configuration::clearWindowFlags() for consistency with other application implementations -- Added @ref Platform::Sdl2Application::KeyEvent::Key::CapsLock, - @relativeref{Platform::Sdl2Application::KeyEvent::Key,ScrollLock}, - @relativeref{Platform::Sdl2Application::KeyEvent::Key,NumLock}, - @relativeref{Platform::Sdl2Application::KeyEvent::Key,PrintScreen}, - @relativeref{Platform::Sdl2Application::KeyEvent::Key,Pause} and - @relativeref{Platform::Sdl2Application::KeyEvent::Key,Menu} for consistency - with @ref Platform::EmscriptenApplication and +- Added @ref Platform::Sdl2ApplicationWindow::KeyEvent::Key::CapsLock, + @relativeref{Platform::Sdl2ApplicationWindow::KeyEvent::Key,ScrollLock}, + @relativeref{Platform::Sdl2ApplicationWindow::KeyEvent::Key,NumLock}, + @relativeref{Platform::Sdl2ApplicationWindow::KeyEvent::Key,PrintScreen}, + @relativeref{Platform::Sdl2ApplicationWindow::KeyEvent::Key,Pause} and + @relativeref{Platform::Sdl2ApplicationWindow::KeyEvent::Key,Menu} for + consistency with @ref Platform::EmscriptenApplication and @ref Platform::GlfwApplication (see [mosra/magnum#547](https://github.com/mosra/magnum/pull/547)) - @ref Platform::Sdl2Application now overrides SDL's default behavior that prevents computer from entering a power-saving mode while the application @@ -1160,9 +1160,9 @@ See also: destroyed could fail with an error saying "cannot make the previous context current" on certain system. This was due to EGL not destroying the context if it's still made current. -- Fixed handling of @ref Platform::Sdl2Application::InputEvent::Modifier::Super, - which was misreported as @relativeref{Platform::Sdl2Application::InputEvent::Modifier,Alt} (see - [mosra/magnum#547](https://github.com/mosra/magnum/pull/547)) +- Fixed handling of @ref Platform::Sdl2ApplicationWindow::InputEvent::Modifier::Super, + which was misreported as @relativeref{Platform::Sdl2ApplicationWindow::InputEvent::Modifier,Alt} + (see [mosra/magnum#547](https://github.com/mosra/magnum/pull/547)) - For meshes with multiple sets of vertex attributes (such as texture coordinates), @ref MeshTools::compile() should be using only the first set but it wasn't. @@ -1345,6 +1345,10 @@ See also: performance and a default, use @relativeref{Platform::EmscriptenApplication::GLConfiguration,Flag::PowerPreferenceLowPower} or @relativeref{Platform::EmscriptenApplication::GLConfiguration,Flag::PowerPreferenceHighPerformance} instead +- GPU-context-related flags were moved from + @ref Platform::Sdl2Application::Configuration::WindowFlag to a new + @ref Platform::Sdl2Application::Configuration::Flag enum, as they're set + globally for all application windows and can't differ per-window - @cpp Shaders::DistanceFieldVector @ce, @cpp Shaders::Flat @ce, @cpp Shaders::Generic @ce, @cpp Shaders::MeshVisualizer2D @ce, @cpp Shaders::MeshVisualizer3D @ce, @cpp Shaders::Phong @ce, @@ -2048,12 +2052,12 @@ Released 2020-06-27, tagged as @ref Platform::AbstractXApplication::mainLoopIteration() for consistency with @ref Platform::Sdl2Application (see [mosra/magnum#387](https://github.com/mosra/magnum/pull/387)) -- Added @ref Platform::Sdl2Application::KeyEvent::Key::Quote "Key::Quote", - @ref Platform::Sdl2Application::KeyEvent::Key::LeftBracket "Key::LeftBracket", - @ref Platform::Sdl2Application::KeyEvent::Key::RightBracket "Key::RightBracket", - @ref Platform::Sdl2Application::KeyEvent::Key::Backslash "Key::Backslash" and - @ref Platform::Sdl2Application::KeyEvent::Key::Backquote "Key::Backquote" - keys to @ref Platform::Sdl2Application::KeyEvent and +- Added @relativeref{Platform::Sdl2ApplicationWindow::KeyEvent,Key::Quote}, + @relativeref{Platform::Sdl2ApplicationWindow::KeyEvent,Key::LeftBracket}, + @relativeref{Platform::Sdl2ApplicationWindow::KeyEvent,Key::RightBracket}, + @relativeref{Platform::Sdl2ApplicationWindow::KeyEvent,Key::Backslash} and + @relativeref{Platform::Sdl2ApplicationWindow::KeyEvent,Key::Backquote} + keys to @cpp Platform::Sdl2Application::KeyEvent @ce and @ref Platform::GlfwApplication::KeyEvent - Added @ref Platform::GlfwApplication::KeyEvent::Key::World1 and @ref Platform::GlfwApplication::KeyEvent::Key::World2 @@ -2071,12 +2075,9 @@ Released 2020-06-27, tagged as - CUDA device selection in @ref Platform::WindowlessEglApplication (see [mosra/magnum#449](https://github.com/mosra/magnum/pull/449)) - Added @ref Platform::GlfwApplication::Configuration::WindowFlag::Borderless -- Added @ref Platform::Sdl2Application::Configuration::WindowFlag::FullscreenDesktop, - @ref Platform::Sdl2Application::Configuration::WindowFlag::AlwaysOnTop "AlwaysOnTop", - @ref Platform::Sdl2Application::Configuration::WindowFlag::SkipTaskbar "SkipTaskbar", - @ref Platform::Sdl2Application::Configuration::WindowFlag::Utility "Utility", - @ref Platform::Sdl2Application::Configuration::WindowFlag::Tooltip "Tooltip" - and @ref Platform::Sdl2Application::Configuration::WindowFlag::PopupMenu "PopupMenu" +- Added @cpp Platform::Sdl2Application::Configuration::WindowFlag::FullscreenDesktop @ce, + @cpp AlwaysOnTop @ce, @cpp SkipTaskbar @ce, @cpp Utility @ce, + @cpp Tooltip @ce and @cpp PopupMenu @ce - Added @ref Platform::Sdl2Application::Configuration::addWindowFlags() and @ref Platform::Sdl2Application::Configuration::clearWindowFlags() "clearWindowFlags()" for consistency with similar functions in @@ -2378,7 +2379,7 @@ Released 2020-06-27, tagged as - The @ref Primitives::cylinderSolid() and @ref Primitives::coneSolid() primitives were missing a face when both caps and texture coordinates were enabled (see [mosra/magnum#386](https://github.com/mosra/magnum/issues/386)) -- @ref Platform::Sdl2Application::Configuration::WindowFlag::Vulkan was +- @cpp Platform::Sdl2Application::Configuration::WindowFlag::Vulkan @ce was enabled conditionally only for SDL >= 2.0.6, but the version defines were never included so it was always disabled - Fixed missing handling of @@ -2409,7 +2410,7 @@ Released 2020-06-27, tagged as caused @ref Platform::Sdl2Application::setMinimalLoopPeriod() "setMinimalLoopPeriod()" to be ignored even though Vsync was in fact not enabled. - It was not possible to override DPI scaling using - @ref Platform::Sdl2Application::Configuration as command-line arguments + @cpp Platform::Sdl2Application::Configuration @ce as command-line arguments always got a priority (see [mosra/magnum#416](https://github.com/mosra/magnum/issues/416)) - Fixed an otherwise harmless OOB access in @ref MeshTools::tipsifyInPlace() that could trigger ASan or debug iterator errors @@ -2911,10 +2912,10 @@ Released 2019-10-24, tagged as in @ref Platform::GlfwApplication and @ref Platform::EmscriptenApplication) for changing window title at runtime as opposed to setting them on application startup -- Added @ref Platform::Sdl2Application::Configuration::WindowFlag::OpenGL and - @ref Platform::Sdl2Application::Configuration::WindowFlag::Vulkan "WindowFlag::Vulkan", - meant to be used together with @ref Platform::Sdl2Application::Configuration::WindowFlag::Contextless for - manual creation of OpenGL contexts and Vulkan instances +- Added @cpp Platform::Sdl2Application::Configuration::WindowFlag::OpenGL @ce + and @cpp WindowFlag::Vulkan @ce, meant to be used together with + @cpp Platform::Sdl2Application::Configuration::WindowFlag::Contextless @ce + for manual creation of OpenGL contexts and Vulkan instances - @ref Platform::WindowlessEglApplication now uses the @m_class{m-doc-external} [EGL_EXT_device_enumeration](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_enumeration.txt), @m_class{m-doc-external} [EGL_EXT_platform_base](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_platform_base.txt) and diff --git a/doc/platform.dox b/doc/platform.dox index 08fe06ebd..685e90c5d 100644 --- a/doc/platform.dox +++ b/doc/platform.dox @@ -136,8 +136,8 @@ target_link_libraries(myapplication @section platform-configuration Specifying configuration By default the application is created with some reasonable defaults (e.g. -window size 800x600 pixels). If you want something else, you can pass -@ref Platform::Sdl2Application::Configuration "Configuration" instance to +window size 800x600 pixels). If you want something else, you can pass a +@relativeref{Platform::Sdl2ApplicationWindow,Configuration} instance to the application constructor. Using method chaining it can be done conveniently like this: @@ -148,7 +148,7 @@ this: Sometimes you may want to set up the application based on a configuration file or system introspection, which can't really be done inside the base class initializer. You can specify @ref NoCreate in the constructor instead and pass -the @relativeref{Platform::Sdl2Application,Configuration} later to a +the @relativeref{Platform::Sdl2ApplicationWindow,Configuration} later to a @relativeref{Platform::Sdl2Application,create()} function: @snippet Platform.cpp createcontext diff --git a/src/Magnum/Platform/AndroidApplication.h b/src/Magnum/Platform/AndroidApplication.h index 2c20b326e..dadab90a8 100644 --- a/src/Magnum/Platform/AndroidApplication.h +++ b/src/Magnum/Platform/AndroidApplication.h @@ -149,8 +149,8 @@ to simplify porting. @section Platform-AndroidApplication-resizing Responding to viewport events Unlike in desktop application implementations, where this is controlled via -@ref Sdl2Application::Configuration::WindowFlag::Resizable, for example, on -Android you have to describe this in the `AndroidManifest.xml` file, as by +@ref Sdl2ApplicationWindow::Configuration::WindowFlag::Resizable, for example, +on Android you have to describe this in the `AndroidManifest.xml` file, as by default the application gets killed and relaunched on screen orientation change. See the @ref platforms-android-apps-manifest-screen-resize "manifest file docs" for more information. diff --git a/src/Magnum/Platform/Screen.h b/src/Magnum/Platform/Screen.h index 6442389a3..ce65879ae 100644 --- a/src/Magnum/Platform/Screen.h +++ b/src/Magnum/Platform/Screen.h @@ -175,7 +175,7 @@ template class BasicScreen: * @brief Key event * * Defined only if the application has a - * @ref Sdl2Application::KeyEvent "KeyEvent". + * @relativeref{Sdl2ApplicationWindow,KeyEvent}. */ typedef typename BasicScreenedApplication::KeyEvent KeyEvent; #endif @@ -192,7 +192,7 @@ template class BasicScreen: * @m_since{2019,10} * * Defined only if the application has a - * @ref Sdl2Application::MouseScrollEvent "MouseScrollEvent". + * @relativeref{Sdl2ApplicationWindow,MouseScrollEvent}. */ typedef typename BasicScreenedApplication::MouseScrollEvent MouseScrollEvent; @@ -201,7 +201,7 @@ template class BasicScreen: * @m_since{2019,10} * * Defined only if the application has a - * @ref Sdl2Application::TextInputEvent "TextInputEvent". + * @relativeref{Sdl2ApplicationWindow,TextInputEvent}. */ typedef typename BasicScreenedApplication::TextInputEvent TextInputEvent; @@ -210,7 +210,7 @@ template class BasicScreen: * @m_since{2019,10} * * Defined only if the application has a - * @ref Sdl2Application::TextEditingEvent "TextEditingEvent". + * @relativeref{Sdl2ApplicationWindow,TextEditingEvent}. */ typedef typename BasicScreenedApplication::TextEditingEvent TextEditingEvent; #endif @@ -379,7 +379,7 @@ template class BasicScreen: * Called when @ref PropagatedEvent::Input is enabled and an key is * pressed. See @ref Sdl2Application::keyPressEvent() "*Application::keyPressEvent()" * for more information. Defined only if the application has a - * @ref Sdl2Application::KeyEvent "KeyEvent". + * @relativeref{Sdl2ApplicationWindow,KeyEvent}. */ virtual void keyPressEvent(KeyEvent& event); @@ -389,7 +389,7 @@ template class BasicScreen: * Called when @ref PropagatedEvent::Input is enabled and an key is * released. See @ref Sdl2Application::keyReleaseEvent() "*Application::keyReleaseEvent()" * for more information. Defined only if the application has a - * @ref Sdl2Application::KeyEvent "KeyEvent". + * @relativeref{Sdl2ApplicationWindow,KeyEvent}. */ virtual void keyReleaseEvent(KeyEvent& event); #endif @@ -437,7 +437,7 @@ template class BasicScreen: * Called when @ref PropagatedEvent::Input is enabled and mouse wheel * is rotated. See @ref Sdl2Application::mouseScrollEvent() "*Application::mouseScrollEvent()" * for more information. Defined only if the application has a - * @ref Sdl2Application::MouseScrollEvent "MouseScrollEvent". + * @relativeref{Sdl2ApplicationWindow,MouseScrollEvent}. */ virtual void mouseScrollEvent(MouseScrollEvent& event); #endif @@ -457,7 +457,7 @@ template class BasicScreen: * * Called when @ref PropagatedEvent::Input is enabled and text is being * input. Defined only if the application has a - * @ref Sdl2Application::TextInputEvent "TextInputEvent". + * @relativeref{Sdl2ApplicationWindow,TextInputEvent}. */ virtual void textInputEvent(TextInputEvent& event); @@ -467,7 +467,7 @@ template class BasicScreen: * * Called when @ref PropagatedEvent::Input and the text is being * edited. Defined only if the application has a - * @ref Sdl2Application::TextEditingEvent "TextEditingEvent". + * @relativeref{Sdl2ApplicationWindow,TextEditingEvent}. */ virtual void textEditingEvent(TextEditingEvent& event); #endif diff --git a/src/Magnum/Platform/Sdl2Application.cpp b/src/Magnum/Platform/Sdl2Application.cpp index 4b41f6821..219e41b91 100644 --- a/src/Magnum/Platform/Sdl2Application.cpp +++ b/src/Magnum/Platform/Sdl2Application.cpp @@ -42,6 +42,7 @@ #include #include #endif +#include #include #include @@ -78,142 +79,91 @@ 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 { +enum class Sdl2ApplicationWindow::WindowFlag: 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 + TextInputActive = 1 << 1, + Resizable = 1 << 2, #endif }; -Sdl2Application::Sdl2Application(const Arguments& arguments): Sdl2Application{arguments, Configuration{}} {} +Sdl2ApplicationWindow::Sdl2ApplicationWindow(Sdl2Application& application, NoCreateT): _application{application}, _windowFlags{WindowFlag::Redraw} {} -Sdl2Application::Sdl2Application(const Arguments& arguments, const Configuration& configuration): Sdl2Application{arguments, NoCreate} { - create(configuration); +#ifndef CORRADE_TARGET_EMSCRIPTEN +Sdl2ApplicationWindow::Sdl2ApplicationWindow(Sdl2Application& application, const Configuration& configuration): Sdl2ApplicationWindow{application, NoCreate} { + if(!tryCreateWindow(configuration)) std::exit(1); } -#ifdef MAGNUM_TARGET_GL -Sdl2Application::Sdl2Application(const Arguments& arguments, const Configuration& configuration, const GLConfiguration& glConfiguration): Sdl2Application{arguments, NoCreate} { - create(configuration, glConfiguration); -} +Sdl2ApplicationWindow::Sdl2ApplicationWindow(Sdl2Application& application): Sdl2ApplicationWindow{application, Configuration{}} {} #endif -Sdl2Application::Sdl2Application(const Arguments& arguments, NoCreateT): +Sdl2ApplicationWindow::~Sdl2ApplicationWindow() { #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); + /* If the window isn't created yet because tryCreateWindow(), nothing to + do. This also covers the case for the main application window, which + gets destroyed (and reset to null) from within ~Sdl2Application() + because it has to be done before SDL_Quit(), and this destructor is + called after that. There it also means that it's not needed to remove + itself from the window list, as the list is already gone at this + point. */ + if(_window) + destroyWindow(); #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 +bool Sdl2ApplicationWindow::tryCreateWindow(const Configuration& configuration) { + CORRADE_ASSERT(!_window, + "Platform::Sdl2ApplicationWindow::tryCreateWindow(): window already created", false); - if(SDL_Init(SDL_INIT_VIDEO) < 0) { - Error() << "Cannot initialize SDL:" << SDL_GetError(); - std::exit(1); + /* Save DPI scaling values from configuration for future use, scale window + based on those */ + _configurationDpiScalingPolicy = configuration.dpiScalingPolicy(); + _configurationDpiScaling = configuration.dpiScaling(); + const Vector2i scaledWindowSize = configuration.size()*dpiScaling(configuration); + + /* Create a 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|Uint32(configuration.windowFlags())|_application._configurationFlags))) + { + Error() << "Platform::Sdl2ApplicationWindow::tryCreateWindow(): cannot create a window:" << SDL_GetError(); + return false; } - /* 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")}; -} + /* Add itself to the window list */ + const std::size_t windowId = SDL_GetWindowID(_window); + if(windowId >= _application._windows.size()) { + for(Sdl2ApplicationWindow*& i: arrayAppend(_application._windows, NoInit, windowId - _application._windows.size() + 1)) + i = nullptr; + } + CORRADE_INTERNAL_ASSERT(!_application._windows[windowId]); + _application._windows[windowId] = this; -void Sdl2Application::create() { - create(Configuration{}); + return true; } -void Sdl2Application::create(const Configuration& configuration) { - if(!tryCreate(configuration)) std::exit(1); -} +#ifndef CORRADE_TARGET_EMSCRIPTEN +void Sdl2ApplicationWindow::destroyWindow() { + /* To prevent accidental double destructions and such, this function should + only be called if a window is actually created */ + CORRADE_INTERNAL_ASSERT(_window); -#ifdef MAGNUM_TARGET_GL -void Sdl2Application::create(const Configuration& configuration, const GLConfiguration& glConfiguration) { - if(!tryCreate(configuration, glConfiguration)) std::exit(1); + /* Remove itself from the window list */ + const std::size_t id = SDL_GetWindowID(_window); + CORRADE_INTERNAL_ASSERT(id < _application._windows.size()); + _application._windows[id] = nullptr; + + SDL_DestroyWindow(_window); } #endif -Vector2 Sdl2Application::dpiScaling(const Configuration& configuration) { +Vector2 Sdl2ApplicationWindow::dpiScaling(const Configuration& configuration) { /* Print a helpful warning in case some extra steps are needed for HiDPI support */ #ifdef CORRADE_TARGET_APPLE @@ -229,18 +179,18 @@ Vector2 Sdl2Application::dpiScaling(const Configuration& configuration) { return dpiScalingInternal(configuration.dpiScalingPolicy(), configuration.dpiScaling()); } -Vector2 Sdl2Application::dpiScalingInternal(const Implementation::Sdl2DpiScalingPolicy configurationDpiScalingPolicy, const Vector2& configurationDpiScaling) const { - std::ostream* verbose = _verboseLog ? Debug::output() : nullptr; +Vector2 Sdl2ApplicationWindow::dpiScalingInternal(const Implementation::Sdl2DpiScalingPolicy configurationDpiScalingPolicy, const Vector2& configurationDpiScaling) const { + std::ostream* verbose = _application._verboseLog ? Debug::output() : nullptr; /* 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; + if(!_application._commandLineDpiScaling.isZero()) { + Debug{verbose} << "Platform::Sdl2Application: user-defined DPI scaling" << _application._commandLineDpiScaling; + return _application._commandLineDpiScaling; + } else if(_application._commandLineDpiScalingPolicy != Implementation::Sdl2DpiScalingPolicy::Default) { + dpiScalingPolicy = _application._commandLineDpiScalingPolicy; } else if(!configurationDpiScaling.isZero()) { Debug{verbose} << "Platform::Sdl2Application: app-defined DPI scaling" << configurationDpiScaling; return configurationDpiScaling; @@ -346,7 +296,7 @@ Vector2 Sdl2Application::dpiScalingInternal(const Implementation::Sdl2DpiScaling #endif } -void Sdl2Application::setWindowTitle(const Containers::StringView title) { +void Sdl2ApplicationWindow::setWindowTitle(const Containers::StringView title) { #ifndef CORRADE_TARGET_EMSCRIPTEN SDL_SetWindowTitle(_window, Containers::String::nullTerminatedGlobalView(title).data()); @@ -359,7 +309,7 @@ void Sdl2Application::setWindowTitle(const Containers::StringView title) { } #if !defined(CORRADE_TARGET_EMSCRIPTEN) && SDL_MAJOR_VERSION*1000 + SDL_MINOR_VERSION*100 + SDL_PATCHLEVEL >= 2005 -void Sdl2Application::setWindowIcon(const ImageView2D& image) { +void Sdl2ApplicationWindow::setWindowIcon(const ImageView2D& image) { Uint32 format; /** @todo handle sRGB differently? */ switch(image.format()) { case PixelFormat::RGB8Srgb: @@ -371,7 +321,7 @@ void Sdl2Application::setWindowIcon(const ImageView2D& image) { format = SDL_PIXELFORMAT_RGBA32; break; default: - CORRADE_ASSERT_UNREACHABLE("Platform::Sdl2Application::setWindowIcon(): unexpected format" << image.format(), ); + CORRADE_ASSERT_UNREACHABLE("Platform::Sdl2ApplicationWindow::setWindowIcon(): unexpected format" << image.format(), ); } /* Images are loaded with origin at bottom left, flip it to top left. SDL @@ -383,41 +333,320 @@ void Sdl2Application::setWindowIcon(const ImageView2D& image) { 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_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 + +Vector2i Sdl2ApplicationWindow::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 Sdl2ApplicationWindow::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 Sdl2ApplicationWindow::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 Sdl2ApplicationWindow::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 Sdl2ApplicationWindow::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 + +Vector2 Sdl2ApplicationWindow::dpiScaling() const { + return dpiScalingInternal(_application._configurationDpiScalingPolicy, _application._configurationDpiScaling); +} + +void Sdl2ApplicationWindow::redraw() { _windowFlags |= WindowFlag::Redraw; } + +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 Containers::StringView CursorMap[] { + "default"_s, + "text"_s, + "wait"_s, + "crosshair"_s, + "progress"_s, + "nwse-resize"_s, + "nesw-resize"_s, + "ew-resize"_s, + "ns-resize"_s, + "move"_s, + "not-allowed"_s, + "pointer"_s, + "none"_s + /* Hidden & locked not supported yet */ +}; +#endif + +} + +void Sdl2ApplicationWindow::setCursor(Cursor cursor) { + #ifndef CORRADE_TARGET_EMSCRIPTEN + CORRADE_ASSERT(_window, "Platform::Sdl2ApplicationWindow::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(_application._cursors) && Containers::arraySize(_application._cursors) == Containers::arraySize(CursorMap)); + + if(!_application._cursors[UnsignedInt(cursor)]) + _application._cursors[UnsignedInt(cursor)] = SDL_CreateSystemCursor(CursorMap[UnsignedInt(cursor)]); + + SDL_SetCursor(_application._cursors[UnsignedInt(cursor)]); + #else + CORRADE_ASSERT(_surface, "Platform::Sdl2Application::setCursor(): no window opened", ); + _cursor = cursor; + CORRADE_INTERNAL_ASSERT(UnsignedInt(cursor) < Containers::arraySize(CursorMap)); + magnumPlatformSetCursor(CursorMap[UnsignedInt(cursor)].data(), + CursorMap[UnsignedInt(cursor)].size()); + #endif +} + +Sdl2Application::Cursor Sdl2ApplicationWindow::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(_application._cursors); i++) + if(_application._cursors[i] == cursor) return Cursor(i); + + return Cursor::Arrow; + #else + return _cursor; + #endif +} + +void Sdl2ApplicationWindow::viewportEvent(ViewportEvent&) {} +void Sdl2ApplicationWindow::keyPressEvent(KeyEvent&) {} +void Sdl2ApplicationWindow::keyReleaseEvent(KeyEvent&) {} +void Sdl2ApplicationWindow::mousePressEvent(MouseEvent&) {} +void Sdl2ApplicationWindow::mouseReleaseEvent(MouseEvent&) {} +void Sdl2ApplicationWindow::mouseMoveEvent(MouseMoveEvent&) {} +void Sdl2ApplicationWindow::mouseScrollEvent(MouseScrollEvent&) {} +void Sdl2ApplicationWindow::multiGestureEvent(MultiGestureEvent&) {} +void Sdl2ApplicationWindow::textInputEvent(TextInputEvent&) {} +void Sdl2ApplicationWindow::textEditingEvent(TextEditingEvent&) {} + +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 { + VSyncEnabled = 1 << 0, + NoTickEvent = 1 << 1, + NoAnyEvent = 1 << 2, + Exit = 1 << 3, + #ifdef CORRADE_TARGET_APPLE + HiDpiWarningPrinted = 1 << 4 + #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): Sdl2ApplicationWindow{*this, NoCreate} + #ifndef CORRADE_TARGET_EMSCRIPTEN + , _minimalLoopPeriod{0} + #endif +{ + 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{}); +} - SDL_SetWindowIcon(_window, icon); - SDL_FreeSurface(icon); +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 bool Sdl2Application::tryCreate(const Configuration& configuration) { #ifdef MAGNUM_TARGET_GL - if(!(configuration.windowFlags() & Configuration::WindowFlag::Contextless)) + if(!(configuration.flags() & Configuration::Flag::Contextless)) return tryCreate(configuration, GLConfiguration{}); #endif - #ifndef CORRADE_TARGET_EMSCRIPTEN - /* Save DPI scaling values from configuration for future use, scale window - based on those */ - _configurationDpiScalingPolicy = configuration.dpiScalingPolicy(); - _configurationDpiScaling = configuration.dpiScaling(); - const Vector2i scaledWindowSize = configuration.size()*dpiScaling(configuration); + /* Save the application-global configuration flags to be used to create all + windows */ + _configurationFlags = Uint32(configuration.flags()); - /* 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(); + #ifndef CORRADE_TARGET_EMSCRIPTEN + /* Create the main window */ + if(!tryCreateWindow(configuration)) return false; - } + + /* Register the main window in the window list */ + CORRADE_INTERNAL_ASSERT(_windows.isEmpty()); + arrayAppend(_windows, this); /* Emscripten-specific initialization */ #else @@ -449,7 +678,7 @@ bool Sdl2Application::tryCreate(const Configuration& configuration) { based on those */ _configurationDpiScalingPolicy = configuration.dpiScalingPolicy(); _configurationDpiScaling = configuration.dpiScaling(); - const Vector2i scaledWindowSize = windowSize*dpiScaling(configuration); + const Vector2i scaledWindowSize = configuration.size()*dpiScaling(configuration); Uint32 flags = SDL_OPENGL|SDL_HWSURFACE|SDL_DOUBLEBUF; if(configuration.windowFlags() & Configuration::WindowFlag::Resizable) { @@ -470,11 +699,16 @@ bool Sdl2Application::tryCreate(const Configuration& configuration) { #ifdef MAGNUM_TARGET_GL bool Sdl2Application::tryCreate(const Configuration& configuration, const GLConfiguration& glConfiguration) { - CORRADE_ASSERT(!(configuration.windowFlags() & Configuration::WindowFlag::Contextless), - "Platform::Sdl2Application::tryCreate(): cannot pass Configuration::WindowFlag::Contextless when creating an OpenGL context", false); + CORRADE_ASSERT(!(configuration.flags() & Configuration::Flag::Contextless), + "Platform::Sdl2Application::tryCreate(): cannot pass Configuration::Flag::Contextless when creating an OpenGL context", false); CORRADE_ASSERT(_context->version() == GL::Version::None, "Platform::Sdl2Application::tryCreate(): context already created", false); + /* Save the application-global configuration flags to be used to create all + windows. Since we're creating a GL context, request the window to also + be OpenGL-enabled. */ + _configurationFlags = Uint32(configuration.flags()|Configuration::Flag::OpenGL); + /* Enable double buffering, set up buffer sizes */ SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_RED_SIZE, glConfiguration.colorBufferSize().r()); @@ -494,12 +728,6 @@ bool Sdl2Application::tryCreate(const Configuration& configuration, const GLConf #endif #ifndef CORRADE_TARGET_EMSCRIPTEN - /* Save DPI scaling values from configuration for future use, scale window - based on those */ - _configurationDpiScalingPolicy = configuration.dpiScalingPolicy(); - _configurationDpiScaling = configuration.dpiScaling(); - const Vector2i scaledWindowSize = configuration.size()*dpiScaling(configuration); - /* Request debug context if GpuValidation is enabled either via the configuration or via command-line */ GLConfiguration::Flags glFlags = glConfiguration.flags(); @@ -559,25 +787,18 @@ bool Sdl2Application::tryCreate(const Configuration& configuration, const GLConf #endif } - /* Create a window. Hide it by default so we don't have distracting window + /* Hide the main window by default so we don't have distracting window blinking in case the context creation fails due to unsupported configuration or if it gets destroyed for fallback context creation below. */ - 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(); + Sdl2ApplicationWindow::Configuration hiddenWindowConfiguration{configuration}; + hiddenWindowConfiguration.addWindowFlags(Sdl2ApplicationWindow::Configuration::WindowFlag::Hidden); + + /* Create a window */ + if(!tryCreateWindow(hiddenWindowConfiguration)) return false; - } - /* Create context */ + /* Create a context */ _glContext = SDL_GL_CreateContext(_window); #ifndef MAGNUM_TARGET_GLES @@ -618,7 +839,8 @@ bool Sdl2Application::tryCreate(const Configuration& configuration, const GLConf on Linux at least, but better stay on the safe side as this way worked correctly for 10+ years on all platforms and reusing an existing window might not. */ - SDL_DestroyWindow(_window); + destroyWindow(); + _window = nullptr; SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); @@ -631,14 +853,8 @@ bool Sdl2Application::tryCreate(const Configuration& configuration, const GLConf SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, int(UnsignedLong(glFlags & ~GLConfiguration::Flag::ForwardCompatible) & 0xffffffffu)); /* Create a new window using the refreshed GL attributes */ - 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())))) - { - Error() << "Platform::Sdl2Application::tryCreate(): cannot create window:" << SDL_GetError(); + if(!tryCreateWindow(hiddenWindowConfiguration)) return false; - } /* Create compatibility context */ _glContext = SDL_GL_CreateContext(_window); @@ -648,7 +864,7 @@ bool Sdl2Application::tryCreate(const Configuration& configuration, const GLConf /* Cannot create context (or fallback compatibility context on desktop) */ if(!_glContext) { Error() << "Platform::Sdl2Application::tryCreate(): cannot create context:" << SDL_GetError(); - SDL_DestroyWindow(_window); + destroyWindow(); _window = nullptr; return false; } @@ -694,7 +910,7 @@ bool Sdl2Application::tryCreate(const Configuration& configuration, const GLConf based on those */ _configurationDpiScalingPolicy = configuration.dpiScalingPolicy(); _configurationDpiScaling = configuration.dpiScaling(); - const Vector2i scaledWindowSize = windowSize*dpiScaling(); + const Vector2i scaledWindowSize = configuration.size()*dpiScaling(configuration); Uint32 flags = SDL_OPENGL|SDL_HWSURFACE|SDL_DOUBLEBUF; if(configuration.windowFlags() & Configuration::WindowFlag::Resizable) { @@ -714,7 +930,7 @@ bool Sdl2Application::tryCreate(const Configuration& configuration, const GLConf if(!_context->tryCreate(glConfiguration)) { #ifndef CORRADE_TARGET_EMSCRIPTEN SDL_GL_DeleteContext(_glContext); - SDL_DestroyWindow(_window); + destroyWindow(); _window = nullptr; #else SDL_FreeSurface(_surface); @@ -733,66 +949,13 @@ bool Sdl2Application::tryCreate(const Configuration& configuration, const GLConf } #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 - -Vector2 Sdl2Application::dpiScaling() const { - return dpiScalingInternal(_configurationDpiScalingPolicy, _configurationDpiScaling); -} - #ifdef CORRADE_TARGET_EMSCRIPTEN void Sdl2Application::setContainerCssClass(const Containers::StringView cssClass) { magnumPlatformSetContainerCssClass(cssClass.data(), cssClass.size()); } #endif -void Sdl2Application::swapBuffers() { +void Sdl2ApplicationWindow::swapBuffers() { #ifndef CORRADE_TARGET_EMSCRIPTEN SDL_GL_SwapWindow(_window); #else @@ -825,13 +988,21 @@ bool Sdl2Application::setSwapInterval(const Int interval) { return true; } -void Sdl2Application::redraw() { _flags |= Flag::Redraw; } - Sdl2Application::~Sdl2Application() { + #ifndef CORRADE_TARGET_EMSCRIPTEN + /* Destroy the main SDL window first. After that, there should be no + remaining registered windows. */ + if(_window) + destroyWindow(); + for(Sdl2ApplicationWindow* i: _windows) + CORRADE_ASSERT(!i, "Sdl2Application: destructed with windows still open", ); + #else /* 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. */ + all APIs. */ + if(_window) SDL_DestroyWindow(_window); + #endif #ifdef MAGNUM_TARGET_GL /* Destroy Magnum context first to avoid it potentially accessing the @@ -850,9 +1021,6 @@ Sdl2Application::~Sdl2Application() { SDL_FreeCursor(cursor); #endif - #ifndef CORRADE_TARGET_EMSCRIPTEN - if(_window) SDL_DestroyWindow(_window); - #endif SDL_Quit(); } @@ -877,6 +1045,28 @@ void Sdl2Application::exit(const int exitCode) { _exitCode = exitCode; } +#ifndef CORRADE_TARGET_EMSCRIPTEN +void Sdl2Application::makeContextCurrent(Sdl2ApplicationWindow& window) { + /* Only do it if it is not active already */ + if(_activeGlContextWindow != window._window) { + SDL_GL_MakeCurrent(window._window, _glContext); + _activeGlContextWindow = window._window; +#warning TODO + // Context::current().resetState(Context::State::WindowSpecific); + } +} +#endif + +template inline void Sdl2Application::callEventHandler(std::size_t windowId, void(Sdl2ApplicationWindow::*eventHandler)(Args...), Args&&... args) { + // if(!(windowId < _windows.size() && _windows[windowId])) { + // Debug() << "HUH" << windowId << _windows.size(); + // return; + // } + + CORRADE_INTERNAL_ASSERT(windowId < _windows.size() && _windows[windowId]); + (_windows[windowId]->*eventHandler)(std::forward(args)...); +} + bool Sdl2Application::mainLoopIteration() { /* If exit was requested directly in the constructor, exit immediately without calling anything else */ @@ -922,6 +1112,7 @@ bool Sdl2Application::mainLoopIteration() { while(SDL_PollEvent(&event)) { switch(event.type) { case SDL_WINDOWEVENT: + CORRADE_INTERNAL_ASSERT(event.window.windowID < _windows.size() && _windows[event.window.windowID]); switch(event.window.event) { /* Not using SDL_WINDOWEVENT_RESIZED, because that doesn't get fired when the window is resized programmatically @@ -943,15 +1134,18 @@ bool Sdl2Application::mainLoopIteration() { #endif dpiScaling()}; /** @todo handle also WM_DPICHANGED events when a window is moved between displays with different DPI */ - viewportEvent(e); - _flags |= Flag::Redraw; +#warning make context current here? probably?? + makeContextCurrent(*_windows[event.window.windowID]); + callEventHandler(event.window.windowID, &Sdl2ApplicationWindow::viewportEvent, e); + _windows[event.window.windowID]->_windowFlags |= WindowFlag::Redraw; #endif } break; +#warning SDL_WINDOWEVENT_CLOSE /* 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; + _windows[event.window.windowID]->_windowFlags |= WindowFlag::Redraw; if(!(_flags & Flag::NoAnyEvent)) anyEvent(event); break; default: @@ -961,7 +1155,9 @@ bool Sdl2Application::mainLoopIteration() { 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); + callEventHandler(event.key.windowID, + event.type == SDL_KEYDOWN ? &Sdl2ApplicationWindow::keyPressEvent : &Sdl2ApplicationWindow::keyReleaseEvent, + e); } break; case SDL_MOUSEBUTTONDOWN: @@ -971,34 +1167,37 @@ bool Sdl2Application::mainLoopIteration() { , event.button.clicks #endif }; - event.type == SDL_MOUSEBUTTONDOWN ? mousePressEvent(e) : mouseReleaseEvent(e); + callEventHandler(event.key.windowID, + event.type == SDL_MOUSEBUTTONDOWN ? &Sdl2ApplicationWindow::mousePressEvent : &Sdl2ApplicationWindow::mouseReleaseEvent, + e); } break; case SDL_MOUSEWHEEL: { MouseScrollEvent e{event, {Float(event.wheel.x), Float(event.wheel.y)}}; - mouseScrollEvent(e); + callEventHandler(event.wheel.windowID, &Sdl2ApplicationWindow::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); + callEventHandler(event.motion.windowID, &Sdl2ApplicationWindow::mouseMoveEvent, e); break; } case SDL_MULTIGESTURE: { MultiGestureEvent e{event, {event.mgesture.x, event.mgesture.y}, event.mgesture.dTheta, event.mgesture.dDist, event.mgesture.numFingers}; +#warning wtf, why no window ID?! multiGestureEvent(e); break; } case SDL_TEXTINPUT: { TextInputEvent e{event, event.text.text}; - textInputEvent(e); + callEventHandler(event.text.windowID, &Sdl2ApplicationWindow::textInputEvent, e); } break; case SDL_TEXTEDITING: { TextEditingEvent e{event, event.edit.text, event.edit.start, event.edit.length}; - textEditingEvent(e); + callEventHandler(event.edit.windowID, &Sdl2ApplicationWindow::textEditingEvent, e); } break; case SDL_QUIT: { @@ -1024,11 +1223,18 @@ bool Sdl2Application::mainLoopIteration() { /* Tick event */ if(!(_flags & Flag::NoTickEvent)) tickEvent(); - /* Draw event */ - if(_flags & Flag::Redraw) { - _flags &= ~Flag::Redraw; - drawEvent(); + /* Draw events */ + bool somethingDrawn = false; + for(std::size_t i = 0; i != _windows.size(); ++i) { + if(!_windows[i] || !(_windows[i]->_windowFlags & WindowFlag::Redraw)) continue; + + _windows[i]->_windowFlags &= ~WindowFlag::Redraw; + callEventHandler(i, + &Sdl2ApplicationWindow::drawEvent); + somethingDrawn = true; + } + if(somethingDrawn) { #ifndef CORRADE_TARGET_EMSCRIPTEN /* If VSync is not enabled, delay to prevent CPU hogging (if set) */ if(!(_flags & Flag::VSyncEnabled) && _minimalLoopPeriod) { @@ -1056,99 +1262,6 @@ bool Sdl2Application::mainLoopIteration() { 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 Containers::StringView CursorMap[] { - "default"_s, - "text"_s, - "wait"_s, - "crosshair"_s, - "progress"_s, - "nwse-resize"_s, - "nesw-resize"_s, - "ew-resize"_s, - "ns-resize"_s, - "move"_s, - "not-allowed"_s, - "pointer"_s, - "none"_s - /* 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)); - magnumPlatformSetCursor(CursorMap[UnsignedInt(cursor)].data(), - CursorMap[UnsignedInt(cursor)].size()); - #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 */ @@ -1205,34 +1318,7 @@ void Sdl2Application::anyEvent(SDL_Event&) { _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(): +Sdl2ApplicationWindow::Configuration::Configuration(): #if !defined(CORRADE_TARGET_EMSCRIPTEN) && !defined(CORRADE_TARGET_IOS) _title(Containers::String::nullTerminatedGlobalView("Magnum SDL2 Application"_s)), #endif @@ -1243,38 +1329,105 @@ Sdl2Application::Configuration::Configuration(): #endif _dpiScalingPolicy{DpiScalingPolicy::Default} {} -Sdl2Application::Configuration::~Configuration() = default; +Sdl2ApplicationWindow::Configuration::~Configuration() = default; + +#ifdef MAGNUM_BUILD_DEPRECATED +namespace { + CORRADE_IGNORE_DEPRECATED_PUSH + constexpr Sdl2ApplicationWindow::Configuration::WindowFlags DeprecatedWindowFlags = Sdl2ApplicationWindow::Configuration::WindowFlag::Contextless|Sdl2ApplicationWindow::Configuration::WindowFlag::OpenGL|Sdl2ApplicationWindow::Configuration::WindowFlag::Vulkan; + CORRADE_IGNORE_DEPRECATED_POP +} +#endif + +Sdl2Application::Configuration& Sdl2Application::Configuration::setWindowFlags(WindowFlags flags) { + /* If deprecated flags are present, reset these bits in the application + flags instead and remove them from the to-be-set window flags */ + #ifdef MAGNUM_BUILD_DEPRECATED + if(const WindowFlags deprecatedFlags = flags & DeprecatedWindowFlags) { + _flags &= ~Flags(Uint32(DeprecatedWindowFlags)); + _flags |= Flags(Uint32(deprecatedFlags)); + flags &= ~deprecatedFlags; + } + #endif + + Sdl2ApplicationWindow::Configuration::setWindowFlags(flags); + return *this; +} + +Sdl2Application::Configuration& Sdl2Application::Configuration::addWindowFlags(WindowFlags flags) { + /* If deprecated flags are present, add these bits in the application + flags instead and remove them from the to-be-added window flags */ + #ifdef MAGNUM_BUILD_DEPRECATED + if(const WindowFlags deprecatedFlags = flags & DeprecatedWindowFlags) { + _flags |= Flags(Uint32(deprecatedFlags)); + flags &= ~deprecatedFlags; + } + #endif + + Sdl2ApplicationWindow::Configuration::addWindowFlags(flags); + return *this; +} + +Sdl2Application::Configuration& Sdl2Application::Configuration::clearWindowFlags(WindowFlags flags) { + /* If deprecated flags are present, add these bits in the application + flags instead and remove them from the to-be-cleared window flags */ + #ifdef MAGNUM_BUILD_DEPRECATED + if(const WindowFlags deprecatedFlags = flags & DeprecatedWindowFlags) { + _flags &= ~Flags(Uint32(deprecatedFlags)); + flags &= ~deprecatedFlags; + } + #endif + + Sdl2ApplicationWindow::Configuration::clearWindowFlags(flags); + return *this; +} -Containers::StringView Sdl2Application::KeyEvent::keyName(const Key key) { +Containers::StringView Sdl2ApplicationWindow::KeyEvent::keyName(const Key key) { return SDL_GetKeyName(SDL_Keycode(key)); } -Containers::StringView Sdl2Application::KeyEvent::keyName() const { +Containers::StringView Sdl2ApplicationWindow::KeyEvent::keyName() const { return keyName(_key); } -Sdl2Application::InputEvent::Modifiers Sdl2Application::MouseEvent::modifiers() { +Sdl2ApplicationWindow::InputEvent::Modifiers Sdl2ApplicationWindow::MouseEvent::modifiers() { if(_modifiers) return *_modifiers; return *(_modifiers = fixedModifiers(Uint16(SDL_GetModState()))); } -Sdl2Application::InputEvent::Modifiers Sdl2Application::MouseMoveEvent::modifiers() { +Sdl2ApplicationWindow::InputEvent::Modifiers Sdl2ApplicationWindow::MouseMoveEvent::modifiers() { if(_modifiers) return *_modifiers; return *(_modifiers = fixedModifiers(Uint16(SDL_GetModState()))); } -Vector2i Sdl2Application::MouseScrollEvent::position() { +Vector2i Sdl2ApplicationWindow::MouseScrollEvent::position() { if(_position) return *_position; _position = Vector2i{}; SDL_GetMouseState(&_position->x(), &_position->y()); return *_position; } -Sdl2Application::InputEvent::Modifiers Sdl2Application::MouseScrollEvent::modifiers() { +Sdl2ApplicationWindow::InputEvent::Modifiers Sdl2ApplicationWindow::MouseScrollEvent::modifiers() { if(_modifiers) return *_modifiers; return *(_modifiers = fixedModifiers(Uint16(SDL_GetModState()))); } +#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 + template class BasicScreen; template class BasicScreenedApplication; diff --git a/src/Magnum/Platform/Sdl2Application.h b/src/Magnum/Platform/Sdl2Application.h index 9026dec4d..d083d442b 100644 --- a/src/Magnum/Platform/Sdl2Application.h +++ b/src/Magnum/Platform/Sdl2Application.h @@ -95,1003 +95,1123 @@ namespace Implementation { enum class Sdl2DpiScalingPolicy: UnsignedByte; } -/** @nosubgrouping -@brief SDL2 application +class Sdl2Application; -@m_keywords{Application} +/** +@brief SDL2 application window +@m_since_latest -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). +@ref TODOTODO document -@m_class{m-block m-success} +See @ref Sdl2Application for more information. +*/ +class Sdl2ApplicationWindow { + public: + class Configuration; + class ViewportEvent; + class InputEvent; + class KeyEvent; + class MouseEvent; + class MouseMoveEvent; + class MouseScrollEvent; + class MultiGestureEvent; + class TextInputEvent; + class TextEditingEvent; -@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. + /** + * @brief Constructor + * + * @ref TODOTODO document + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + explicit Sdl2ApplicationWindow(Sdl2Application& application, const Configuration& configuration = Configuration{}); + #else + /* Configuration is only forward-declared at this point */ + explicit Sdl2ApplicationWindow(Sdl2Application& application, const Configuration& configuration); + explicit Sdl2ApplicationWindow(Sdl2Application& application); + #endif -@section Platform-Sdl2Application-bootstrap Bootstrap application + /** @brief Copying is not allowed */ + Sdl2ApplicationWindow(const Sdl2ApplicationWindow&) = delete; -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: + /** @brief Moving is not allowed */ + Sdl2ApplicationWindow(Sdl2ApplicationWindow&&) = delete; -@code{.sh} -mkdir build && cd build -cmake .. -cmake --build . -./src/MyApplication # or ./src/Debug/MyApplication -@endcode + /** @brief Copying is not allowed */ + Sdl2ApplicationWindow& operator=(const Sdl2ApplicationWindow&) = delete; -See @ref cmake for more information. + /** @brief Moving is not allowed */ + Sdl2ApplicationWindow& operator=(Sdl2ApplicationWindow&&) = delete; -@section Platform-Sdl2Application-bootstrap-emscripten Bootstrap application for Emscripten + /* Compared to the top-level application which is never deleted through + a pointer, standalone windows most likely will, so the destructor is + virtual */ + virtual ~Sdl2ApplicationWindow(); -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. + /** @brief Application instance */ + Sdl2Application& application() { return _application; } + const Sdl2Application& application() const { return _application; } /**< @overload */ -@section Platform-Sdl2Application-bootstrap-ios Bootstrap application for iOS + #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 -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. + /** @{ @name Window handling */ -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. + /** + * @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; -@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 + #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); -You can then open the generated project file in Xcode and build/deploy it from -there. + /** + * @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); -@section Platform-Sdl2Application-bootstrap-winrt Bootstrap application for Windows RT + /** + * @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 -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. + #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 -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: + /** + * @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; -@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 + /** + * @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); -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. + /** + * @brief Set window title + * @m_since{2019,10} + * + * The @p title is expected to be encoded in UTF-8. + */ + void setWindowTitle(Containers::StringView title); -@section Platform-Sdl2Application-usage General usage + #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 -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: + /** + * @brief Swap buffers + * + * Paints currently rendered framebuffer on screen. + * @see @ref Sdl2Application::setSwapInterval() + */ + void swapBuffers(); -@code{.cmake} -find_package(Magnum REQUIRED Sdl2Application) + /** + * @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(); -# ... -target_link_libraries(your-app PRIVATE Magnum::Sdl2Application) -@endcode + 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); -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. + /** + * @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; -@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) -# 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) + /* Since 1.8.17, the original short-hand group closing doesn't work + anymore. FFS. */ + /** + * @} + */ -set(MAGNUM_WITH_SDL2APPLICATION ON CACHE BOOL "" FORCE) -add_subdirectory(magnum EXCLUDE_FROM_ALL) -@endcode + /** @{ @name Keyboard handling */ - + /** + * @brief Key press event + * + * Called when an key is pressed. Default implementation does nothing. + */ + virtual void keyPressEvent(KeyEvent& event); -@m_class{m-note m-warning} + /** + * @brief Key release event + * + * Called when an key is released. Default implementation does nothing. + */ + virtual void keyReleaseEvent(KeyEvent& event); -@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. + /* Since 1.8.17, the original short-hand group closing doesn't work + anymore. FFS. */ + /** + * @} + */ -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. + /** @{ @name Mouse handling */ -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. + 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 */ -@code{.cpp} -class MyApplication: public Platform::Sdl2Application { - // implement required methods... -}; -MAGNUM_SDL2APPLICATION_MAIN(MyApplication) -@endcode + #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 + }; -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. + /** + * @brief Set cursor type + * @m_since{2020,06} + * + * Expects that a window is already created. Default is + * @ref Cursor::Arrow. + * @ref TODOTODO uhh clean up the docs to say "that the window is", not "a" + */ + void setCursor(Cursor cursor); -@subsection Platform-Sdl2Application-usage-power Power management + /** + * @brief Get current cursor type + * @m_since{2020,06} + */ + Cursor cursor(); -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. + #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 -@code{.sh} -SDL_VIDEO_ALLOW_SCREENSAVER=0 ./your-app -@endcode + private: + /** + * @brief Mouse press event + * + * Called when mouse button is pressed. Default implementation does + * nothing. + */ + virtual void mousePressEvent(MouseEvent& event); -@subsection Platform-Sdl2Application-usage-posix POSIX specifics + /** + * @brief Mouse release event + * + * Called when mouse button is released. Default implementation does + * nothing. + */ + virtual void mouseReleaseEvent(MouseEvent& event); -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: + /** + * @brief Mouse move event + * + * Called when mouse is moved. Default implementation does nothing. + */ + virtual void mouseMoveEvent(MouseMoveEvent& event); -@code{.sh} -SDL_NO_SIGNAL_HANDLERS=1 ./your-app -@endcode + /** + * @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); -See also the [SDL Wiki](https://wiki.libsdl.org/SDL_EventType#SDL_QUIT) for -details. + /* Since 1.8.17, the original short-hand group closing doesn't work + anymore. FFS. */ + /** + * @} + */ -@subsection Platform-Sdl2Application-usage-linux Linux specifics + /** @{ @name Touch gesture handling */ -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. + /** + * @brief Multi gesture event + * + * Called when the user performs a gesture using multiple fingers. + * Default implementation does nothing. + * @experimental + */ + virtual void multiGestureEvent(MultiGestureEvent& event); -@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 + /* Since 1.8.17, the original short-hand group closing doesn't work + anymore. FFS. */ + /** + * @} + */ -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. + /** @{ @name Text input handling */ -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: + /** + * @brief Text input event + * + * Called when text input is active and the text is being input. + * @see @ref Sdl2Application::isTextInputActive() + */ + virtual void textInputEvent(TextInputEvent& event); -- @ref Configuration::WindowFlag::Borderless hides the menu bar -- @ref Configuration::WindowFlag::Resizable makes the application respond to - device orientation changes + /** + * @brief Text editing event + * + * Called when text input is active and the text is being edited. + */ + virtual void textEditingEvent(TextEditingEvent& event); -@subsection Platform-Sdl2Application-usage-emscripten Emscripten specifics + /* Since 1.8.17, the original short-hand group closing doesn't work + anymore. FFS. */ + /** + * @} + */ -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. + private: + friend Sdl2Application; -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. + enum class WindowFlag: UnsignedByte; + typedef Containers::EnumSet WindowFlags; + CORRADE_ENUMSET_FRIEND_OPERATORS(WindowFlags) -@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. + /* Used by Sdl2Application(NoCreateT) */ + explicit Sdl2ApplicationWindow(Sdl2Application& application, NoCreateT); -@subsection Platform-Sdl2Application-usage-gles OpenGL ES specifics + Vector2 dpiScalingInternal(Implementation::Sdl2DpiScalingPolicy configurationDpiScalingPolicy, const Vector2& configurationDpiScaling) const; + #ifndef CORRADE_TARGET_EMSCRIPTEN + bool tryCreateWindow(const Configuration& configuration); + void destroyWindow(); + #endif -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. + Sdl2Application& _application; -@section Platform-Sdl2Application-dpi DPI awareness + /* These are saved from configuration to be reused in dpiScaling() and + viewportEvent() later */ + Implementation::Sdl2DpiScalingPolicy _configurationDpiScalingPolicy{}; + Vector2 _configurationDpiScaling; -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). + #ifndef CORRADE_TARGET_EMSCRIPTEN + SDL_Window* _window{}; + Vector2i _viewportSize; + #else + SDL_Surface* _surface{}; + Vector2i _lastKnownCanvasSize; + #endif -- 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. + WindowFlags _windowFlags; +}; -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. +/** @nosubgrouping +@brief SDL2 application -The default is depending on the platform: +@m_keywords{Application} -- 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. +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). -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: +@m_class{m-block m-success} -@snippet Platform.cpp Sdl2Application-dpi-scaling +@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. -If your application is saving and restoring window size, it's advisable to take -@ref dpiScaling() into account: +@section Platform-Sdl2Application-bootstrap Bootstrap application -- 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} {} +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: - int& argc; /**< @brief Argument count */ - char** argv; /**< @brief Argument values */ - }; +@code{.sh} +mkdir build && cd build +cmake .. +cmake --build . +./src/MyApplication # or ./src/Debug/MyApplication +@endcode - 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; +See @ref cmake for more information. - #ifdef MAGNUM_TARGET_GL - /** - * @brief Construct with an 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 +@section Platform-Sdl2Application-bootstrap-emscripten Bootstrap application for Emscripten - /** - * @brief Construct without explicit GPU context 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. - */ - #ifdef DOXYGEN_GENERATING_OUTPUT - explicit Sdl2Application(const Arguments& arguments, const Configuration& configuration = Configuration{}); - #else - /* Configuration is only forward-declared at this point */ - explicit Sdl2Application(const Arguments& arguments, const Configuration& configuration); - explicit Sdl2Application(const Arguments& arguments); - #endif +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. - /** - * @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); +@section Platform-Sdl2Application-bootstrap-ios Bootstrap application for iOS - /** @brief Copying is not allowed */ - Sdl2Application(const Sdl2Application&) = delete; +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. - /** @brief Moving is not allowed */ - Sdl2Application(Sdl2Application&&) = delete; +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. - /** @brief Copying is not allowed */ - Sdl2Application& operator=(const Sdl2Application&) = delete; +@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 - /** @brief Moving is not allowed */ - Sdl2Application& operator=(Sdl2Application&&) = delete; +You can then open the generated project file in Xcode and build/deploy it from +there. - /** - * @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(); +@section Platform-Sdl2Application-bootstrap-winrt Bootstrap application for Windows RT - /** - * @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(); +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. - /** - * @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 Platform.cpp exit-from-constructor - */ - void exit(int exitCode = 0); +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: - #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 +@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 - #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 +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. - protected: - /* Nobody will need to have (and delete) Sdl2Application*, thus this is - faster than public pure virtual destructor */ - ~Sdl2Application(); +@section Platform-Sdl2Application-usage General usage - #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 +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: - /** - * @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); +@code{.cmake} +find_package(Magnum REQUIRED Sdl2Application) - /** - * @brief Create a window with default configuration and OpenGL context - * - * Equivalent to calling @ref create(const Configuration&) with - * default-constructed @ref Configuration. - */ - void create(); +# ... +target_link_libraries(your-app PRIVATE Magnum::Sdl2Application) +@endcode - #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 +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. - /** - * @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); +@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) +# 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) - /** @{ @name Screen handling */ +set(MAGNUM_WITH_SDL2APPLICATION ON CACHE BOOL "" FORCE) +add_subdirectory(magnum EXCLUDE_FROM_ALL) +@endcode - 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); +@m_class{m-note m-warning} - /** - * @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); +@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. - /** - * @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 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. - #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 +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. - /** - * @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; +@code{.cpp} +class MyApplication: public Platform::Sdl2Application { + // implement required methods... +}; +MAGNUM_SDL2APPLICATION_MAIN(MyApplication) +@endcode - /** - * @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); +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. - /** - * @brief Set window title - * @m_since{2019,10} - * - * The @p title is expected to be encoded in UTF-8. - */ - void setWindowTitle(Containers::StringView title); +@subsection Platform-Sdl2Application-usage-power Power management - #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 +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. - #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 +@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 +@relativeref{Sdl2ApplicationWindow,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: + +- @relativeref{Sdl2ApplicationWindow,Configuration::WindowFlag::Borderless} + hides the menu bar +- @relativeref{Sdl2ApplicationWindow,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 @relativeref{Sdl2ApplicationWindow,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 @relativeref{Sdl2ApplicationWindow,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 + @relativeref{Sdl2ApplicationWindow,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 + @relativeref{Sdl2ApplicationWindow,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 @relativeref{Sdl2ApplicationWindow,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 + @relativeref{Sdl2ApplicationWindow,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 @relativeref{Sdl2ApplicationWindow,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 @relativeref{Sdl2ApplicationWindow,Configuration::DpiScalingPolicy::Virtual}, + taken from the `Xft.dpi` property. If the property is not available, it + falls back to @relativeref{Sdl2ApplicationWindow,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 Platform.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 Sdl2ApplicationWindow { + public: + /** @brief Application arguments */ + struct Arguments { + /** @brief Constructor */ + /*implicit*/ constexpr Arguments(int& argc, char** argv) noexcept: argc{argc}, argv{argv} {} - /** - * @brief Swap buffers - * - * Paints currently rendered framebuffer on screen. - * @see @ref setSwapInterval() - */ - void swapBuffers(); + int& argc; /**< @brief Argument count */ + char** argv; /**< @brief Argument values */ + }; - /** @brief Swap interval */ - Int swapInterval() const; + class Configuration; + #ifdef MAGNUM_TARGET_GL + class GLConfiguration; + #endif + class ExitEvent; + #ifdef MAGNUM_TARGET_GL /** - * @brief Set swap interval + * @brief Construct with an OpenGL context + * @param arguments Application arguments + * @param configuration Application configuration + * @param glConfiguration OpenGL context configuration * - * 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 + * Creates application with default or user-specified configuration. + * See @relativeref{Sdl2ApplicationWindow,Configuration} for more + * information. The program exits if the context cannot be created, see + * @ref tryCreate() for an alternative. * - * 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() + * @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 setMinimalLoopPeriod(UnsignedInt milliseconds) { - _minimalLoopPeriod = milliseconds; - } + explicit Sdl2Application(const Arguments& arguments, const Configuration& configuration, const GLConfiguration& glConfiguration); #endif /** - * @brief Redraw immediately + * @brief Construct without explicit GPU context configuration * - * 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 + * If @relativeref{Sdl2ApplicationWindow,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. * - * 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. + * 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. * - * 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. + * See also @ref building-features for more information. */ - virtual void viewportEvent(ViewportEvent& event); + #ifdef DOXYGEN_GENERATING_OUTPUT + explicit Sdl2Application(const Arguments& arguments, const Configuration& configuration = Configuration{}); + #else + /* Configuration is only forward-declared at this point */ + explicit Sdl2Application(const Arguments& arguments, const Configuration& configuration); + explicit Sdl2Application(const Arguments& arguments); + #endif /** - * @brief Draw event + * @brief Construct without creating a window + * @param arguments Application arguments * - * 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(). + * Unlike above, the window is not created and must be created later + * with @ref create() or @ref tryCreate(). */ - virtual void drawEvent() = 0; + explicit Sdl2Application(const Arguments& arguments, NoCreateT); - /* Since 1.8.17, the original short-hand group closing doesn't work - anymore. FFS. */ - /** - * @} - */ + /** @brief Copying is not allowed */ + Sdl2Application(const Sdl2Application&) = delete; - /** @{ @name Keyboard handling */ + /** @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 Key press event + * @brief Execute application main loop + * @return Value for returning from @cpp main() @ce * - * Called when an key is pressed. Default implementation does nothing. + * Calls @ref mainLoopIteration() in a loop until @ref exit() is + * called. See @ref MAGNUM_SDL2APPLICATION_MAIN() for usage + * information. */ - virtual void keyPressEvent(KeyEvent& event); + int exec(); /** - * @brief Key release event + * @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 when an key is released. Default implementation does nothing. + * 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(). */ - virtual void keyReleaseEvent(KeyEvent& event); + bool mainLoopIteration(); - /* Since 1.8.17, the original short-hand group closing doesn't work - anymore. FFS. */ /** - * @} + * @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 Platform.cpp exit-from-constructor */ + void exit(int exitCode = 0); - /** @{ @name Mouse handling */ - - public: + #if defined(MAGNUM_TARGET_GL) && !defined(CORRADE_TARGET_EMSCRIPTEN) /** - * @brief Cursor type - * @m_since{2020,06} + * @brief Underlying OpenGL context + * @m_since{2019,10} * - * @see @ref setCursor() + * 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". */ - 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 */ + SDL_GLContext glContext() { return _glContext; } + #endif - #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 - }; + protected: + /* Nobody will need to have (and delete) Sdl2Application*, thus this is + faster than public pure virtual destructor */ + ~Sdl2Application(); + #ifdef MAGNUM_TARGET_GL /** - * @brief Set cursor type - * @m_since{2020,06} + * @brief Create a window with given configuration for OpenGL context + * @param configuration Application configuration + * @param glConfiguration OpenGL context configuration * - * 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} + * 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. */ - Cursor cursor(); + void create(const Configuration& configuration, const GLConfiguration& glConfiguration); + #endif - #ifndef CORRADE_TARGET_EMSCRIPTEN /** - * @brief Warp mouse cursor to given coordinates + * @brief Create a window with given configuration * - * @note Not available in @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". - */ - void warpCursor(const Vector2i& position) { - SDL_WarpMouseInWindow(_window, position.x(), position.y()); - } - #endif + * If @relativeref{Sdl2ApplicationWindow,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); - #ifdef MAGNUM_BUILD_DEPRECATED /** - * @brief Whether mouse is locked + * @brief Create a window with default configuration and OpenGL context * - * @m_deprecated_since{2020,06} Use @ref cursor() together with - * @ref Cursor::HiddenLocked instead. + * Equivalent to calling @ref create(const Configuration&) with + * default-constructed @relativeref{Sdl2ApplicationWindow,Configuration}. */ - CORRADE_DEPRECATED("use cursor() together with Cursor::HiddenLocked instead") bool isMouseLocked() const { return SDL_GetRelativeMouseMode(); } + void create(); + #ifdef MAGNUM_TARGET_GL /** - * @brief Enable or disable mouse locking + * @brief Try to create context with given configuration for OpenGL context * - * @m_deprecated_since{2020,06} Use @ref setCursor() together with - * @ref Cursor::HiddenLocked instead. + * 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. */ - CORRADE_DEPRECATED("use setCursor() together with Cursor::HiddenLocked instead") void setMouseLocked(bool enabled); + bool tryCreate(const Configuration& configuration, const GLConfiguration& glConfiguration); #endif - private: /** - * @brief Mouse press event + * @brief Try to create context with given configuration * - * Called when mouse button is pressed. Default implementation does - * nothing. + * Unlike @ref create(const Configuration&) returns @cpp false @ce if + * the context cannot be created, @cpp true @ce otherwise. */ - virtual void mousePressEvent(MouseEvent& event); + bool tryCreate(const Configuration& configuration); + + /** @{ @name Screen handling */ + public: + #if defined(CORRADE_TARGET_EMSCRIPTEN) || defined(DOXYGEN_GENERATING_OUTPUT) /** - * @brief Mouse release event + * @brief Set container CSS class * - * Called when mouse button is released. Default implementation does - * nothing. + * 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. */ - virtual void mouseReleaseEvent(MouseEvent& event); + void setContainerCssClass(Containers::StringView cssClass); + #endif + + /** @brief Swap interval */ + Int swapInterval() const; /** - * @brief Mouse move event + * @brief Set swap interval * - * Called when mouse is moved. Default implementation does nothing. + * 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() */ - virtual void mouseMoveEvent(MouseMoveEvent& event); + bool setSwapInterval(Int interval); + #ifndef CORRADE_TARGET_EMSCRIPTEN /** - * @brief Mouse scroll event + * @brief Set minimal loop period * - * Called when a scrolling device is used (mouse wheel or scrolling - * area on a touchpad). Default implementation does nothing. + * 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() */ - virtual void mouseScrollEvent(MouseScrollEvent& event); + void setMinimalLoopPeriod(UnsignedInt milliseconds) { + _minimalLoopPeriod = milliseconds; + } + #endif /* Since 1.8.17, the original short-hand group closing doesn't work anymore. FFS. */ @@ -1099,16 +1219,25 @@ class Sdl2Application { * @} */ - /** @{ @name Touch gesture handling */ + /** @{ @name Mouse handling */ + #ifdef MAGNUM_BUILD_DEPRECATED /** - * @brief Multi gesture event + * @brief Whether mouse is locked * - * Called when the user performs a gesture using multiple fingers. - * Default implementation does nothing. - * @experimental + * @m_deprecated_since{2020,06} Use @ref cursor() together with + * @ref Cursor::HiddenLocked instead. */ - virtual void multiGestureEvent(MultiGestureEvent& event); + 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 /* Since 1.8.17, the original short-hand group closing doesn't work anymore. FFS. */ @@ -1117,7 +1246,7 @@ class Sdl2Application { */ /** @{ @name Text input handling */ - public: + /** * @brief Whether text input is active * @@ -1158,22 +1287,6 @@ class Sdl2Application { */ 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. */ /** @@ -1182,6 +1295,7 @@ class Sdl2Application { /** @{ @name Special events */ + private: /** * @brief Exit event * @@ -1233,11 +1347,16 @@ class Sdl2Application { */ private: + friend Sdl2ApplicationWindow; + enum class Flag: UnsignedByte; typedef Containers::EnumSet Flags; CORRADE_ENUMSET_FRIEND_OPERATORS(Flags) - Vector2 dpiScalingInternal(Implementation::Sdl2DpiScalingPolicy configurationDpiScalingPolicy, const Vector2& configurationDpiScaling) const; + #ifndef CORRADE_TARGET_EMSCRIPTEN + void makeContextCurrent(Sdl2ApplicationWindow& window); + #endif + template void callEventHandler(std::size_t windowId, void(Sdl2ApplicationWindow::*eventHandler)(Args...), Args&&...); #ifndef CORRADE_TARGET_EMSCRIPTEN SDL_Cursor* _cursors[12]{}; @@ -1245,29 +1364,33 @@ class Sdl2Application { Cursor _cursor; #endif - /* These are saved from command-line arguments, and from configuration - to be reused in dpiScaling() and viewportEvent() later */ + /* These are saved from command-line arguments to be reused in + dpiScaling() and viewportEvent() later */ bool _verboseLog{}; - Implementation::Sdl2DpiScalingPolicy _commandLineDpiScalingPolicy{}, _configurationDpiScalingPolicy{}; - Vector2 _commandLineDpiScaling, _configurationDpiScaling; + Implementation::Sdl2DpiScalingPolicy _commandLineDpiScalingPolicy{}; + Vector2 _commandLineDpiScaling; + /* Configuration::Flags, propagated to all created windows. Can't use a + concrete type as Configuration is only a forward declaration at this + point. */ + Uint32 _configurationFlags; #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{}; + SDL_Window* _activeGlContextWindow{}; #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 + #ifndef CORRADE_TARGET_EMSCRIPTEN + Containers::Array _windows; + #endif Flags _flags; @@ -1284,7 +1407,7 @@ The created window is always with a double-buffered OpenGL context. @ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features for more information. -@see @ref Sdl2Application(), @ref create(), @ref tryCreate() +@see @ref Sdl2Application(const Arguments&, const Configuration&, const GLConfiguration&), @ref create(), @ref tryCreate() */ class Sdl2Application::GLConfiguration: public GL::Context::Configuration { public: @@ -1584,12 +1707,16 @@ namespace Implementation { } /** -@brief Configuration - -@see @ref Sdl2Application(), @ref GLConfiguration, @ref create(), - @ref tryCreate() +@brief Window configuration + +Inherited by @ref Sdl2Application::Configuration which adds global options for +all windows. +@see @ref Sdl2ApplicationWindow(), + @ref Sdl2Application::Sdl2Application(const Arguments&, const Configuration&, const GLConfiguration&), + @ref Sdl2Application::GLConfiguration, @ref Sdl2Application::create(), + @ref Sdl2Application::tryCreate() */ -class Sdl2Application::Configuration { +class Sdl2ApplicationWindow::Configuration { public: /** * @brief Window flag @@ -1720,38 +1847,34 @@ class Sdl2Application::Configuration { #endif #endif + #ifdef MAGNUM_BUILD_DEPRECATED /** - * 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. Can't be used with - * @ref Sdl2Application(const Arguments&, const Configuration&, const GLConfiguration&), - * @ref create(const Configuration&, const GLConfiguration&) or - * @ref tryCreate(const Configuration&, const GLConfiguration&). + * Do not create any GPU context. + * @m_deprecated_since_latest Use the application-global + * @ref Sdl2Application::Configuration::Flag::Contextless + * instead. */ - Contextless = 1u << 31, /* Hope this won't ever conflict with anything */ + Contextless CORRADE_DEPRECATED_ENUM("use Sdl2Application::Configuration::Flag::Contextless instead") = 1u << 31, /** - * 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&, const Configuration&, const GLConfiguration&), - * @ref create(const Configuration&, const GLConfiguration&) or - * @ref tryCreate(const Configuration&, const GLConfiguration&). - * @m_since{2019,10} + * Request a window for use with OpenGL. + * @m_deprecated_since_latest Use the application-global + * @ref Sdl2Application::Configuration::Flag::OpenGL + * instead. */ - OpenGL = SDL_WINDOW_OPENGL, + OpenGL CORRADE_DEPRECATED_ENUM("use Sdl2Application::Configuration::Flag::OpenGL instead") = 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. + * Request a window for use with Vulkan. + * @m_deprecated_since_latest Use the application-global + * @ref Sdl2Application::Configuration::Flag::Vulkan + * instead. * @note Available since SDL 2.0.6, not available on * @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". - * @m_since{2019,10} */ - Vulkan = SDL_WINDOW_VULKAN + Vulkan CORRADE_DEPRECATED_ENUM("use Sdl2Application::Configuration::Flag::Vulkan instead") = SDL_WINDOW_VULKAN + #endif #endif }; @@ -1971,6 +2094,142 @@ class Sdl2Application::Configuration { Vector2 _dpiScaling; }; +CORRADE_ENUMSET_OPERATORS(Sdl2ApplicationWindow::Configuration::WindowFlags) + +/** +@brief Application configuration + +Inherits @ref Sdl2ApplicationWindow::Configuration. +@see @ref Sdl2Application(), @ref create(), @ref tryCreate() +*/ +class Sdl2Application::Configuration: public Sdl2ApplicationWindow::Configuration { + public: + /** + * @brief Application flag + * @m_since_latest + * + * @see @ref Flags, @ref setFlags() + */ + enum class Flag: Uint32 { + /** + * 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. Can't be used with + * @ref Sdl2Application(const Arguments&, const Configuration&, const GLConfiguration&), + * @ref create(const Configuration&, const GLConfiguration&) or + * @ref tryCreate(const Configuration&, const GLConfiguration&). + */ + Contextless = 1u << 31, /* Hope this won't ever conflict with anything */ + + /** + * Request a window for use with OpenGL. Useful in combination with + * @ref Flag::Contextless, otherwise enabled implicitly when + * creating an OpenGL context using + * @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 Flag::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 Application flags + * @m_since_latest + * + * @see @ref setFlags() + */ + typedef Containers::EnumSet Flags; + + explicit Configuration() = default; + + /** + * @brief Application flags + * @m_since_latest + */ + Flags flags() const { return _flags; } + + /** + * @brief Set application flags + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Default are none. To avoid clearing default flags by accident, + * prefer to use @ref addFlags() and @ref clearFlags() instead. + */ + Configuration& setFlags(Flags flags) { + _flags = flags; + return *this; + } + + /** + * @brief Add application flags + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Unlike @ref setFlags(), ORs the flags with existing instead of + * replacing them. Useful for preserving the defaults. + * @see @ref clearFlags() + */ + Configuration& addFlags(Flags flags) { + _flags |= flags; + return *this; + } + + /** + * @brief Clear application flags + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Unlike @ref setFlags(), ANDs the inverse of @p flags with existing + * instead of replacing them. Useful for removing default flags. + * @see @ref addFlags() + */ + Configuration& clearFlags(Flags flags) { + _flags &= ~flags; + return *this; + } + + /* Overloads to remove a WTF factor from method chaining order */ + #ifndef DOXYGEN_GENERATING_OUTPUT + Configuration& setTitle(Containers::StringView title) { + Sdl2ApplicationWindow::Configuration::setTitle(title); + return *this; + } + Configuration& setSize(const Vector2i& size, DpiScalingPolicy dpiScalingPolicy = DpiScalingPolicy::Default) { + Sdl2ApplicationWindow::Configuration::setSize(size, dpiScalingPolicy); + return *this; + } + Configuration& setSize(const Vector2i& size, const Vector2& dpiScaling) { + Sdl2ApplicationWindow::Configuration::setSize(size, dpiScaling); + return *this; + } + /* On deprecated builds these propagate the appropriate context flags + to setFlags() / addFlags() / clearFlags() */ + Configuration& setWindowFlags(WindowFlags flags); + Configuration& addWindowFlags(WindowFlags flags); + Configuration& clearWindowFlags(WindowFlags flags); + #endif + + private: + Flags _flags; +}; + +CORRADE_ENUMSET_OPERATORS(Sdl2Application::Configuration::Flags) + /** @brief Exit event @@ -2024,7 +2283,7 @@ class Sdl2Application::ExitEvent { @see @ref viewportEvent() */ -class Sdl2Application::ViewportEvent { +class Sdl2ApplicationWindow::ViewportEvent { public: /** @brief Copying is not allowed */ ViewportEvent(const ViewportEvent&) = delete; @@ -2125,7 +2384,7 @@ class Sdl2Application::ViewportEvent { @ref keyReleaseEvent(), @ref mousePressEvent(), @ref mouseReleaseEvent(), @ref mouseMoveEvent() */ -class Sdl2Application::InputEvent { +class Sdl2ApplicationWindow::InputEvent { public: /** * @brief Modifier @@ -2235,7 +2494,7 @@ class Sdl2Application::InputEvent { @see @ref keyPressEvent(), @ref keyReleaseEvent() */ -class Sdl2Application::KeyEvent: public Sdl2Application::InputEvent { +class Sdl2ApplicationWindow::KeyEvent: public InputEvent { public: /** * @brief Key @@ -2556,7 +2815,7 @@ class Sdl2Application::KeyEvent: public Sdl2Application::InputEvent { @see @ref MouseMoveEvent, @ref MouseScrollEvent, @ref mousePressEvent(), @ref mouseReleaseEvent() */ -class Sdl2Application::MouseEvent: public Sdl2Application::InputEvent { +class Sdl2ApplicationWindow::MouseEvent: public InputEvent { public: /** * @brief Mouse button @@ -2623,7 +2882,7 @@ class Sdl2Application::MouseEvent: public Sdl2Application::InputEvent { @see @ref MouseEvent, @ref MouseScrollEvent, @ref mouseMoveEvent() */ -class Sdl2Application::MouseMoveEvent: public Sdl2Application::InputEvent { +class Sdl2ApplicationWindow::MouseMoveEvent: public InputEvent { public: /** * @brief Mouse button @@ -2684,7 +2943,7 @@ class Sdl2Application::MouseMoveEvent: public Sdl2Application::InputEvent { @see @ref MouseEvent, @ref MouseMoveEvent, @ref mouseScrollEvent() */ -class Sdl2Application::MouseScrollEvent: public Sdl2Application::InputEvent { +class Sdl2ApplicationWindow::MouseScrollEvent: public InputEvent { public: /** @brief Scroll offset */ Vector2 offset() const { return _offset; } @@ -2719,7 +2978,7 @@ class Sdl2Application::MouseScrollEvent: public Sdl2Application::InputEvent { @experimental @see @ref multiGestureEvent() */ -class Sdl2Application::MultiGestureEvent { +class Sdl2ApplicationWindow::MultiGestureEvent { public: /** @brief Copying is not allowed */ MultiGestureEvent(const MultiGestureEvent&) = delete; @@ -2796,7 +3055,7 @@ class Sdl2Application::MultiGestureEvent { @see @ref TextEditingEvent, @ref textInputEvent() */ -class Sdl2Application::TextInputEvent { +class Sdl2ApplicationWindow::TextInputEvent { public: /** @brief Copying is not allowed */ TextInputEvent(const TextInputEvent&) = delete; @@ -2854,7 +3113,7 @@ class Sdl2Application::TextInputEvent { @see @ref textEditingEvent() */ -class Sdl2Application::TextEditingEvent { +class Sdl2ApplicationWindow::TextEditingEvent { public: /** @brief Copying is not allowed */ TextEditingEvent(const TextEditingEvent&) = delete; @@ -2962,6 +3221,7 @@ When no other application header is included this macro is also aliased to #ifndef DOXYGEN_GENERATING_OUTPUT #ifndef MAGNUM_APPLICATION_MAIN typedef Sdl2Application Application; +typedef Sdl2ApplicationWindow ApplicationWindow; typedef BasicScreen Screen; typedef BasicScreenedApplication ScreenedApplication; #define MAGNUM_APPLICATION_MAIN(className) MAGNUM_SDL2APPLICATION_MAIN(className) @@ -2970,9 +3230,8 @@ typedef BasicScreenedApplication ScreenedApplication; #endif #endif -CORRADE_ENUMSET_OPERATORS(Sdl2Application::Configuration::WindowFlags) -CORRADE_ENUMSET_OPERATORS(Sdl2Application::InputEvent::Modifiers) -CORRADE_ENUMSET_OPERATORS(Sdl2Application::MouseMoveEvent::Buttons) +CORRADE_ENUMSET_OPERATORS(Sdl2ApplicationWindow::InputEvent::Modifiers) +CORRADE_ENUMSET_OPERATORS(Sdl2ApplicationWindow::MouseMoveEvent::Buttons) }} diff --git a/src/Magnum/Platform/Test/Sdl2ApplicationTest.cpp b/src/Magnum/Platform/Test/Sdl2ApplicationTest.cpp index d470211c6..6bde84903 100644 --- a/src/Magnum/Platform/Test/Sdl2ApplicationTest.cpp +++ b/src/Magnum/Platform/Test/Sdl2ApplicationTest.cpp @@ -23,9 +23,11 @@ DEALINGS IN THE SOFTWARE. */ +#include #include #include #include +#include #include #include "Magnum/ImageView.h" @@ -174,6 +176,106 @@ Debug& operator<<(Debug& debug, const Application::KeyEvent::Key value) { using namespace Containers::Literals; +#ifndef CORRADE_TARGET_EMSCRIPTEN +struct Sdl2ApplicationTestWindow: Platform::ApplicationWindow { + explicit Sdl2ApplicationTestWindow(Platform::Application& application, std::size_t id): Platform::ApplicationWindow{application, Configuration{} + .setTitle(Utility::format("Window {}", id)) + .setSize({400, 300}) + .addWindowFlags(Configuration::WindowFlag::Resizable) + }, _id{id} {} + + void viewportEvent(ViewportEvent& event) override { + Debug{} << "window" << _id << "viewport event" << event.windowSize() + #ifdef MAGNUM_TARGET_GL + << event.framebufferSize() + #endif + << event.dpiScaling(); +#warning query the GL fb size here and compare + // GL::defaultFramebuffer.setViewport({{}, event.framebufferSize()}); + } + + void drawEvent() override { + Debug{} << "window" << _id << "draw event"; + #ifdef MAGNUM_TARGET_GL + GL::defaultFramebuffer.clear(GL::FramebufferClear::Color); + #endif + + swapBuffers(); + } + + /* For testing event coordinates */ + void mousePressEvent(MouseEvent& event) override { + Debug{} << "window" << _id << "mouse press event:" << event.position() << Int(event.button()); + } + + void mouseReleaseEvent(MouseEvent& event) override { + Debug{} << "window" << _id << "mouse release event:" << event.position() << Int(event.button()); + } + + void mouseMoveEvent(MouseMoveEvent& event) override { + Debug{} << "window" << _id << "mouse move event:" << event.position() << event.relativePosition() << Uint32(event.buttons()); + } + + void mouseScrollEvent(MouseScrollEvent& event) override { + Debug{} << "window" << _id << "mouse scroll event:" << event.offset() << event.position(); + } + + void keyPressEvent(KeyEvent& event) override { + Debug{} << "window" << _id << "key press event:" << SDL_Keycode(event.key()) << event.keyName(); + + if(event.key() == KeyEvent::Key::F1) { + Debug{} << "starting text input"; + application().startTextInput(); + } else if(event.key() == KeyEvent::Key::Esc) { + Debug{} << "stopping text input"; + application().stopTextInput(); + } else if(event.key() == KeyEvent::Key::T) { + Debug{} << "setting window title"; + setWindowTitle("This is a UTF-8 Window Titleā„¢ and it should have no exclamation mark!!"_s.exceptSuffix(2)); + } + #ifndef CORRADE_TARGET_EMSCRIPTEN + else if(event.key() == KeyEvent::Key::S) { + Debug{} << "setting window size, which should trigger a viewport event"; + setWindowSize(Vector2i{300, 200}); + } else if(event.key() == KeyEvent::Key::W) { + Debug{} << "setting max window size, which should trigger a viewport event"; + setMaxWindowSize(Vector2i{700, 500}); + } + #endif + else if(event.key() == KeyEvent::Key::H) { + Debug{} << "toggling hand cursor"; + setCursor(cursor() == Cursor::Arrow ? Cursor::Hand : Cursor::Arrow); + } + #ifndef CORRADE_TARGET_EMSCRIPTEN + else if(event.key() == KeyEvent::Key::L) { + Debug{} << "toggling locked mouse"; + setCursor(cursor() == Cursor::Arrow ? Cursor::HiddenLocked : Cursor::Arrow); + } + #else + else if(event.key() == KeyEvent::Key::F) { + Debug{} << "toggling fullscreen"; + setContainerCssClass((_fullscreen ^= true) ? "mn-fullsize" : ""); + } + #endif + else if(event.key() == KeyEvent::Key::X) { + Debug{} << "requesting an exit with code 5"; + exit(5); + } + } + + void keyReleaseEvent(KeyEvent& event) override { + Debug{} << "window" << _id << "key release event:" << SDL_Keycode(event.key()) << event.keyName(); + } + + void textInputEvent(TextInputEvent& event) override { + Debug{} << "window" << _id << "text input event:" << event.text(); + } + + private: + std::size_t _id; +}; +#endif + struct Sdl2ApplicationTest: Platform::Application { explicit Sdl2ApplicationTest(const Arguments& arguments); @@ -242,6 +344,10 @@ struct Sdl2ApplicationTest: Platform::Application { Debug{} << "setting max window size, which should trigger a viewport event"; setMaxWindowSize(Vector2i{700, 500}); } + else if(event.key() == KeyEvent::Key::O) { + Debug{} << "opening window" << _windows.size(); + arrayAppend(_windows, Containers::pointer(*this, _windows.size())); + } #endif else if(event.key() == KeyEvent::Key::H) { Debug{} << "toggling hand cursor"; @@ -303,10 +409,12 @@ struct Sdl2ApplicationTest: Platform::Application { if(event.type == SDL_WINDOWEVENT) d << event.window.event; } - #ifdef CORRADE_TARGET_EMSCRIPTEN private: + #ifndef CORRADE_TARGET_EMSCRIPTEN + Containers::Array> _windows; + #else bool _fullscreen = false; - #endif + #endif }; Sdl2ApplicationTest::Sdl2ApplicationTest(const Arguments& arguments): Platform::Application{arguments, NoCreate} { diff --git a/src/Magnum/SceneGraph/Camera.h b/src/Magnum/SceneGraph/Camera.h index 0aaf8785c..28eeb4ada 100644 --- a/src/Magnum/SceneGraph/Camera.h +++ b/src/Magnum/SceneGraph/Camera.h @@ -156,7 +156,7 @@ template class Camera: public AbstractFeature