#ifndef Magnum_Platform_EmscriptenApplication_h #define Magnum_Platform_EmscriptenApplication_h /* This file is part of Magnum. Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 Vladimír Vondruš Copyright © 2018, 2019 Jonathan Hale Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #if defined(CORRADE_TARGET_EMSCRIPTEN) || defined(DOXYGEN_GENERATING_OUTPUT) /** @file * @brief Class @ref Magnum::Platform::EmscriptenApplication, macro @ref MAGNUM_EMSCRIPTENAPPLICATION_MAIN() */ #endif #include #include #include #include "Magnum/Magnum.h" #include "Magnum/Tags.h" #include "Magnum/GL/GL.h" #include "Magnum/Math/Vector4.h" #include "Magnum/Platform/Platform.h" #if defined(CORRADE_TARGET_EMSCRIPTEN) || defined(DOXYGEN_GENERATING_OUTPUT) #ifndef DOXYGEN_GENERATING_OUTPUT struct EmscriptenKeyboardEvent; struct EmscriptenMouseEvent; struct EmscriptenWheelEvent; #endif namespace Magnum { namespace Platform { /** @nosubgrouping @brief Emscripten application Application running on Emscripten. This application library is available only on @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten", see respective sections in the @ref building-corrade-cross-emscripten "Corrade" and @ref building-cross-emscripten "Magnum" building documentation. It is built if `WITH_EMSCRIPTENAPPLICATION` is enabled when building Magnum. @section Platform-EmscriptenApplication-bootstrap Bootstrap application Fully contained base application using @ref Sdl2Application for desktop build and @ref EmscriptenApplication for Emscripten build along with full HTML markup and CMake setup is available in `base-emscripten` branch of the [Magnum Bootstrap](https://github.com/mosra/magnum-bootstrap) repository, download it as [tar.gz](https://github.com/mosra/magnum-bootstrap/archive/base-emscripten.tar.gz) or [zip](https://github.com/mosra/magnum-bootstrap/archive/base-emscripten.zip) file. After extracting the downloaded archive, you can do the desktop build in the same way as with @ref Sdl2Application. For the Emscripten build you also need to put the contents of toolchains repository from https://github.com/mosra/toolchains in a `toolchains/` subdirectory. There are two toolchain files. The `generic/Emscripten.cmake` is for the classical (asm.js) build, the `generic/Emscripten-wasm.cmake` is for WebAssembly build. Don't forget to adapt `EMSCRIPTEN_PREFIX` variable in `toolchains/generic/Emscripten*.cmake` to path where Emscripten is installed; you can also pass it explicitly on command-line using `-DEMSCRIPTEN_PREFIX`. Default is `/usr/emscripten`. Then create build directory and run `cmake` and build/install commands in it. Set `CMAKE_PREFIX_PATH` to where you have all the dependencies installed, set `CMAKE_INSTALL_PREFIX` to have the files installed in proper location (a webserver, e.g. `/srv/http/emscripten`). @code{.sh} mkdir build-emscripten && cd build-emscripten cmake .. \ -DCMAKE_TOOLCHAIN_FILE="../toolchains/generic/Emscripten.cmake" \ -DCMAKE_PREFIX_PATH=/usr/lib/emscripten/system \ -DCMAKE_INSTALL_PREFIX=/srv/http/emscripten cmake --build . cmake --build . --target install @endcode You can then open `MyApplication.html` in your browser (through a webserver, e.g. http://localhost/emscripten/MyApplication.html). Detailed information about deployment for Emscripten and all needed boilerplate together with a troubleshooting guide is available in @ref platforms-html5. @section Platform-EmscriptenApplication-usage General usage Request the `EmscriptenApplication` component of the `Magnum` package and link to the `Magnum::EmscriptenApplication` target: @code{.cmake} find_package(Magnum REQUIRED) if(CORRADE_TARGET_EMSCRIPTEN) find_package(Magnum REQUIRED EmscriptenApplication) endif() # ... if(CORRADE_TARGET_EMSCRIPTEN) target_link_libraries(your-app Magnum::EmscriptenApplication) endif() @endcode If no other application is requested, you can also use the generic `Magnum::Application` alias to simplify porting. See @ref building and @ref cmake for more information. In C++ code you need to implement at least @ref drawEvent() to be able to draw on the screen. @code{.cpp} class MyApplication: public Platform::EmscriptenApplication { // implement required methods... }; MAGNUM_EMSCRIPTENAPPLICATION_MAIN(MyApplication) @endcode If no other application header is included, this class is also aliased to @cpp Platform::Application @ce and the macro is aliased to @cpp MAGNUM_APPLICATION_MAIN() @ce to simplify porting. @section Platform-EmscriptenApplication-browser Browser-specific behavior Leaving a default (zero) size in @ref Configuration will cause the app to use a 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-EmscriptenApplication-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. Unlike desktop platforms, the browser has no concept of application exit code, so the return value of @ref exec() is always @cpp 0 @ce and whatever is passed to @ref exit(int) is ignored. @section Platform-EmscriptenApplication-webgl WebGL-specific behavior While WebGL itself requires all extensions to be [enabled explicitly](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Using_Extensions), by default Emscripten enables all supported extensions that don't have a negative effect on performance to simplify porting. This is controlled by @ref GLConfiguration::Flag::EnableExtensionsByDefault and the flag is enabled by default. When disabled, you are expected to enable desired extensions manually using @m_class{m-doc-external} [emscripten_webgl_enable_extension()](https://emscripten.org/docs/api_reference/html5.h.html#c.emscripten_webgl_enable_extension). @attention Because @ref GLConfiguration::Flag::EnableExtensionsByDefault is among default flags, calling @ref GLConfiguration::setFlags() will reset this default, causing crashes at runtime when extension functionality is used. To be safe, you might want to use @ref GLConfiguration::addFlags() and @ref GLConfiguration::clearFlags() instead. @section Platform-EmscriptenApplication-dpi DPI awareness Since this application targets only web browsers, DPI handling isn't as general as in case of @ref Sdl2Application or @ref GlfwApplication. See @ref Platform-Sdl2Application-dpi "Sdl2Application DPI awareness" documentation for a guide covering all platform differences. For this application in particular, @ref windowSize() can be different than @ref framebufferSize() on HiDPI displays --- which is different from @ref Sdl2Application behavior on Emscripten. By default, @ref dpiScaling() is @cpp 1.0f @ce in both dimensions but it can be overriden using custom DPI scaling --- the `--magnum-dpi-scaling` command-line options are supported the same way as in @ref Sdl2Application, only in the form of URL GET parameters, similarly to all other @ref platforms-html5-environment "command-line options". Having @ref dpiScaling() set to @cpp 1.0f @ce is done in order to have consistent behavior with other platforms --- platforms have either @ref windowSize() equivalent to @ref framebufferSize() and then @ref dpiScaling() specifies the UI scale (Windows/Linux/Android-like) or @ref windowSize() different from @ref framebufferSize() (which defines the UI scale) and then @ref dpiScaling() is @cpp 1.0f @ce (macOS/iOS-like), so this is the second case. The actual device pixel ratio is expressed in the ratio of @ref windowSize() and @ref framebufferSize() so crossplatform code shouldn't have a need to query it, however for completeness it's exposed in @ref devicePixelRatio() and @ref ViewportEvent::devicePixelRatio(). Setting custom DPI scaling will affect @ref framebufferSize() (larger values making the canvas backing framebuffer larger and vice versa), @ref windowSize() will stay unaffected as it's controlled by the CSS, and @ref devicePixelRatio() will stay the same as well as it's defined by the browser. To avoid confusion, documentation of all @ref EmscriptenApplication APIs always mentions only the web case, consult equivalent APIs in @ref Sdl2Application or @ref GlfwApplication for behavior in those implementations. */ class EmscriptenApplication { public: /** @brief Application arguments */ struct Arguments { /** @brief Constructor */ /*implicit*/ constexpr Arguments(int& argc, char** argv) noexcept: argc{argc}, argv{argv} {} int& argc; /**< @brief Argument count */ char** argv; /**< @brief Argument values */ }; class Configuration; class GLConfiguration; class ViewportEvent; class InputEvent; class MouseEvent; class MouseMoveEvent; class MouseScrollEvent; class KeyEvent; class TextInputEvent; /** * @brief Execute main loop * * Calls @ref mainLoopIteration() in a loop until @ref exit() is * called. See @ref MAGNUM_EMSCRIPTENAPPLICATION_MAIN() for usage * information. */ void exec(); /** * @brief Exit application main loop * @param exitCode Exit code for compatibility with other application * implementations * * Stops main loop started by @ref exec(). */ void exit(int exitCode = 0); /** * @brief Run one iteration of application main loop * * Called internally from @ref exec(). If you want to have better * control over how the main loop behaves, you can call this function * yourself from your own `main()` function instead of it being called * automatically from @ref exec() / @ref MAGNUM_EMSCRIPTENAPPLICATION_MAIN(). */ void mainLoopIteration(); #ifdef MAGNUM_TARGET_GL /** * @brief Construct with given configuration for WebGL context * @param arguments Application arguments * @param configuration Application configuration * @param glConfiguration WebGL context configuration * * Creates application with default or user-specified configuration. * See @ref Configuration for more information. The program exits if * the context cannot be created, see @ref tryCreate() for an * alternative. * * @note This function is available only if Magnum is compiled with * @ref MAGNUM_TARGET_GL enabled (done by default). See * @ref building-features for more information. */ explicit EmscriptenApplication(const Arguments& arguments, const Configuration& configuration, const GLConfiguration& glConfiguration); #endif /** * @brief Construct with given configuration * * If @ref Configuration::WindowFlag::Contextless is present or Magnum * was not built with @ref MAGNUM_TARGET_GL, this creates a window * without any GPU context attached, leaving that part on the user. * * If none of the flags is present and Magnum was built with * @ref MAGNUM_TARGET_GL, this is equivalent to calling * @ref EmscriptenApplication(const Arguments&, const Configuration&, const GLConfiguration&) * with default-constructed @ref GLConfiguration. * * See also @ref building-features for more information. */ explicit EmscriptenApplication(const Arguments& arguments, const Configuration& configuration); /** * @brief Construct with default configuration * * Equivalent to calling @ref EmscriptenApplication(const Arguments&, const Configuration&) * with default-constructed @ref Configuration. */ explicit EmscriptenApplication(const Arguments& arguments); /** * @brief Construct without setting up a canvas * @param arguments Application arguments * * Unlike above, the canvas is not set up and must be created later * with @ref create() or @ref tryCreate(). */ explicit EmscriptenApplication(const Arguments& arguments, NoCreateT); /** @brief Copying is not allowed */ EmscriptenApplication(const EmscriptenApplication&) = delete; /** @brief Moving is not allowed */ EmscriptenApplication(EmscriptenApplication&&) = delete; /** @brief Copying is not allowed */ EmscriptenApplication& operator=(const EmscriptenApplication&) = delete; /** @brief Moving is not allowed */ EmscriptenApplication& operator=(EmscriptenApplication&&) = delete; protected: /* Nobody will need to have (and delete) EmscriptenApplication*, thus this is faster than public pure virtual destructor */ ~EmscriptenApplication(); #ifdef MAGNUM_TARGET_GL /** * @brief Set up a canvas with given configuration for WebGL context * @param configuration Application configuration * @param glConfiguration WebGL context configuration * * Must be called only if the context wasn't created by the constructor * itself, i.e. when passing @ref NoCreate to it. Error message is * printed and the program exits if the context cannot be created, see * @ref tryCreate() for an alternative. * * @note This function is available only if Magnum is compiled with * @ref MAGNUM_TARGET_GL enabled (done by default). See * @ref building-features for more information. */ void create(const Configuration& configuration, const GLConfiguration& glConfiguration); #endif /** * @brief Set up a canvas with given configuration and WebGL context * * Equivalent to calling @ref create(const Configuration&, const GLConfiguration&) * with default-constructed @ref GLConfiguration. */ void create(const Configuration& configuration); /** * @brief Set up a canvas with default configuration and WebGL 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 WebGL 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 Canvas size * * Canvas size to which all input event coordinates can be related. * On HiDPI displays, canvas size can be different from * @ref framebufferSize(). See @ref Platform-Sdl2Application-dpi for * more information. Note that this method is named "window size" to be * API-compatible with Application implementations on other platforms. */ Vector2i windowSize() const; #if defined(MAGNUM_TARGET_GL) || defined(DOXYGEN_GENERATING_OUTPUT) /** * @brief Framebuffer size * * On HiDPI displays, framebuffer size can be different from * @ref windowSize(). See @ref Platform-EmscriptenApplication-dpi for * more information. * * @note This function is available only if Magnum is compiled with * @ref MAGNUM_TARGET_GL enabled (done by default). See * @ref building-features for more information. * * @see @ref Sdl2Application::framebufferSize() */ Vector2i framebufferSize() const; #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&) const for * calculating a value depending on user configuration. By default set * to @cpp 1.0 @ce, see @ref Platform-EmscriptenApplication-dpi for * more information. * @see @ref framebufferSize(), @ref devicePixelRatio() */ Vector2 dpiScaling() const { return _dpiScaling; } /** * @brief DPI scaling for given configuration * * Calculates DPI scaling that would be used when creating a window * with given @p configuration. Takes into account DPI scaling policy * and custom scaling specified via URL GET parameters. See * @ref Platform-EmscriptenApplication-dpi for more information. * @ref devicePixelRatio() */ Vector2 dpiScaling(const Configuration& configuration) const; /** * @brief Device pixel ratio * * Crossplatform code shouldn't need to query this value because the * pixel ratio is already expressed in the ratio of @ref windowSize() * and @ref framebufferSize() values. * @see @ref dpiScaling() */ Vector2 devicePixelRatio() const { return _devicePixelRatio; } protected: /** * @brief Swap buffers * * Paints currently rendered framebuffer on screen. */ void swapBuffers(); /** @copydoc Sdl2Application::redraw() */ void redraw() { _flags |= Flag::Redraw; } private: /** @copydoc GlfwApplication::viewportEvent(ViewportEvent&) */ virtual void viewportEvent(ViewportEvent& event); /** @copydoc Sdl2Application::drawEvent() */ virtual void drawEvent() = 0; /*@}*/ /** @{ @name Keyboard handling */ /** @copydoc Sdl2Application::keyPressEvent() */ virtual void keyPressEvent(KeyEvent& event); /** @copydoc Sdl2Application::keyReleaseEvent() */ virtual void keyReleaseEvent(KeyEvent& event); /*@}*/ /** @{ @name Mouse handling */ 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); /*@}*/ /** @{ @name Text input handling */ public: /** * @brief Whether text input is active * * If text input is active, text input events go to @ref textInputEvent(). * @note Note that in @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten" the * value is emulated and might not reflect external events like * closing on-screen keyboard. * @see @ref startTextInput(), @ref stopTextInput() */ bool isTextInputActive() const { return !!(_flags & Flag::TextInputActive); } /** * @brief Start text input * * Starts text input that will go to @ref textInputEvent(). * @see @ref stopTextInput(), @ref isTextInputActive(), * @ref setTextInputRect() */ void startTextInput(); /** * @brief Stop text input * * Stops text input that went to @ref textInputEvent(). * @see @ref startTextInput(), @ref isTextInputActive(), * @ref textInputEvent() */ void stopTextInput(); /** * @brief Set text input rectangle * * The @p rect defines an area where the text is being displayed, for * example to hint the system where to place on-screen keyboard. * @note Currently not implemented, included only for compatibility with * other Application implementations. */ 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); /*@}*/ private: enum class Flag: UnsignedByte { Redraw = 1 << 0, TextInputActive = 1 << 1 }; typedef Containers::EnumSet Flags; CORRADE_ENUMSET_FRIEND_OPERATORS(Flags) /* Sorry, but can't use Configuration::WindowFlags here :( */ void setupCallbacks(bool resizable); Vector2 _devicePixelRatio, _dpiScaling; Vector2i _lastKnownCanvasSize; Flags _flags; #ifdef MAGNUM_TARGET_GL Containers::Pointer _context; #endif /* These are saved from command-line arguments */ bool _verboseLog{}; Vector2 _commandLineDpiScaling; }; CORRADE_ENUMSET_OPERATORS(EmscriptenApplication::Flags) #ifdef MAGNUM_TARGET_GL /** @brief WebGL context configuration The created context is always with a double-buffered OpenGL context. @note This function is available only if Magnum is compiled with @ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features for more information. @see @ref EmscriptenApplication(), @ref Configuration, @ref create(), @ref tryCreate() */ class EmscriptenApplication::GLConfiguration { public: /** * @brief Context flag * * @see @ref Flags, @ref setFlags(), @ref GL::Context::Flag */ enum class Flag: Int { /** * Premultiplied alpha. If set, the alpha channel of the rendering * context will be treated as representing premultiplied alpha * values. If not set, the alpha channel represents * non-premultiplied alpha. */ PremultipliedAlpha = 1 << 0, /** * Preserve drawing buffer. If set, the contents of the drawing * buffer are preserved between consecutive @ref drawEvent() calls. * If not, color, depth and stencil are cleared before entering * @ref drawEvent(). Not setting this gives better performance. */ PreserveDrawingBuffer = 1 << 1, /** * Prefer low power to high performance. If set, the WebGL power * preference will be set to reduce power consumption. */ PreferLowPowerToHighPerformance = 1 << 2, /** * Fail if major performance caveat. If set, requests context * creation to abort if the browser is only able to create a * context that does not give good hardware-accelerated * performance. */ FailIfMajorPerformanceCaveat = 1 << 3, /** * Explicit swap control. For more details, see the * [Emscripten API reference](https://emscripten.org/docs/api_reference/html5.h.html#c.EmscriptenWebGLContextAttributes.explicitSwapControl). */ ExplicitSwapControl = 1 << 4, /** * Enable WebGL extensions by default. Enabled by default. For more * details, see @ref Platform-EmscriptenApplication-webgl and the * [Emscripten API reference](https://emscripten.org/docs/api_reference/html5.h.html#c.EmscriptenWebGLContextAttributes.enableExtensionsByDefault). */ EnableExtensionsByDefault = 1 << 5, /** * Render via offscreen back buffer. For more details, see the * [Emscripten API reference](https://emscripten.org/docs/api_reference/html5.h.html#c.EmscriptenWebGLContextAttributes.renderViaOffscreenBackBuffer). */ RenderViaOffscreenBackBuffer = 1 << 6, /** * Proxy content to main thread. For more details, see the * [Emscripten API reference](https://emscripten.org/docs/api_reference/html5.h.html#c.EmscriptenWebGLContextAttributes.proxyContextToMainThread). */ ProxyContextToMainThread = 1 << 7 }; /** * @brief Context flags * * @see @ref setFlags(), @ref GL::Context::Flags */ typedef Containers::EnumSet Flags; /*implicit*/ GLConfiguration(); /** @brief Context flags */ Flags flags() const { return _flags; } /** * @brief Set context flags * @return Reference to self (for method chaining) * * Default is @ref Flag::EnableExtensionsByDefault. * @see @ref addFlags(), @ref clearFlags(), @ref GL::Context::flags() */ GLConfiguration& setFlags(Flags flags) { _flags = flags; return *this; } /** * @brief Add context flags * @return Reference to self (for method chaining) * * Unlike @ref setFlags(), ORs the flags with existing instead of * replacing them. Useful for preserving the defaults. * @see @ref clearFlags() */ GLConfiguration& addFlags(Flags flags) { _flags |= flags; return *this; } /** * @brief Clear context flags * @return Reference to self (for method chaining) * * Unlike @ref setFlags(), ANDs the inverse of @p flags with existing * instead of replacing them. Useful for removing default flags. * @see @ref addFlags() */ GLConfiguration& clearFlags(Flags flags) { _flags &= ~flags; return *this; } /** * @brief Set context version * * @note This function does nothing and is included only for * compatibility with other toolkits. @ref GL::Version::GLES200 or * @ref GL::Version::GLES300 is used based on engine compile-time * settings. */ GLConfiguration& setVersion(GL::Version) { return *this; } /** @brief Color buffer size */ Vector4i colorBufferSize() const { return _colorBufferSize; } /** * @brief Set color buffer size * * Default is @cpp {8, 8, 8, 0} @ce (8-bit-per-channel RGB, no alpha). * @see @ref setDepthBufferSize(), @ref setStencilBufferSize() */ GLConfiguration& setColorBufferSize(const Vector4i& size) { _colorBufferSize = size; return *this; } /** @brief Depth buffer size */ Int depthBufferSize() const { return _depthBufferSize; } /** * @brief Set depth buffer size * * Default is @cpp 24 @ce bits. * @see @ref setColorBufferSize(), @ref setStencilBufferSize() */ GLConfiguration& setDepthBufferSize(Int size) { _depthBufferSize = size; return *this; } /** @brief Stencil buffer size */ Int stencilBufferSize() const { return _stencilBufferSize; } /** * @brief Set stencil buffer size * * Default is @cpp 0 @ce bits (i.e., no stencil buffer). * @see @ref setColorBufferSize(), @ref setDepthBufferSize() */ GLConfiguration& setStencilBufferSize(Int size) { _stencilBufferSize = size; return *this; } /** @brief Sample count */ Int sampleCount() const { return _sampleCount; } /** * @brief Set sample count * @return Reference to self (for method chaining) * * Default is @cpp 0 @ce, thus no multisampling. See also * @ref GL::Renderer::Feature::Multisampling. * Note that WebGL does not allow setting the sample count, but merely * enabling or disabling multisampling. Multisampling will be enabled * if sample count is greater than @cpp 0 @ce. */ GLConfiguration& setSampleCount(Int count) { _sampleCount = count; return *this; } private: Vector4i _colorBufferSize; Int _depthBufferSize, _stencilBufferSize; Int _sampleCount; Flags _flags{Flag::EnableExtensionsByDefault}; }; CORRADE_ENUMSET_OPERATORS(EmscriptenApplication::GLConfiguration::Flags) #endif /** @brief Configuration @see @ref EmscriptenApplication(), @ref GLConfiguration, @ref create(), @ref tryCreate() */ class EmscriptenApplication::Configuration { public: /** * @brief Window flag * * @see @ref WindowFlags, @ref setWindowFlags() */ enum class WindowFlag: UnsignedShort { /** * Do not create any GPU context. Use together with * @ref EmscriptenApplication(const Arguments&), * @ref EmscriptenApplication(const Arguments&, const Configuration&), * @ref create(const Configuration&) or * @ref tryCreate(const Configuration&) to prevent implicit * creation of an WebGL context. */ Contextless = 1 << 0, /** * Resizable canvas. This causes the framebuffer to be resized * when the @cb{.html} @ce size changes, either directly * or as a consequence of browser window size change. * * Implement @ref viewportEvent() to react to the resizing events. */ Resizable = 1 << 1 }; /** * @brief Window flags * * @see @ref setWindowFlags() */ typedef Containers::EnumSet WindowFlags; constexpr /*implicit*/ Configuration() {} /** * @brief Set window title * @return Reference to self (for method chaining) * * @note This function does nothing and is included only for * compatibility with other toolkits. You need to set the title * separately in the HTML markup. */ template Configuration& setTitle(const T&) { return *this; } /** @brief Window size */ Vector2i size() const { return _size; } /** * @brief Set canvas size * @param size Desired canvas size * @param dpiScaling Custom DPI scaling value * * Default is a zero vector, meaning a value that matches the display * or canvas size is autodetected. See * @ref Platform-EmscriptenApplication-dpi for more information. When * @p dpiScaling is not a zero vector, this function sets the DPI * scaling directly. The resulting @ref windowSize() is * @cpp size*dpiScaling @ce and @ref dpiScaling() is @p dpiScaling. */ Configuration& setSize(const Vector2i& size, const Vector2& dpiScaling = {}) { _size = size; _dpiScaling = dpiScaling; return *this; } /** * @brief Custom DPI scaling * * If zero, the devices pixel ratio has a priority over this value. * The `--magnum-dpi-scaling` option (specified via URL GET parameters) * has a priority over any application-set value. * @see @ref setSize(const Vector2i&, const Vector2&) */ Vector2 dpiScaling() const { return _dpiScaling; } /** @brief Window flags */ WindowFlags windowFlags() const { return _windowFlags; } /** * @brief Set window flags * @return Reference to self (for method chaining) */ Configuration& setWindowFlags(WindowFlags windowFlags) { _windowFlags = windowFlags; return *this; } private: Vector2i _size; Vector2 _dpiScaling; WindowFlags _windowFlags; }; CORRADE_ENUMSET_OPERATORS(EmscriptenApplication::Configuration::WindowFlags) /** @brief Viewport event @see @ref viewportEvent() */ class EmscriptenApplication::ViewportEvent { public: /** @brief Copying is not allowed */ ViewportEvent(const ViewportEvent&) = delete; /** @brief Moving is not allowed */ ViewportEvent(ViewportEvent&&) = delete; /** @brief Copying is not allowed */ ViewportEvent& operator=(const ViewportEvent&) = delete; /** @brief Moving is not allowed */ ViewportEvent& operator=(ViewportEvent&&) = delete; /** * @brief Canvas size * * On HiDPI displays, window size can be different from * @ref framebufferSize(). See @ref Platform-EmscriptenApplication-dpi * for more information. Note that this method is named "window size" * to be API-compatible with Application implementations on other * platforms. * @see @ref EmscriptenApplication::windowSize() */ Vector2i windowSize() const { return _windowSize; } #if defined(MAGNUM_TARGET_GL) || defined(DOXYGEN_GENERATING_OUTPUT) /** * @brief Framebuffer size * * On HiDPI displays, framebuffer size can be different from * @ref windowSize(). See @ref Platform-EmscriptenApplication-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 EmscriptenApplication::framebufferSize() */ Vector2i framebufferSize() const { return _framebufferSize; } #endif /** * @brief DPI scaling * * On some platforms moving a browser window between displays can * result in DPI scaling value being changed in tandem with a * canvas/framebuffer size. Simply resizing the canvas doesn't change * the DPI scaling value. See @ref Platform-EmscriptenApplication-dpi * for more information. * @see @ref EmscriptenApplication::dpiScaling() */ Vector2 dpiScaling() const { return _dpiScaling; } /** * @brief Device pixel ratio * * On some platforms moving a browser window between displays can * result in device pixel ratio value being changed. Crossplatform code * shouldn't need to query this value because the ratio is already * expressed in the ratio of @ref windowSize() and @ref framebufferSize() * values. See @ref Platform-EmscriptenApplication-dpi for more * information. * @see @ref EmscriptenApplication::devicePixelRatio() */ Vector2 devicePixelRatio() const { return _devicePixelRatio; } private: friend EmscriptenApplication; explicit ViewportEvent(const Vector2i& windowSize, #ifdef MAGNUM_TARGET_GL const Vector2i& framebufferSize, #endif const Vector2& dpiScaling, const Vector2& devicePixelRatio): _windowSize{windowSize}, #ifdef MAGNUM_TARGET_GL _framebufferSize{framebufferSize}, #endif _dpiScaling{dpiScaling}, _devicePixelRatio{devicePixelRatio} {} const Vector2i _windowSize; #ifdef MAGNUM_TARGET_GL const Vector2i _framebufferSize; #endif const Vector2 _dpiScaling, _devicePixelRatio; }; /** @brief Base for input events @see @ref KeyEvent, @ref MouseEvent, @ref MouseMoveEvent, @ref keyPressEvent(), @ref mousePressEvent(), @ref mouseReleaseEvent(), @ref mouseMoveEvent() */ class EmscriptenApplication::InputEvent { public: /** * @brief Modifier * * @see @ref Modifiers, @ref KeyEvent::modifiers(), * @ref MouseEvent::modifiers() */ enum class Modifier: Int { /** * Shift * * @see @ref KeyEvent::Key::LeftShift, @ref KeyEvent::Key::RightShift */ Shift = 1 << 0, /** * Ctrl * * @see @ref KeyEvent::Key::LeftCtrl, @ref KeyEvent::Key::RightCtrl */ Ctrl = 1 << 1, /** * Alt * * @see @ref KeyEvent::Key::LeftAlt, @ref KeyEvent::Key::RightAlt */ Alt = 1 << 2, /** * Super key (Windows/⌘) * * @see @ref KeyEvent::Key::LeftSuper, @ref KeyEvent::Key::RightSuper */ Super = 1 << 3 }; /** * @brief Set of modifiers * * @see @ref KeyEvent::modifiers(), @ref MouseEvent::modifiers(), * @ref MouseMoveEvent::modifiers() */ typedef Containers::EnumSet Modifiers; /** @brief Copying is not allowed */ InputEvent(const InputEvent&) = delete; /** @brief Moving is not allowed */ InputEvent(InputEvent&&) = delete; /** @brief Copying is not allowed */ InputEvent& operator=(const InputEvent&) = delete; /** @brief Moving is not allowed */ InputEvent& operator=(InputEvent&&) = delete; /** @brief Whether the event is accepted */ bool isAccepted() const { return _accepted; } /** * @brief Set event as accepted * * If the event is ignored (i.e., not set as accepted), it is * propagated to other elements on the page. By default each event is * ignored and thus propagated. */ void setAccepted(bool accepted = true) { _accepted = accepted; } protected: explicit InputEvent(): _accepted(false) {} ~InputEvent() = default; private: bool _accepted; }; CORRADE_ENUMSET_OPERATORS(EmscriptenApplication::InputEvent::Modifiers) /** @brief Mouse event @see @ref MouseMoveEvent, @ref mousePressEvent(), @ref mouseReleaseEvent() */ class EmscriptenApplication::MouseEvent: public EmscriptenApplication::InputEvent { public: /** * @brief Mouse button * * @see @ref button() */ enum class Button: std::int32_t { Left, /**< Left mouse button */ Middle, /**< Middle mouse button */ Right /**< Right mouse button */ }; /** @brief Button */ Button button() const; /** @brief Position */ Vector2i position() const; /** @brief Modifiers */ Modifiers modifiers() const; private: friend EmscriptenApplication; explicit MouseEvent(const EmscriptenMouseEvent* event): _event(event) {} const EmscriptenMouseEvent* const _event; }; /** @brief Mouse move event @see @ref MouseEvent, @ref mouseMoveEvent() */ class EmscriptenApplication::MouseMoveEvent: public EmscriptenApplication::InputEvent { public: /** * @brief Mouse button * * @see @ref buttons() */ enum class Button: Int { /** Left mouse button */ Left = 1 << 0, /** Middle mouse button */ Middle = 1 << 1, /** Right mouse button */ Right = 1 << 2 }; /** * @brief Set of mouse buttons * * @see @ref buttons() */ typedef Containers::EnumSet