From a902b8475d2e35217148dd273d25bdb32448f5b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 28 Apr 2023 20:12:07 +0200 Subject: [PATCH 1/3] [wip] Platform: create a Sdl2ApplicationWindow base class. Mostly just code motion and related documentation updates because Doxygen is such a crap that it can link to functions defined in a base class but not types! FFS. No actual multi-window support yet, that'll be in the next commits to avoid this noise obscuring them. TODO: Sdl2Window instead? TODO: unsure about certain APIs, whether they should be per-window or not - contextless / gl / vulkan? - mouse warp? - ...? --- doc/changelog-old.dox | 31 +- doc/changelog.dox | 53 +- doc/platform.dox | 6 +- src/Magnum/Platform/AndroidApplication.h | 4 +- src/Magnum/Platform/Screen.h | 18 +- src/Magnum/Platform/Sdl2Application.cpp | 779 +++++++++--------- src/Magnum/Platform/Sdl2Application.h | 973 ++++++++++++----------- src/Magnum/SceneGraph/Camera.h | 2 +- 8 files changed, 982 insertions(+), 884 deletions(-) diff --git a/doc/changelog-old.dox b/doc/changelog-old.dox index 051cb66a2..294166150 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 5654acdf3..d93c97180 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -732,13 +732,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 @@ -1103,9 +1103,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. @@ -1969,12 +1969,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 @@ -1992,12 +1992,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 @@ -2299,7 +2296,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 @@ -2330,7 +2327,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 @@ -2832,10 +2829,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 27636dd1a..78a77486d 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 MagnumPlatform.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 64bb91c54..c9eec6318 100644 --- a/src/Magnum/Platform/Sdl2Application.cpp +++ b/src/Magnum/Platform/Sdl2Application.cpp @@ -78,142 +78,25 @@ 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{}} {} - -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 +Sdl2ApplicationWindow::Sdl2ApplicationWindow(Sdl2Application& application, NoCreateT): _application{application}, _windowFlags{WindowFlag::Redraw} {} -Sdl2Application::Sdl2Application(const Arguments& arguments, NoCreateT): +Sdl2ApplicationWindow::~Sdl2ApplicationWindow() { + /* SDL_DestroyWindow(_window) crashes on windows when _window is nullptr + (it doesn't seem to crash on Linux) */ #ifndef CORRADE_TARGET_EMSCRIPTEN - _minimalLoopPeriod{0}, - #endif - _flags{Flag::Redraw} -{ - Utility::Arguments args{Implementation::windowScalingArguments()}; - #ifdef MAGNUM_TARGET_GL - _context.emplace(NoCreate, args, arguments.argc, arguments.argv); - #else - /** @todo this is duplicated here, in GlfwApplication and in - EmscriptenApplication, figure out a nice non-duplicated way to handle - this */ - args.addOption("log", "default").setHelp("log", "console logging", "default|quiet|verbose") - .setFromEnvironment("log") - .parse(arguments.argc, arguments.argv); - #endif - - /* Available since 2.0.4, disables interception of SIGINT and SIGTERM so - it's possible to Ctrl-C the application even if exitEvent() doesn't set - event.setAccepted(). */ - #ifdef SDL_HINT_NO_SIGNAL_HANDLERS - SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1"); - #endif - /* Available since 2.0.6, uses dedicated OpenGL ES drivers if EGL is used, - and desktop GLES context otherwise. */ - #if defined(MAGNUM_TARGET_GLES) && defined(MAGNUM_TARGET_EGL) && defined(SDL_HINT_OPENGL_ES_DRIVER) - SDL_SetHint(SDL_HINT_OPENGL_ES_DRIVER, "1"); - #endif - /* Available since 2.0.8, disables compositor bypass on X11, which causes - flickering on KWin as the compositor gets shut down on every startup */ - #ifdef SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR - SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0"); - #endif - /* By default, SDL behaves like if it was playing a video or whatever, - preventing the computer from turning off the screen or going to sleep. - While it sorta makes sense for games, it's useless and annoying for - regular apps. Together with the compositor disabling those two are the - most stupid defaults. */ - #ifdef SDL_HINT_VIDEO_ALLOW_SCREENSAVER - SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1"); - #endif - /* Available since 2.0.12, use EGL if desired */ - #if defined(MAGNUM_TARGET_EGL) && defined(SDL_HINT_VIDEO_X11_FORCE_EGL) - SDL_SetHint(SDL_HINT_VIDEO_X11_FORCE_EGL, "1"); - #endif - - if(SDL_Init(SDL_INIT_VIDEO) < 0) { - Error() << "Cannot initialize SDL:" << SDL_GetError(); - std::exit(1); - } - - /* Save command-line arguments */ - if(args.value("log") == "verbose") _verboseLog = true; - const Containers::StringView dpiScaling = args.value("dpi-scaling"); - if(dpiScaling == "default"_s) - _commandLineDpiScalingPolicy = Implementation::Sdl2DpiScalingPolicy::Default; - #ifdef CORRADE_TARGET_APPLE - else if(dpiScaling == "framebuffer"_s) - _commandLineDpiScalingPolicy = Implementation::Sdl2DpiScalingPolicy::Framebuffer; - #endif - #ifndef CORRADE_TARGET_APPLE - #if !defined(CORRADE_TARGET_EMSCRIPTEN) && !defined(CORRADE_TARGET_ANDROID) - else if(dpiScaling == "virtual"_s) - _commandLineDpiScalingPolicy = Implementation::Sdl2DpiScalingPolicy::Virtual; - #endif - else if(dpiScaling == "physical"_s) - _commandLineDpiScalingPolicy = Implementation::Sdl2DpiScalingPolicy::Physical; + if(_window) SDL_DestroyWindow(_window); #endif - else if(dpiScaling.containsAny(" \t\n"_s)) - _commandLineDpiScaling = args.value("dpi-scaling"); - else - _commandLineDpiScaling = Vector2{args.value("dpi-scaling")}; } -void Sdl2Application::create() { - create(Configuration{}); -} - -void Sdl2Application::create(const Configuration& configuration) { - if(!tryCreate(configuration)) std::exit(1); -} - -#ifdef MAGNUM_TARGET_GL -void Sdl2Application::create(const Configuration& configuration, const GLConfiguration& glConfiguration) { - if(!tryCreate(configuration, glConfiguration)) std::exit(1); -} -#endif - -Vector2 Sdl2Application::dpiScaling(const Configuration& configuration) { +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 +112,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; @@ -322,72 +205,360 @@ Vector2 Sdl2Application::dpiScalingInternal(const Implementation::Sdl2DpiScaling #else Debug{verbose} << "Platform::Sdl2Application: sorry, physical DPI scaling only available on SDL 2.0.4+"; #endif - return Vector2{1.0f}; + return Vector2{1.0f}; + + /* HOWEVER, on Windows it gets the virtual DPI scaling, which we don't + want, so we need to call Windows APIs directly instead. Consistency my + ass. Related bug report that will probably never get actually + implemented: https://bugzilla.libsdl.org/show_bug.cgi?id=2473 */ + #elif defined(CORRADE_TARGET_WINDOWS) && !defined(CORRADE_TARGET_WINDOWS_RT) + HDC hDC = GetWindowDC(nullptr); + Vector2i monitorSize{GetDeviceCaps(hDC, HORZSIZE), GetDeviceCaps(hDC, VERTSIZE)}; + SDL_DisplayMode mode; + CORRADE_INTERNAL_ASSERT(SDL_GetDesktopDisplayMode(0, &mode) == 0); + auto dpi = Vector2{Vector2i{mode.w, mode.h}*25.4f/Vector2{monitorSize}}; + const Vector2 dpiScaling{dpi/96.0f}; + Debug{verbose} << "Platform::Sdl2Application: physical DPI scaling" << dpiScaling; + return dpiScaling; + + /* Not implemented otherwise */ + #else + Debug{verbose} << "Platform::Sdl2Application: sorry, physical DPI scaling not implemented on this platform yet"; + return Vector2{1.0f}; + #endif + #endif +} + +void Sdl2ApplicationWindow::setWindowTitle(const Containers::StringView title) { + #ifndef CORRADE_TARGET_EMSCRIPTEN + SDL_SetWindowTitle(_window, + Containers::String::nullTerminatedGlobalView(title).data()); + #else + /* We don't have the _window because SDL_CreateWindow() doesn't exist in + the SDL1/2 hybrid. But it's not used anyway, so pass nullptr there. */ + SDL_SetWindowTitle(nullptr, + Containers::String::nullTerminatedGlobalView(title).data()); + #endif +} + +#if !defined(CORRADE_TARGET_EMSCRIPTEN) && SDL_MAJOR_VERSION*1000 + SDL_MINOR_VERSION*100 + SDL_PATCHLEVEL >= 2005 +void Sdl2ApplicationWindow::setWindowIcon(const ImageView2D& image) { + Uint32 format; /** @todo handle sRGB differently? */ + switch(image.format()) { + case PixelFormat::RGB8Srgb: + case PixelFormat::RGB8Unorm: + format = SDL_PIXELFORMAT_RGB24; + break; + case PixelFormat::RGBA8Srgb: + case PixelFormat::RGBA8Unorm: + format = SDL_PIXELFORMAT_RGBA32; + break; + default: + CORRADE_ASSERT_UNREACHABLE("Platform::Sdl2ApplicationWindow::setWindowIcon(): unexpected format" << image.format(), ); + } + + /* Images are loaded with origin at bottom left, flip it to top left. SDL + only accepted a negative stride until version 2.23.1 and commit + https://github.com/libsdl-org/SDL/commit/535fdc3adcdc08a193ab0d45540014fd536cf251 + so we need to manually flip the image now */ + /** @todo take ImageFlag::YUp into account once it exists */ + Image2D flippedImage{PixelStorage{}.setAlignment(1), image.format(), image.size(), Containers::Array{NoInit, std::size_t(image.size().product()*image.pixelSize())}}; + const Containers::StridedArrayView3D flippedPixels = flippedImage.pixels(); + Utility::copy(image.pixels().flipped<0>(), flippedPixels); + + SDL_Surface* const icon = SDL_CreateRGBSurfaceWithFormatFrom(const_cast(flippedPixels.data()) , flippedImage.size().x(), flippedImage.size().y(), 32, flippedPixels.stride()[0], format); + CORRADE_INTERNAL_ASSERT(icon); + + SDL_SetWindowIcon(_window, icon); + SDL_FreeSurface(icon); +} +#endif + +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 - /* HOWEVER, on Windows it gets the virtual DPI scaling, which we don't - want, so we need to call Windows APIs directly instead. Consistency my - ass. Related bug report that will probably never get actually - implemented: https://bugzilla.libsdl.org/show_bug.cgi?id=2473 */ - #elif defined(CORRADE_TARGET_WINDOWS) && !defined(CORRADE_TARGET_WINDOWS_RT) - HDC hDC = GetWindowDC(nullptr); - Vector2i monitorSize{GetDeviceCaps(hDC, HORZSIZE), GetDeviceCaps(hDC, VERTSIZE)}; - SDL_DisplayMode mode; - CORRADE_INTERNAL_ASSERT(SDL_GetDesktopDisplayMode(0, &mode) == 0); - auto dpi = Vector2{Vector2i{mode.w, mode.h}*25.4f/Vector2{monitorSize}}; - const Vector2 dpiScaling{dpi/96.0f}; - Debug{verbose} << "Platform::Sdl2Application: physical DPI scaling" << dpiScaling; - return dpiScaling; + if(SDL_Init(SDL_INIT_VIDEO) < 0) { + Error() << "Cannot initialize SDL:" << SDL_GetError(); + std::exit(1); + } - /* Not implemented otherwise */ - #else - Debug{verbose} << "Platform::Sdl2Application: sorry, physical DPI scaling not implemented on this platform yet"; - return Vector2{1.0f}; + /* 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 -} - -void Sdl2Application::setWindowTitle(const Containers::StringView title) { - #ifndef CORRADE_TARGET_EMSCRIPTEN - SDL_SetWindowTitle(_window, - Containers::String::nullTerminatedGlobalView(title).data()); - #else - /* We don't have the _window because SDL_CreateWindow() doesn't exist in - the SDL1/2 hybrid. But it's not used anyway, so pass nullptr there. */ - SDL_SetWindowTitle(nullptr, - Containers::String::nullTerminatedGlobalView(title).data()); + 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")}; } -#if !defined(CORRADE_TARGET_EMSCRIPTEN) && SDL_MAJOR_VERSION*1000 + SDL_MINOR_VERSION*100 + SDL_PATCHLEVEL >= 2005 -void Sdl2Application::setWindowIcon(const ImageView2D& image) { - Uint32 format; /** @todo handle sRGB differently? */ - switch(image.format()) { - case PixelFormat::RGB8Srgb: - case PixelFormat::RGB8Unorm: - format = SDL_PIXELFORMAT_RGB24; - break; - case PixelFormat::RGBA8Srgb: - case PixelFormat::RGBA8Unorm: - format = SDL_PIXELFORMAT_RGBA32; - break; - default: - CORRADE_ASSERT_UNREACHABLE("Platform::Sdl2Application::setWindowIcon(): unexpected format" << image.format(), ); - } - - /* Images are loaded with origin at bottom left, flip it to top left. SDL - only accepted a negative stride until version 2.23.1 and commit - https://github.com/libsdl-org/SDL/commit/535fdc3adcdc08a193ab0d45540014fd536cf251 - so we need to manually flip the image now */ - /** @todo take ImageFlag::YUp into account once it exists */ - Image2D flippedImage{PixelStorage{}.setAlignment(1), image.format(), image.size(), Containers::Array{NoInit, std::size_t(image.size().product()*image.pixelSize())}}; - const Containers::StridedArrayView3D flippedPixels = flippedImage.pixels(); - Utility::copy(image.pixels().flipped<0>(), flippedPixels); +void Sdl2Application::create() { + create(Configuration{}); +} - 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); +void Sdl2Application::create(const Configuration& configuration) { + if(!tryCreate(configuration)) std::exit(1); +} - SDL_SetWindowIcon(_window, icon); - SDL_FreeSurface(icon); +#ifdef MAGNUM_TARGET_GL +void Sdl2Application::create(const Configuration& configuration, const GLConfiguration& glConfiguration) { + if(!tryCreate(configuration, glConfiguration)) std::exit(1); } #endif @@ -735,66 +906,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 @@ -827,8 +945,6 @@ bool Sdl2Application::setSwapInterval(const Int interval) { return true; } -void Sdl2Application::redraw() { _flags |= Flag::Redraw; } - Sdl2Application::~Sdl2Application() { /* SDL_DestroyWindow(_window) crashes on windows when _window is nullptr (it doesn't seem to crash on Linux). Because this seems to be yet @@ -852,9 +968,14 @@ Sdl2Application::~Sdl2Application() { SDL_FreeCursor(cursor); #endif - #ifndef CORRADE_TARGET_EMSCRIPTEN - if(_window) SDL_DestroyWindow(_window); - #endif + /* The window would be destroyed in the base ~Sdl2ApplicationWindow(), but + that's too late. Do it before calling SDL_Quit() and then reset the + window pointer so it's not called again after. */ + if(_window) { + SDL_DestroyWindow(_window); + _window = nullptr; + } + SDL_Quit(); } @@ -946,14 +1067,14 @@ bool Sdl2Application::mainLoopIteration() { dpiScaling()}; /** @todo handle also WM_DPICHANGED events when a window is moved between displays with different DPI */ viewportEvent(e); - _flags |= Flag::Redraw; + _windowFlags |= WindowFlag::Redraw; #endif } break; /* Direct everything that wasn't exposed via a callback to anyEvent(), so users can implement event handling for things not present in the Application APIs */ case SDL_WINDOWEVENT_EXPOSED: - _flags |= Flag::Redraw; + _windowFlags |= WindowFlag::Redraw; if(!(_flags & Flag::NoAnyEvent)) anyEvent(event); break; default: @@ -1027,8 +1148,8 @@ bool Sdl2Application::mainLoopIteration() { if(!(_flags & Flag::NoTickEvent)) tickEvent(); /* Draw event */ - if(_flags & Flag::Redraw) { - _flags &= ~Flag::Redraw; + if(_windowFlags & WindowFlag::Redraw) { + _windowFlags &= ~WindowFlag::Redraw; drawEvent(); #ifndef CORRADE_TARGET_EMSCRIPTEN @@ -1058,99 +1179,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 */ @@ -1207,34 +1235,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 @@ -1245,38 +1246,54 @@ Sdl2Application::Configuration::Configuration(): #endif _dpiScalingPolicy{DpiScalingPolicy::Default} {} -Sdl2Application::Configuration::~Configuration() = default; +Sdl2ApplicationWindow::Configuration::~Configuration() = default; -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 fd4be4674..552af95e1 100644 --- a/src/Magnum/Platform/Sdl2Application.h +++ b/src/Magnum/Platform/Sdl2Application.h @@ -95,6 +95,430 @@ namespace Implementation { enum class Sdl2DpiScalingPolicy: UnsignedByte; } +class Sdl2Application; + +/** +@brief SDL2 application window +@m_since_latest + +@ref TODOTODO document + +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; + + /** @brief Copying is not allowed */ + Sdl2ApplicationWindow(const Sdl2ApplicationWindow&) = delete; + + /** @brief Moving is not allowed */ + Sdl2ApplicationWindow(Sdl2ApplicationWindow&&) = delete; + + /** @brief Copying is not allowed */ + Sdl2ApplicationWindow& operator=(const Sdl2ApplicationWindow&) = delete; + + /** @brief Moving is not allowed */ + Sdl2ApplicationWindow& operator=(Sdl2ApplicationWindow&&) = delete; + + /* 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(); + + #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 + + /** @{ @name Window handling */ + + /** + * @brief Window size + * + * Window size to which all input event coordinates can be related. + * Note that, especially on HiDPI systems, it may be different from + * @ref framebufferSize(). Expects that a window is already created. + * See @ref Platform-Sdl2Application-dpi for more information. + * @see @ref dpiScaling() + */ + Vector2i windowSize() const; + + #if !defined(CORRADE_TARGET_EMSCRIPTEN) || defined(DOXYGEN_GENERATING_OUTPUT) + /** + * @brief Set window size + * @param size The size, in screen coordinates + * @m_since{2020,06} + * + * To make the sizing work independently of the display DPI, @p size is + * internally multiplied with @ref dpiScaling() before getting applied. + * Expects that a window is already created. + * @note Not available in @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". + * @see @ref setMinWindowSize(), @ref setMaxWindowSize() + */ + void setWindowSize(const Vector2i& size); + + /** + * @brief Set minimum window size + * @param size The minimum size, in screen coordinates + * @m_since{2019,10} + * + * Note that, unlike in @ref GlfwApplication, SDL2 doesn't have a way + * to disable/remove a size limit. To make the sizing work + * independently of the display DPI, @p size is internally multiplied + * with @ref dpiScaling() before getting applied. Expects that a window + * is already created. + * @note Not available in @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". + * @see @ref setMaxWindowSize(), @ref setWindowSize() + */ + void setMinWindowSize(const Vector2i& size); + + /** + * @brief Set maximal window size + * @param size The maximum size, in screen coordinates + * @m_since{2019,10} + * + * Note that, unlike in @ref GlfwApplication, SDL2 doesn't have a way + * to disable/remove a size limit. To make the sizing work + * independently of the display DPI, @p size is internally multiplied + * with @ref dpiScaling() before getting applied. Expects that a window + * is already created. + * @note Not available in @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". + * @see @ref setMinWindowSize(), @ref setMaxWindowSize() + */ + void setMaxWindowSize(const Vector2i& size); + #endif + + #if defined(MAGNUM_TARGET_GL) || defined(DOXYGEN_GENERATING_OUTPUT) + /** + * @brief Framebuffer size + * + * Size of the default framebuffer. Note that, especially on HiDPI + * systems, it may be different from @ref windowSize(). Expects that a + * window is already created. See @ref Platform-Sdl2Application-dpi for + * more information. + * + * @note This function is available only if Magnum is compiled with + * @ref MAGNUM_TARGET_GL enabled (done by default). See + * @ref building-features for more information. + * + * @see @ref Sdl2Application::framebufferSize(), @ref dpiScaling() + */ + Vector2i framebufferSize() const; + #endif + + /** + * @brief DPI scaling + * + * How the content should be scaled relative to system defaults for + * given @ref windowSize(). If a window is not created yet, returns + * zero vector, use @ref dpiScaling(const Configuration&) for + * calculating a value independently. See @ref Platform-Sdl2Application-dpi + * for more information. + * @see @ref framebufferSize() + */ + Vector2 dpiScaling() const; + + /** + * @brief DPI scaling for given configuration + * + * Calculates DPI scaling that would be used when creating a window + * with given @p configuration. Takes into account DPI scaling policy + * and custom scaling specified on the command-line. See + * @ref Platform-Sdl2Application-dpi for more information. + */ + Vector2 dpiScaling(const Configuration& configuration); + + /** + * @brief Set window title + * @m_since{2019,10} + * + * The @p title is expected to be encoded in UTF-8. + */ + void setWindowTitle(Containers::StringView title); + + #if !defined(CORRADE_TARGET_EMSCRIPTEN) && (SDL_MAJOR_VERSION*1000 + SDL_MINOR_VERSION*100 + SDL_PATCHLEVEL >= 2005 || defined(DOXYGEN_GENERATING_OUTPUT)) + /** + * @brief Set window icon + * @m_since{2020,06} + * + * The @p image is expected to be with origin at bottom left (which is + * the default for imported images) and in one of + * @ref PixelFormat::RGB8Unorm, @ref PixelFormat::RGB8Srgb, + * @ref PixelFormat::RGBA8Unorm or @ref PixelFormat::RGBA8Srgb formats. + * Unlike @ref GlfwApplication::setWindowIcon(), SDL doesn't provide a + * way to supply multiple images in different sizes. + * @note Available since SDL 2.0.5. Not available on + * @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten", use + * @cb{.html} @ce in your HTML markup instead. + * Although it's not documented in SDL itself, the function might + * have no effect on macOS / Wayland, similarly to how + * @ref GlfwApplication::setWindowIcon() behaves on those + * platforms. + * @see @ref platform-windows-icon "Excecutable icon on Windows", + * @ref Trade::IcoImporter "IcoImporter" + */ + void setWindowIcon(const ImageView2D& image); + #endif + + /** + * @brief Swap buffers + * + * Paints currently rendered framebuffer on screen. + * @see @ref Sdl2Application::setSwapInterval() + */ + void swapBuffers(); + + /** + * @brief Redraw immediately + * + * Marks the window for redrawing, resulting in call to @ref drawEvent() + * in the next iteration. You can call it from @ref drawEvent() itself + * to redraw immediately without waiting for user input. + */ + void redraw(); + + private: + /** + * @brief Viewport event + * + * Called when window size changes. The default implementation does + * nothing. If you want to respond to size changes, you should pass the + * new *framebuffer* size to @ref GL::DefaultFramebuffer::setViewport() + * (if using OpenGL) and possibly elsewhere (to + * @ref SceneGraph::Camera::setViewport(), other framebuffers...) and + * the new *window* size and DPI scaling to APIs that respond to user + * events or scale UI elements. + * + * Note that this function might not get called at all if the window + * size doesn't change. You should configure the initial state of your + * cameras, framebuffers etc. in application constructor rather than + * relying on this function to be called. Size of the window can be + * retrieved using @ref windowSize(), size of the backing framebuffer + * via @ref framebufferSize() and DPI scaling using @ref dpiScaling(). + * See @ref Platform-Sdl2Application-dpi for detailed info about these + * values. + */ + virtual void viewportEvent(ViewportEvent& event); + + /** + * @brief Draw event + * + * Called when the screen is redrawn. You should clean the framebuffer + * using @ref GL::DefaultFramebuffer::clear() (if using OpenGL) and + * then add your own drawing functions. After drawing is finished, call + * @ref swapBuffers(). If you want to draw immediately again, call also + * @ref redraw(). + */ + virtual void drawEvent() = 0; + + /* Since 1.8.17, the original short-hand group closing doesn't work + anymore. FFS. */ + /** + * @} + */ + + /** @{ @name Keyboard handling */ + + /** + * @brief Key press event + * + * Called when an key is pressed. Default implementation does nothing. + */ + virtual void keyPressEvent(KeyEvent& event); + + /** + * @brief Key release event + * + * Called when an key is released. Default implementation does nothing. + */ + virtual void keyReleaseEvent(KeyEvent& event); + + /* Since 1.8.17, the original short-hand group closing doesn't work + anymore. FFS. */ + /** + * @} + */ + + /** @{ @name Mouse handling */ + + public: + /** + * @brief Cursor type + * @m_since{2020,06} + * + * @see @ref setCursor() + */ + enum class Cursor: UnsignedInt { + Arrow, /**< Arrow */ + TextInput, /**< Text input */ + Wait, /**< Wait */ + Crosshair, /**< Crosshair */ + WaitArrow, /**< Small wait cursor */ + ResizeNWSE, /**< Double arrow pointing northwest and southeast */ + ResizeNESW, /**< Double arrow pointing northeast and southwest */ + ResizeWE, /**< Double arrow pointing west and east */ + ResizeNS, /**< Double arrow pointing north and south */ + ResizeAll, /**< Four pointed arrow pointing north, south, east, and west */ + No, /**< Slashed circle or crossbones */ + Hand, /**< Hand */ + Hidden, /**< Hidden */ + + #ifndef CORRADE_TARGET_EMSCRIPTEN + /** + * Hidden and locked. When the mouse is locked, only + * @ref MouseMoveEvent::relativePosition() is changing, absolute + * position stays the same. + * @note Not available in @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". + */ + HiddenLocked + #endif + }; + + /** + * @brief Set cursor type + * @m_since{2020,06} + * + * Expects that a window is already created. Default is + * @ref Cursor::Arrow. + * @ref TODOTODO uhh clean up the docs to say "that the window is", not "a" + */ + void setCursor(Cursor cursor); + + /** + * @brief Get current cursor type + * @m_since{2020,06} + */ + Cursor cursor(); + + #ifndef CORRADE_TARGET_EMSCRIPTEN + /** + * @brief Warp mouse cursor to given coordinates + * + * @note Not available in @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". + */ + void warpCursor(const Vector2i& position) { + SDL_WarpMouseInWindow(_window, position.x(), position.y()); + } + #endif + + private: + /** + * @brief Mouse press event + * + * Called when mouse button is pressed. Default implementation does + * nothing. + */ + virtual void mousePressEvent(MouseEvent& event); + + /** + * @brief Mouse release event + * + * Called when mouse button is released. Default implementation does + * nothing. + */ + virtual void mouseReleaseEvent(MouseEvent& event); + + /** + * @brief Mouse move event + * + * Called when mouse is moved. Default implementation does nothing. + */ + virtual void mouseMoveEvent(MouseMoveEvent& event); + + /** + * @brief Mouse scroll event + * + * Called when a scrolling device is used (mouse wheel or scrolling + * area on a touchpad). Default implementation does nothing. + */ + virtual void mouseScrollEvent(MouseScrollEvent& event); + + /* Since 1.8.17, the original short-hand group closing doesn't work + anymore. FFS. */ + /** + * @} + */ + + /** @{ @name Touch gesture handling */ + + /** + * @brief Multi gesture event + * + * Called when the user performs a gesture using multiple fingers. + * Default implementation does nothing. + * @experimental + */ + virtual void multiGestureEvent(MultiGestureEvent& event); + + /* Since 1.8.17, the original short-hand group closing doesn't work + anymore. FFS. */ + /** + * @} + */ + + /** @{ @name Text input handling */ + + /** + * @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); + + /** + * @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. */ + /** + * @} + */ + + private: + friend Sdl2Application; + + enum class WindowFlag: UnsignedByte; + typedef Containers::EnumSet WindowFlags; + CORRADE_ENUMSET_FRIEND_OPERATORS(WindowFlags) + + /* Used by Sdl2Application(NoCreateT) */ + explicit Sdl2ApplicationWindow(Sdl2Application& application, NoCreateT); + + Vector2 dpiScalingInternal(Implementation::Sdl2DpiScalingPolicy configurationDpiScalingPolicy, const Vector2& configurationDpiScaling) const; + + Sdl2Application& _application; + + #ifndef CORRADE_TARGET_EMSCRIPTEN + SDL_Window* _window{}; + #else + SDL_Surface* _surface{}; + Vector2i _lastKnownCanvasSize; + #endif + + WindowFlags _windowFlags; +}; + /** @nosubgrouping @brief SDL2 application @@ -339,8 +763,9 @@ the compositor in system-wide KWin settings. @subsection Platform-Sdl2Application-usage-ios iOS specifics -Leaving a default (zero) window size in @ref Configuration will cause the app -to autodetect it based on the actual device screen size. This also depends on +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 @@ -348,20 +773,22 @@ options needs to be set via a `*.plist` file. Some options can be configured from runtime when creating the SDL2 application window, see documentation of a particular value for details: -- @ref Configuration::WindowFlag::Borderless hides the menu bar -- @ref Configuration::WindowFlag::Resizable makes the application respond to - device orientation changes +- @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 @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 +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 @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. +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 @@ -395,8 +822,8 @@ variable). 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 / + @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 @@ -405,8 +832,8 @@ variable). 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. + @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 @@ -418,8 +845,9 @@ variable). 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. + 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 @@ -433,24 +861,25 @@ affect sharpness of the contents. The default is depending on the platform: - On macOS and iOS, the default and only supported option is - @ref Configuration::DpiScalingPolicy::Framebuffer. On this platform, - @ref windowSize() and @ref framebufferSize() will differ depending on - whether `NSHighResolutionCapable` is enabled in the `*.plist` file or not. - By default, @ref dpiScaling() is @cpp 1.0f @ce in both dimensions but it - can be overridden using custom DPI scaling. -- On Windows, the default is @ref Configuration::DpiScalingPolicy::Framebuffer. + @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 @ref Configuration::DpiScalingPolicy::Virtual, +- 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 @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. + 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, @@ -480,7 +909,7 @@ If your application is saving and restoring window size, it's advisable to take properly handle cases where the window is opened on a display with different DPI. */ -class Sdl2Application { +class Sdl2Application: public Sdl2ApplicationWindow { public: /** @brief Application arguments */ struct Arguments { @@ -491,20 +920,10 @@ class Sdl2Application { char** argv; /**< @brief Argument values */ }; - class Configuration; #ifdef MAGNUM_TARGET_GL class GLConfiguration; #endif class ExitEvent; - class ViewportEvent; - class InputEvent; - class KeyEvent; - class MouseEvent; - class MouseMoveEvent; - class MouseScrollEvent; - class MultiGestureEvent; - class TextInputEvent; - class TextEditingEvent; #ifdef MAGNUM_TARGET_GL /** @@ -514,9 +933,9 @@ class Sdl2Application { * @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. + * See @relativeref{Sdl2ApplicationWindow,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 @@ -528,9 +947,10 @@ class Sdl2Application { /** * @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 @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 @@ -615,17 +1035,6 @@ class Sdl2Application { */ void exit(int exitCode = 0); - #ifndef CORRADE_TARGET_EMSCRIPTEN - /** - * @brief Underlying window handle - * - * Use in case you need to call SDL functionality directly. Returns - * @cpp nullptr @ce in case the window was not created yet. - * @note Not available in @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". - */ - SDL_Window* window() { return _window; } - #endif - #if defined(MAGNUM_TARGET_GL) && !defined(CORRADE_TARGET_EMSCRIPTEN) /** * @brief Underlying OpenGL context @@ -672,181 +1081,54 @@ class Sdl2Application { /** * @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 @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); - - /** - * @brief Create a window with default configuration and OpenGL context - * - * Equivalent to calling @ref create(const Configuration&) with - * default-constructed @ref Configuration. - */ - void create(); - - #ifdef MAGNUM_TARGET_GL - /** - * @brief Try to create context with given configuration for OpenGL context - * - * Unlike @ref create(const Configuration&, const GLConfiguration&) - * returns @cpp false @ce if the context cannot be created, - * @cpp true @ce otherwise. - * - * @note This function is available only if Magnum is compiled with - * @ref MAGNUM_TARGET_GL enabled (done by default). See - * @ref building-features for more information. - */ - bool tryCreate(const Configuration& configuration, const GLConfiguration& glConfiguration); - #endif - - /** - * @brief Try to create context with given configuration - * - * Unlike @ref create(const Configuration&) returns @cpp false @ce if - * the context cannot be created, @cpp true @ce otherwise. - */ - bool tryCreate(const Configuration& configuration); - - /** @{ @name Screen handling */ - - public: - /** - * @brief Window size - * - * Window size to which all input event coordinates can be related. - * Note that, especially on HiDPI systems, it may be different from - * @ref framebufferSize(). Expects that a window is already created. - * See @ref Platform-Sdl2Application-dpi for more information. - * @see @ref dpiScaling() - */ - Vector2i windowSize() const; - - #if !defined(CORRADE_TARGET_EMSCRIPTEN) || defined(DOXYGEN_GENERATING_OUTPUT) - /** - * @brief Set window size - * @param size The size, in screen coordinates - * @m_since{2020,06} - * - * To make the sizing work independently of the display DPI, @p size is - * internally multiplied with @ref dpiScaling() before getting applied. - * Expects that a window is already created. - * @note Not available in @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". - * @see @ref setMinWindowSize(), @ref setMaxWindowSize() - */ - void setWindowSize(const Vector2i& size); - - /** - * @brief Set minimum window size - * @param size The minimum size, in screen coordinates - * @m_since{2019,10} - * - * Note that, unlike in @ref GlfwApplication, SDL2 doesn't have a way - * to disable/remove a size limit. To make the sizing work - * independently of the display DPI, @p size is internally multiplied - * with @ref dpiScaling() before getting applied. Expects that a window - * is already created. - * @note Not available in @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". - * @see @ref setMaxWindowSize(), @ref setWindowSize() - */ - void setMinWindowSize(const Vector2i& size); - - /** - * @brief Set maximal window size - * @param size The maximum size, in screen coordinates - * @m_since{2019,10} - * - * Note that, unlike in @ref GlfwApplication, SDL2 doesn't have a way - * to disable/remove a size limit. To make the sizing work - * independently of the display DPI, @p size is internally multiplied - * with @ref dpiScaling() before getting applied. Expects that a window - * is already created. - * @note Not available in @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". - * @see @ref setMinWindowSize(), @ref setMaxWindowSize() - */ - void setMaxWindowSize(const Vector2i& size); - #endif - - #if defined(MAGNUM_TARGET_GL) || defined(DOXYGEN_GENERATING_OUTPUT) - /** - * @brief Framebuffer size - * - * Size of the default framebuffer. Note that, especially on HiDPI - * systems, it may be different from @ref windowSize(). Expects that a - * window is already created. See @ref Platform-Sdl2Application-dpi for - * more information. - * - * @note This function is available only if Magnum is compiled with - * @ref MAGNUM_TARGET_GL enabled (done by default). See - * @ref building-features for more information. - * - * @see @ref Sdl2Application::framebufferSize(), @ref dpiScaling() - */ - Vector2i framebufferSize() const; - #endif - - /** - * @brief DPI scaling - * - * How the content should be scaled relative to system defaults for - * given @ref windowSize(). If a window is not created yet, returns - * zero vector, use @ref dpiScaling(const Configuration&) for - * calculating a value independently. See @ref Platform-Sdl2Application-dpi - * for more information. - * @see @ref framebufferSize() - */ - Vector2 dpiScaling() const; - - /** - * @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. + * See also @ref building-features for more information. */ - Vector2 dpiScaling(const Configuration& configuration); + void create(const Configuration& configuration); /** - * @brief Set window title - * @m_since{2019,10} + * @brief Create a window with default configuration and OpenGL context * - * The @p title is expected to be encoded in UTF-8. + * Equivalent to calling @ref create(const Configuration&) with + * default-constructed @relativeref{Sdl2ApplicationWindow,Configuration}. */ - void setWindowTitle(Containers::StringView title); + void create(); - #if !defined(CORRADE_TARGET_EMSCRIPTEN) && (SDL_MAJOR_VERSION*1000 + SDL_MINOR_VERSION*100 + SDL_PATCHLEVEL >= 2005 || defined(DOXYGEN_GENERATING_OUTPUT)) + #ifdef MAGNUM_TARGET_GL /** - * @brief Set window icon - * @m_since{2020,06} + * @brief Try to create context with given configuration for OpenGL context * - * 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" + * 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. */ - void setWindowIcon(const ImageView2D& image); + bool tryCreate(const Configuration& configuration, const GLConfiguration& glConfiguration); #endif + /** + * @brief Try to create context with given configuration + * + * Unlike @ref create(const Configuration&) returns @cpp false @ce if + * the context cannot be created, @cpp true @ce otherwise. + */ + bool tryCreate(const Configuration& configuration); + + /** @{ @name Screen handling */ + + public: #if defined(CORRADE_TARGET_EMSCRIPTEN) || defined(DOXYGEN_GENERATING_OUTPUT) /** * @brief Set container CSS class @@ -872,14 +1154,6 @@ class Sdl2Application { void setContainerCssClass(Containers::StringView cssClass); #endif - /** - * @brief Swap buffers - * - * Paints currently rendered framebuffer on screen. - * @see @ref setSwapInterval() - */ - void swapBuffers(); - /** @brief Swap interval */ Int swapInterval() const; @@ -912,71 +1186,6 @@ class Sdl2Application { } #endif - /** - * @brief Redraw immediately - * - * Marks the window for redrawing, resulting in call to @ref drawEvent() - * in the next iteration. You can call it from @ref drawEvent() itself - * to redraw immediately without waiting for user input. - */ - void redraw(); - - private: - /** - * @brief Viewport event - * - * Called when window size changes. The default implementation does - * nothing. If you want to respond to size changes, you should pass the - * new *framebuffer* size to @ref GL::DefaultFramebuffer::setViewport() - * (if using OpenGL) and possibly elsewhere (to - * @ref SceneGraph::Camera::setViewport(), other framebuffers...) and - * the new *window* size and DPI scaling to APIs that respond to user - * events or scale UI elements. - * - * Note that this function might not get called at all if the window - * size doesn't change. You should configure the initial state of your - * cameras, framebuffers etc. in application constructor rather than - * relying on this function to be called. Size of the window can be - * retrieved using @ref windowSize(), size of the backing framebuffer - * via @ref framebufferSize() and DPI scaling using @ref dpiScaling(). - * See @ref Platform-Sdl2Application-dpi for detailed info about these - * values. - */ - virtual void viewportEvent(ViewportEvent& event); - - /** - * @brief Draw event - * - * Called when the screen is redrawn. You should clean the framebuffer - * using @ref GL::DefaultFramebuffer::clear() (if using OpenGL) and - * then add your own drawing functions. After drawing is finished, call - * @ref swapBuffers(). If you want to draw immediately again, call also - * @ref redraw(). - */ - virtual void drawEvent() = 0; - - /* Since 1.8.17, the original short-hand group closing doesn't work - anymore. FFS. */ - /** - * @} - */ - - /** @{ @name Keyboard handling */ - - /** - * @brief Key press event - * - * Called when an key is pressed. Default implementation does nothing. - */ - virtual void keyPressEvent(KeyEvent& event); - - /** - * @brief Key release event - * - * Called when an key is released. Default implementation does nothing. - */ - virtual void keyReleaseEvent(KeyEvent& event); - /* Since 1.8.17, the original short-hand group closing doesn't work anymore. FFS. */ /** @@ -985,65 +1194,6 @@ class Sdl2Application { /** @{ @name Mouse handling */ - public: - /** - * @brief Cursor type - * @m_since{2020,06} - * - * @see @ref setCursor() - */ - enum class Cursor: UnsignedInt { - Arrow, /**< Arrow */ - TextInput, /**< Text input */ - Wait, /**< Wait */ - Crosshair, /**< Crosshair */ - WaitArrow, /**< Small wait cursor */ - ResizeNWSE, /**< Double arrow pointing northwest and southeast */ - ResizeNESW, /**< Double arrow pointing northeast and southwest */ - ResizeWE, /**< Double arrow pointing west and east */ - ResizeNS, /**< Double arrow pointing north and south */ - ResizeAll, /**< Four pointed arrow pointing north, south, east, and west */ - No, /**< Slashed circle or crossbones */ - Hand, /**< Hand */ - Hidden, /**< Hidden */ - - #ifndef CORRADE_TARGET_EMSCRIPTEN - /** - * Hidden and locked. When the mouse is locked, only - * @ref MouseMoveEvent::relativePosition() is changing, absolute - * position stays the same. - * @note Not available in @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". - */ - HiddenLocked - #endif - }; - - /** - * @brief Set cursor type - * @m_since{2020,06} - * - * Expects that a window is already created. Default is - * @ref Cursor::Arrow. - */ - void setCursor(Cursor cursor); - - /** - * @brief Get current cursor type - * @m_since{2020,06} - */ - Cursor cursor(); - - #ifndef CORRADE_TARGET_EMSCRIPTEN - /** - * @brief Warp mouse cursor to given coordinates - * - * @note Not available in @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". - */ - void warpCursor(const Vector2i& position) { - SDL_WarpMouseInWindow(_window, position.x(), position.y()); - } - #endif - #ifdef MAGNUM_BUILD_DEPRECATED /** * @brief Whether mouse is locked @@ -1062,55 +1212,6 @@ class Sdl2Application { CORRADE_DEPRECATED("use setCursor() together with Cursor::HiddenLocked instead") void setMouseLocked(bool enabled); #endif - private: - /** - * @brief Mouse press event - * - * Called when mouse button is pressed. Default implementation does - * nothing. - */ - virtual void mousePressEvent(MouseEvent& event); - - /** - * @brief Mouse release event - * - * Called when mouse button is released. Default implementation does - * nothing. - */ - virtual void mouseReleaseEvent(MouseEvent& event); - - /** - * @brief Mouse move event - * - * Called when mouse is moved. Default implementation does nothing. - */ - virtual void mouseMoveEvent(MouseMoveEvent& event); - - /** - * @brief Mouse scroll event - * - * Called when a scrolling device is used (mouse wheel or scrolling - * area on a touchpad). Default implementation does nothing. - */ - virtual void mouseScrollEvent(MouseScrollEvent& event); - - /* Since 1.8.17, the original short-hand group closing doesn't work - anymore. FFS. */ - /** - * @} - */ - - /** @{ @name Touch gesture handling */ - - /** - * @brief Multi gesture event - * - * Called when the user performs a gesture using multiple fingers. - * Default implementation does nothing. - * @experimental - */ - virtual void multiGestureEvent(MultiGestureEvent& event); - /* Since 1.8.17, the original short-hand group closing doesn't work anymore. FFS. */ /** @@ -1118,7 +1219,7 @@ class Sdl2Application { */ /** @{ @name Text input handling */ - public: + /** * @brief Whether text input is active * @@ -1159,22 +1260,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. */ /** @@ -1183,6 +1268,7 @@ class Sdl2Application { /** @{ @name Special events */ + private: /** * @brief Exit event * @@ -1234,12 +1320,12 @@ 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 SDL_Cursor* _cursors[12]{}; #else @@ -1253,11 +1339,7 @@ class Sdl2Application { Vector2 _commandLineDpiScaling, _configurationDpiScaling; #ifndef CORRADE_TARGET_EMSCRIPTEN - SDL_Window* _window{}; UnsignedInt _minimalLoopPeriod; - #else - SDL_Surface* _surface{}; - Vector2i _lastKnownCanvasSize; #endif #ifdef MAGNUM_TARGET_GL @@ -1285,7 +1367,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: @@ -1587,10 +1669,12 @@ namespace Implementation { /** @brief Configuration -@see @ref Sdl2Application(), @ref GLConfiguration, @ref create(), - @ref tryCreate() +@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 @@ -1723,13 +1807,13 @@ class Sdl2Application::Configuration { /** * 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&). + * @ref Sdl2Application::Sdl2Application(const Arguments&, const Configuration&), + * @ref Sdl2Application::create(const Configuration&) or + * @ref Sdl2Application::tryCreate(const Configuration&) to prevent + * implicit creation of an OpenGL context. Can't be used with + * @ref Sdl2Application::Sdl2Application(const Arguments&, const Configuration&, const GLConfiguration&), + * @ref Sdl2Application::create(const Configuration&, const GLConfiguration&) or + * @ref Sdl2Application::tryCreate(const Configuration&, const GLConfiguration&). */ Contextless = 1u << 31, /* Hope this won't ever conflict with anything */ @@ -1737,9 +1821,9 @@ class Sdl2Application::Configuration { * 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&). + * @ref Sdl2Application::Sdl2Application(const Arguments&, const Configuration&, const GLConfiguration&), + * @ref Sdl2Application::create(const Configuration&, const GLConfiguration&) + * or @ref Sdl2Application::tryCreate(const Configuration&, const GLConfiguration&). * @m_since{2019,10} */ OpenGL = SDL_WINDOW_OPENGL, @@ -2025,7 +2109,7 @@ class Sdl2Application::ExitEvent { @see @ref viewportEvent() */ -class Sdl2Application::ViewportEvent { +class Sdl2ApplicationWindow::ViewportEvent { public: /** @brief Copying is not allowed */ ViewportEvent(const ViewportEvent&) = delete; @@ -2126,7 +2210,7 @@ class Sdl2Application::ViewportEvent { @ref keyReleaseEvent(), @ref mousePressEvent(), @ref mouseReleaseEvent(), @ref mouseMoveEvent() */ -class Sdl2Application::InputEvent { +class Sdl2ApplicationWindow::InputEvent { public: /** * @brief Modifier @@ -2235,7 +2319,7 @@ class Sdl2Application::InputEvent { @see @ref keyPressEvent(), @ref keyReleaseEvent() */ -class Sdl2Application::KeyEvent: public Sdl2Application::InputEvent { +class Sdl2ApplicationWindow::KeyEvent: public InputEvent { public: /** * @brief Key @@ -2533,7 +2617,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 @@ -2600,7 +2684,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 @@ -2661,7 +2745,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; } @@ -2696,7 +2780,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; @@ -2773,7 +2857,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; @@ -2831,7 +2915,7 @@ class Sdl2Application::TextInputEvent { @see @ref textEditingEvent() */ -class Sdl2Application::TextEditingEvent { +class Sdl2ApplicationWindow::TextEditingEvent { public: /** @brief Copying is not allowed */ TextEditingEvent(const TextEditingEvent&) = delete; @@ -2939,6 +3023,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) @@ -2947,9 +3032,9 @@ 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::Configuration::WindowFlags) +CORRADE_ENUMSET_OPERATORS(Sdl2ApplicationWindow::InputEvent::Modifiers) +CORRADE_ENUMSET_OPERATORS(Sdl2ApplicationWindow::MouseMoveEvent::Buttons) }} diff --git a/src/Magnum/SceneGraph/Camera.h b/src/Magnum/SceneGraph/Camera.h index 4f8abcca8..085639814 100644 --- a/src/Magnum/SceneGraph/Camera.h +++ b/src/Magnum/SceneGraph/Camera.h @@ -156,7 +156,7 @@ template class Camera: public AbstractFeature Date: Sat, 29 Apr 2023 17:46:07 +0200 Subject: [PATCH 2/3] Platform: make GPU context options application-global. Instead of per-window. It's just too complicated to allow some windows with GL context, some with Vulkan, some with neither and some with both. For now at least. --- doc/changelog.dox | 4 + src/Magnum/Platform/Sdl2Application.cpp | 59 +++++++- src/Magnum/Platform/Sdl2Application.h | 180 +++++++++++++++++++++--- 3 files changed, 216 insertions(+), 27 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index d93c97180..d295bff0e 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -1273,6 +1273,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, diff --git a/src/Magnum/Platform/Sdl2Application.cpp b/src/Magnum/Platform/Sdl2Application.cpp index c9eec6318..186828f59 100644 --- a/src/Magnum/Platform/Sdl2Application.cpp +++ b/src/Magnum/Platform/Sdl2Application.cpp @@ -564,7 +564,7 @@ void Sdl2Application::create(const Configuration& configuration, const GLConfigu 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 @@ -584,7 +584,7 @@ bool Sdl2Application::tryCreate(const Configuration& configuration) { #endif SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, scaledWindowSize.x(), scaledWindowSize.y(), - SDL_WINDOW_ALLOW_HIGHDPI|SDL_WINDOW_OPENGL|Uint32(configuration.windowFlags() & ~Configuration::WindowFlag::Contextless)))) + SDL_WINDOW_ALLOW_HIGHDPI|SDL_WINDOW_OPENGL|Uint32(configuration.windowFlags())))) { Error() << "Platform::Sdl2Application::tryCreate(): cannot create window:" << SDL_GetError(); return false; @@ -641,8 +641,8 @@ 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); @@ -1248,6 +1248,57 @@ Sdl2ApplicationWindow::Configuration::Configuration(): 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 Sdl2ApplicationWindow::KeyEvent::keyName(const Key key) { return SDL_GetKeyName(SDL_Keycode(key)); } diff --git a/src/Magnum/Platform/Sdl2Application.h b/src/Magnum/Platform/Sdl2Application.h index 552af95e1..2c4cebec6 100644 --- a/src/Magnum/Platform/Sdl2Application.h +++ b/src/Magnum/Platform/Sdl2Application.h @@ -920,6 +920,7 @@ class Sdl2Application: public Sdl2ApplicationWindow { char** argv; /**< @brief Argument values */ }; + class Configuration; #ifdef MAGNUM_TARGET_GL class GLConfiguration; #endif @@ -1667,8 +1668,10 @@ namespace Implementation { } /** -@brief Configuration +@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(), @@ -1805,38 +1808,34 @@ class Sdl2ApplicationWindow::Configuration { #endif #endif + #ifdef MAGNUM_BUILD_DEPRECATED /** - * Do not create any GPU context. Use together with - * @ref Sdl2Application::Sdl2Application(const Arguments&, const Configuration&), - * @ref Sdl2Application::create(const Configuration&) or - * @ref Sdl2Application::tryCreate(const Configuration&) to prevent - * implicit creation of an OpenGL context. Can't be used with - * @ref Sdl2Application::Sdl2Application(const Arguments&, const Configuration&, const GLConfiguration&), - * @ref Sdl2Application::create(const Configuration&, const GLConfiguration&) or - * @ref Sdl2Application::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::Sdl2Application(const Arguments&, const Configuration&, const GLConfiguration&), - * @ref Sdl2Application::create(const Configuration&, const GLConfiguration&) - * or @ref Sdl2Application::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 }; @@ -2056,6 +2055,142 @@ class Sdl2ApplicationWindow::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 @@ -3032,7 +3167,6 @@ typedef BasicScreenedApplication ScreenedApplication; #endif #endif -CORRADE_ENUMSET_OPERATORS(Sdl2ApplicationWindow::Configuration::WindowFlags) CORRADE_ENUMSET_OPERATORS(Sdl2ApplicationWindow::InputEvent::Modifiers) CORRADE_ENUMSET_OPERATORS(Sdl2ApplicationWindow::MouseMoveEvent::Buttons) From 17c74b23d78ec87dd62b44fef59c3601e03b17ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 29 Apr 2023 19:55:18 +0200 Subject: [PATCH 3/3] [wip] everything is terrible TODO: some of this (the configuration DPI scaling/policy) should go to the previos commit i guess? --- src/Magnum/Platform/Sdl2Application.cpp | 237 ++++++++++++------ src/Magnum/Platform/Sdl2Application.h | 48 +++- .../Platform/Test/Sdl2ApplicationTest.cpp | 112 ++++++++- 3 files changed, 315 insertions(+), 82 deletions(-) diff --git a/src/Magnum/Platform/Sdl2Application.cpp b/src/Magnum/Platform/Sdl2Application.cpp index 186828f59..3c38f8017 100644 --- a/src/Magnum/Platform/Sdl2Application.cpp +++ b/src/Magnum/Platform/Sdl2Application.cpp @@ -42,6 +42,7 @@ #include #include #endif +#include #include #include @@ -88,14 +89,80 @@ enum class Sdl2ApplicationWindow::WindowFlag: UnsignedByte { Sdl2ApplicationWindow::Sdl2ApplicationWindow(Sdl2Application& application, NoCreateT): _application{application}, _windowFlags{WindowFlag::Redraw} {} +#ifndef CORRADE_TARGET_EMSCRIPTEN +Sdl2ApplicationWindow::Sdl2ApplicationWindow(Sdl2Application& application, const Configuration& configuration): Sdl2ApplicationWindow{application, NoCreate} { + if(!tryCreateWindow(configuration)) std::exit(1); +} + +Sdl2ApplicationWindow::Sdl2ApplicationWindow(Sdl2Application& application): Sdl2ApplicationWindow{application, Configuration{}} {} +#endif + Sdl2ApplicationWindow::~Sdl2ApplicationWindow() { - /* SDL_DestroyWindow(_window) crashes on windows when _window is nullptr - (it doesn't seem to crash on Linux) */ #ifndef CORRADE_TARGET_EMSCRIPTEN - if(_window) SDL_DestroyWindow(_window); + /* 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 } +bool Sdl2ApplicationWindow::tryCreateWindow(const Configuration& configuration) { + CORRADE_ASSERT(!_window, + "Platform::Sdl2ApplicationWindow::tryCreateWindow(): window already created", false); + + /* 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; + } + + /* 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; + + return true; +} + +#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); + + /* 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 Sdl2ApplicationWindow::dpiScaling(const Configuration& configuration) { /* Print a helpful warning in case some extra steps are needed for HiDPI support */ @@ -568,27 +635,18 @@ bool Sdl2Application::tryCreate(const Configuration& configuration) { 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())))) - { - 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 @@ -620,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) { @@ -646,6 +704,11 @@ bool Sdl2Application::tryCreate(const Configuration& configuration, const GLConf 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()); @@ -665,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(); @@ -732,25 +789,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 @@ -791,7 +841,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); @@ -804,14 +855,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); @@ -821,7 +866,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; } @@ -867,7 +912,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) { @@ -887,7 +932,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); @@ -946,10 +991,20 @@ bool Sdl2Application::setSwapInterval(const Int interval) { } 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 @@ -968,14 +1023,6 @@ Sdl2Application::~Sdl2Application() { SDL_FreeCursor(cursor); #endif - /* The window would be destroyed in the base ~Sdl2ApplicationWindow(), but - that's too late. Do it before calling SDL_Quit() and then reset the - window pointer so it's not called again after. */ - if(_window) { - SDL_DestroyWindow(_window); - _window = nullptr; - } - SDL_Quit(); } @@ -1000,6 +1047,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 */ @@ -1045,6 +1114,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 @@ -1066,15 +1136,18 @@ bool Sdl2Application::mainLoopIteration() { #endif dpiScaling()}; /** @todo handle also WM_DPICHANGED events when a window is moved between displays with different DPI */ - viewportEvent(e); - _windowFlags |= WindowFlag::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: - _windowFlags |= WindowFlag::Redraw; + _windows[event.window.windowID]->_windowFlags |= WindowFlag::Redraw; if(!(_flags & Flag::NoAnyEvent)) anyEvent(event); break; default: @@ -1084,7 +1157,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: @@ -1094,34 +1169,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: { @@ -1147,11 +1225,18 @@ bool Sdl2Application::mainLoopIteration() { /* Tick event */ if(!(_flags & Flag::NoTickEvent)) tickEvent(); - /* Draw event */ - if(_windowFlags & WindowFlag::Redraw) { - _windowFlags &= ~WindowFlag::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) { diff --git a/src/Magnum/Platform/Sdl2Application.h b/src/Magnum/Platform/Sdl2Application.h index 2c4cebec6..cc7b681dc 100644 --- a/src/Magnum/Platform/Sdl2Application.h +++ b/src/Magnum/Platform/Sdl2Application.h @@ -118,6 +118,19 @@ class Sdl2ApplicationWindow { class TextInputEvent; class TextEditingEvent; + /** + * @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 + /** @brief Copying is not allowed */ Sdl2ApplicationWindow(const Sdl2ApplicationWindow&) = delete; @@ -135,6 +148,10 @@ class Sdl2ApplicationWindow { virtual */ virtual ~Sdl2ApplicationWindow(); + /** @brief Application instance */ + Sdl2Application& application() { return _application; } + const Sdl2Application& application() const { return _application; } /**< @overload */ + #ifndef CORRADE_TARGET_EMSCRIPTEN /** * @brief Underlying window handle @@ -506,11 +523,21 @@ class Sdl2ApplicationWindow { explicit Sdl2ApplicationWindow(Sdl2Application& application, NoCreateT); Vector2 dpiScalingInternal(Implementation::Sdl2DpiScalingPolicy configurationDpiScalingPolicy, const Vector2& configurationDpiScaling) const; + #ifndef CORRADE_TARGET_EMSCRIPTEN + bool tryCreateWindow(const Configuration& configuration); + void destroyWindow(); + #endif Sdl2Application& _application; + /* These are saved from configuration to be reused in dpiScaling() and + viewportEvent() later */ + Implementation::Sdl2DpiScalingPolicy _configurationDpiScalingPolicy{}; + Vector2 _configurationDpiScaling; + #ifndef CORRADE_TARGET_EMSCRIPTEN SDL_Window* _window{}; + Vector2i _viewportSize; #else SDL_Surface* _surface{}; Vector2i _lastKnownCanvasSize; @@ -1327,18 +1354,27 @@ class Sdl2Application: public Sdl2ApplicationWindow { typedef Containers::EnumSet Flags; CORRADE_ENUMSET_FRIEND_OPERATORS(Flags) + #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]{}; #else 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 UnsignedInt _minimalLoopPeriod; #endif @@ -1346,12 +1382,16 @@ class Sdl2Application: public Sdl2ApplicationWindow { #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; diff --git a/src/Magnum/Platform/Test/Sdl2ApplicationTest.cpp b/src/Magnum/Platform/Test/Sdl2ApplicationTest.cpp index cf38df483..8076aca19 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" @@ -54,6 +56,106 @@ namespace Magnum { namespace Platform { namespace Test { namespace { 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); @@ -118,6 +220,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"; @@ -157,10 +263,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} {