Browse Source

Platform: Add initial EmscriptenApplication

Signed-off-by: Squareys <squareys@googlemail.com>
pull/300/head
Squareys 8 years ago committed by Vladimír Vondruš
parent
commit
b970b4d247
  1. 4
      CMakeLists.txt
  2. 19
      doc/platforms-html5.dox
  3. 14
      modules/FindMagnum.cmake
  4. 1
      package/ci/travis-emscripten.sh
  5. 46
      src/Magnum/Platform/CMakeLists.txt
  6. 615
      src/Magnum/Platform/EmscriptenApplication.cpp
  7. 1372
      src/Magnum/Platform/EmscriptenApplication.h
  8. 33
      src/Magnum/Platform/Test/CMakeLists.txt
  9. 86
      src/Magnum/Platform/Test/EmscriptenApplicationTest.cpp
  10. 28
      src/Magnum/Platform/Test/EmscriptenApplicationTest.html

4
CMakeLists.txt

@ -122,6 +122,10 @@ option(WITH_EGLCONTEXT "Build EglContext library" OFF)
if(CORRADE_TARGET_ANDROID)
option(WITH_ANDROIDAPPLICATION "Build AndroidApplication library" OFF)
# Emscripten-specific application libraries
elseif(CORRADE_TARGET_EMSCRIPTEN)
option(WITH_EMSCRIPTENAPPLICATION "Build EmscriptenApplication library" OFF)
# iOS-specific application libraries
elseif(CORRADE_TARGET_IOS)
option(WITH_WINDOWLESSIOSAPPLICATION "Build WindowlessIosApplication library" OFF)

19
doc/platforms-html5.dox

@ -97,14 +97,14 @@ In case you don't have an OpenGL ES build set up yet, you need to copy
Magnum source to the `modules/` dir in your project so it is able to find the
WebGL libraries.
Magnum provides an Emscripten application wrapper in
@ref Platform::Sdl2Application. See its documentation for more information
Magnum provides Emscripten application wrappers in @ref Platform::Sdl2Application
and @ref Platform::EmscriptenApplication. See their documentation for more information
about general usage. You can also use the Emscripten APIs directly or any other
way.
@note The @ref Platform::Sdl2Application also contains a fully configured
bootstrap project that's ready to build and deploy. Check its documentation
for details.
@note @ref Platform::Sdl2Application and @ref Platform::EmscriptenApplication
also contains a fully configured bootstrap projects that are ready to build
and deploy. Check their documentation for details.
To target the web browser, you need to provide a HTML markup for your
application. Template one is below. The markup references two files,
@ -350,8 +350,11 @@ Module.doNotCaptureKeyboard = true;
</script>
@endcode
The above is implicitly set for windowless apps, because these don't have any
event loop.
The above is implicitly set for @ref Platform::WindowlessEglApplicaiton, because
it does not have an event loop.
@cb{.js} Module.doNotCaptureKeyboard @ce is not supported by
@ref Platform::EmscriptenApplication.
Another solution is to specify the element on which it should capture keybard
using @cb{.js} Module.keyboardListeningElement @ce --- it requires the actual
@ -367,7 +370,7 @@ like this:
After that, the canvas can be focused with a @m_class{m-label m-default} **Tab**
key. But because Emscripten eats all mouse input, the `mousedown` event won't
be propagated to focus the canvas unlesss you do that manually:
be propagated to focus the canvas unless you do that manually:
@code{.js}
Module.keyboardListeningElement = Module.canvas;

14
modules/FindMagnum.cmake

@ -67,6 +67,8 @@
# TextureTools - TextureTools library
# Trade - Trade library
# Vk - Vk library
# AndroidApplication - Android application
# EmscriptenApplication - Emscripten application
# GlfwApplication - GLFW application
# GlxApplication - GLX application
# Sdl2Application - SDL2 application
@ -338,10 +340,10 @@ endif()
set(_MAGNUM_LIBRARY_COMPONENT_LIST
Audio DebugTools GL MeshTools Primitives SceneGraph Shaders Text
TextureTools Trade Vk
AndroidApplication GlfwApplication GlxApplication Sdl2Application
XEglApplication WindowlessCglApplication WindowlessEglApplication
WindowlessGlxApplication WindowlessIosApplication WindowlessWglApplication
WindowlessWindowsEglApplication
AndroidApplication EmscriptenApplication GlfwApplication GlxApplication
Sdl2Application XEglApplication WindowlessCglApplication
WindowlessEglApplication WindowlessGlxApplication WindowlessIosApplication
WindowlessWglApplication WindowlessWindowsEglApplication
CglContext EglContext GlxContext WglContext
OpenGLTester)
set(_MAGNUM_PLUGIN_COMPONENT_LIST
@ -410,6 +412,10 @@ endif()
set(_MAGNUM_Trade_DEPENDENCIES )
set(_MAGNUM_AndroidApplication_DEPENDENCIES GL)
set(_MAGNUM_EmscriptenApplication_DEPENDENCIES)
if(MAGNUM_TARGET_GL)
list(APPEND _MAGNUM_EmscriptenApplication_DEPENDENCIES GL)
endif()
set(_MAGNUM_GlfwApplication_DEPENDENCIES )
if(MAGNUM_TARGET_GL)

1
package/ci/travis-emscripten.sh

@ -50,6 +50,7 @@ cmake .. \
-DCMAKE_FIND_ROOT_PATH=$HOME/deps \
-DWITH_AUDIO=ON \
-DWITH_VK=OFF \
-DWITH_EMSCRIPTENAPPLICATION=ON \
-DWITH_SDL2APPLICATION=ON \
-DWITH_WINDOWLESSEGLAPPLICATION=ON \
-DWITH_ANYAUDIOIMPORTER=ON \

46
src/Magnum/Platform/CMakeLists.txt

@ -3,6 +3,7 @@
#
# Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019
# Vladimír Vondruš <mosra@centrum.cz>
# Copyright © 2018, 2019 Jonathan Hale <squareys@googlemail.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
@ -35,9 +36,9 @@ set(MagnumPlatform_HEADERS
set(MagnumPlatform_PRIVATE_HEADERS )
# DPI scaling queries only for Sdl2Application and GlfwApplication at the
# moment, build the files only then
if(WITH_GLFWAPPLICATION OR WITH_SDL2APPLICATION)
# DPI scaling queries only for EmscriptenApplication, Sdl2Application and
# GlfwApplication at the moment, build the files only then
if(WITH_EMSCRIPTENAPPLICATION OR WITH_GLFWAPPLICATION OR WITH_SDL2APPLICATION)
# List of libraries to link when using the MagnumPlatformObjects target
# TODO: use target_link_libraries() when we are on a CMake version that
# supports it (3.12?)
@ -170,6 +171,45 @@ if(WITH_ANDROIDAPPLICATION)
add_library(Magnum::AndroidApplication ALIAS MagnumAndroidApplication)
endif()
# Emscripten application
if(WITH_EMSCRIPTENAPPLICATION)
if(NOT CORRADE_TARGET_EMSCRIPTEN)
message(FATAL_ERROR "EmscriptenApplication is available only when targeting Emscripten. Set WITH_EMSCRIPTENAPPLICATION to OFF to skip building it.")
endif()
set(MagnumEmscriptenApplication_SRCS
$<TARGET_OBJECTS:MagnumPlatformObjects>
EmscriptenApplication.cpp)
set(MagnumEmscriptenApplication_HEADERS
EmscriptenApplication.h)
add_library(MagnumEmscriptenApplication STATIC
${MagnumEmscriptenApplication_SRCS}
${MagnumEmscriptenApplication_HEADERS}
${MagnumEmscriptenApplication_PRIVATE_HEADERS})
set_target_properties(MagnumEmscriptenApplication PROPERTIES
DEBUG_POSTFIX "-d"
FOLDER "Magnum/Platform")
# TODO: use MagnumPlatformObjects instead of ${MagnumPlatform_*} when
# CMake supports it
target_link_libraries(MagnumEmscriptenApplication PUBLIC Magnum
${MagnumPlatform_LINK_LIBRARIES})
target_compile_definitions(MagnumEmscriptenApplication PRIVATE
${MagnumPlatform_COMPILE_DEFINITIONS})
if(TARGET_GL)
target_link_libraries(MagnumEmscriptenApplication PUBLIC MagnumGL)
endif()
install(FILES ${MagnumEmscriptenApplication_HEADERS} DESTINATION ${MAGNUM_INCLUDE_INSTALL_DIR}/Platform)
install(TARGETS MagnumEmscriptenApplication
RUNTIME DESTINATION ${MAGNUM_BINARY_INSTALL_DIR}
LIBRARY DESTINATION ${MAGNUM_LIBRARY_INSTALL_DIR}
ARCHIVE DESTINATION ${MAGNUM_LIBRARY_INSTALL_DIR})
# Magnum EmscriptenApplication target alias for superprojects
add_library(Magnum::EmscriptenApplication ALIAS MagnumEmscriptenApplication)
endif()
# GLFW application
if(WITH_GLFWAPPLICATION)
find_package(GLFW)

615
src/Magnum/Platform/EmscriptenApplication.cpp

@ -0,0 +1,615 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019
Vladimír Vondruš <mosra@centrum.cz>
Copyright © 2018, 2019 Jonathan Hale <squareys@googlemail.com>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#include "EmscriptenApplication.h"
#include <emscripten/emscripten.h>
#include <emscripten/html5.h>
#include <Corrade/Containers/ArrayView.h>
#include <Corrade/Utility/Arguments.h>
#include <Corrade/Utility/Debug.h>
#include <Corrade/Utility/String.h>
#include "Magnum/Math/ConfigurationValue.h"
#ifdef MAGNUM_TARGET_GL
#include "Magnum/GL/Version.h"
#include "Magnum/Platform/GLContext.h"
#endif
#include "Magnum/Platform/Implementation/DpiScaling.h"
namespace Magnum { namespace Platform {
namespace {
typedef EmscriptenApplication::KeyEvent::Key Key;
/* Entry for key name to `Key` enum mapping */
struct Entry {
const char* key;
Key value;
};
/* Key name to `Key` enum mapping. These need to be sorted for use with
std::lower_bound in KeyEvent::toKey */
constexpr Entry KeyMapping[]{
{"AltLeft", Key::LeftAlt},
{"AltRight", Key::RightAlt},
{"ArrowDown", Key::Down},
{"ArrowLeft", Key::Left},
{"ArrowRight", Key::Right},
{"ArrowUp", Key::Up},
{"Backslash", Key::Backslash},
{"Backspace", Key::Backspace},
{"CapsLock", Key::CapsLock},
{"Comma", Key::Comma},
{"ContextMenu", Key::Menu},
{"ControlLeft", Key::LeftCtrl},
{"ControlRight", Key::RightCtrl},
{"Delete", Key::Delete},
{"End", Key::End},
{"Enter", Key::Enter},
{"Equal", Key::Equal},
{"Escape", Key::Esc},
{"Home", Key::Home},
{"Insert", Key::Insert},
{"MetaLeft", Key::LeftSuper},
{"MetaRight", Key::RightSuper},
{"Minus", Key::Minus},
{"NumLock", Key::NumLock},
{"PageDown", Key::PageDown},
{"PageUp", Key::PageUp},
{"Pause", Key::Pause},
{"Period", Key::Period},
{"Plus", Key::Plus},
{"PrintScreen", Key::PrintScreen},
{"Quote", Key::Quote},
{"ScrollLock", Key::ScrollLock},
{"ShiftLeft", Key::LeftShift},
{"ShiftRight", Key::RightShift},
{"Slash", Key::Slash},
{"Space", Key::Space},
{"Tab", Key::Tab},
};
/* Predicate for Entry "less than" to use with std::lower_bound */
struct EntryCompare {
bool operator()(const Entry& a, const char* const& b) {
return std::strcmp(a.key, b) < 0;
}
bool operator()(const char*& a, const Entry& b) {
return std::strcmp(a, b.key) < 0;
}
};
/* Translate emscripten key code (as defined by
https://www.w3.org/TR/uievents-code/#key-code-attribute-value)
to Key enum.
@param key Keyboard layout dependent key string, e.g. 'a', or '-'
@param code Keyboard layout independent key string, e.g. 'KeyA' or 'Minus'.
Note that the y key on some layouts may result in 'KeyZ'.
*/
Key toKey(const EM_UTF8* key, const EM_UTF8* code) {
const size_t keyLength = std::strlen(key);
if(keyLength == 0) return Key::Unknown;
/* We use key for a-z as it gives us a keyboard layout respecting
representation of the key, i.e. we get `z` for z depending on layout
where code may give us `y` independent of the layout. */
if(keyLength == 1) {
if(key[0] >= 'a' && key[0] <= 'z') return Key(key[0]);
else if(key[0] >= 'A' && key[0] <= 'Z') return Key(key[0] - 'A' + 'a');
}
/* We use code for 0-9 as it allows us to differentiate towards Numpad digits.
For digits independent of numpad or not, key is e.g. '0' for Zero */
const size_t codeLength = std::strlen(code);
if(Utility::String::viewBeginsWith({code, codeLength}, "Digit")) {
return Key(code[5]);
/* Numpad keys */
} else if(Utility::String::viewBeginsWith({code, codeLength}, "Numpad")) {
std::string numKey(code + 6);
if(numKey == "Add") return Key::NumAdd;
if(numKey == "Decimal") return Key::NumDecimal;
if(numKey == "Divide") return Key::NumDivide;
if(numKey == "Enter") return Key::NumEnter;
if(numKey == "Equal") return Key::NumEqual;
if(numKey == "Multiply") return Key::NumMultiply;
if(numKey == "Subtract") return Key::NumSubtract;
/* Numpad0 - Numpad9 */
const Int num = numKey[0] - '0';
if(num >= 0 && num <= 9) {
return Key(num + Int(Key::NumZero));
}
return Key::Unknown;
}
const auto mapping = Containers::arrayView(KeyMapping,
Containers::arraySize(KeyMapping));
const Entry* found =
std::lower_bound(mapping.begin(), mapping.end(), code, EntryCompare{});
if(found != mapping.end() && std::strcmp(found->key, code) == 0) {
return found->value;
}
/* F1 - F12 */
if(code[0] == 'F') {
/* F1-F9 */
if(code[2] != '\0') {
const Int num = code[2] - '0';
return Key(Int(Key::F10) + num);
}
/* F10-F12 */
const Int num = code[1] - '1';
return Key(Int(Key::F1) + num);
}
return Key::Unknown;
}
}
#ifdef MAGNUM_TARGET_GL
EmscriptenApplication::EmscriptenApplication(const Arguments& arguments): EmscriptenApplication{arguments, Configuration{}, GLConfiguration{}} {}
EmscriptenApplication::EmscriptenApplication(const Arguments& arguments, const Configuration& configuration): EmscriptenApplication{arguments, configuration, GLConfiguration{}} {}
EmscriptenApplication::EmscriptenApplication(const Arguments& arguments, const Configuration& configuration, const GLConfiguration& glConfiguration): EmscriptenApplication{arguments, NoCreate} {
create(configuration, glConfiguration);
}
#endif
EmscriptenApplication::EmscriptenApplication(const Arguments& arguments, NoCreateT):
_flags{Flag::Redraw}
{
Utility::Arguments args{Implementation::windowScalingArguments()};
#ifdef MAGNUM_TARGET_GL
_context.reset(new GLContext{NoCreate, args, arguments.argc, arguments.argv});
#else
args.parse(arguments.argc, arguments.argv);
#endif
/* Save command-line arguments */
if(args.value("log") == "verbose") _verboseLog = true;
const std::string dpiScaling = args.value("dpi-scaling");
/* Use physical DPI scaling */
if(dpiScaling == "default" || dpiScaling == "physical") {
/* Use explicit dpi scaling vector */
} else if(dpiScaling.find_first_of(" \t\n") != std::string::npos)
_commandLineDpiScaling = args.value<Vector2>("dpi-scaling");
/* Use explicit dpi scaling scalar */
else
_commandLineDpiScaling = Vector2{args.value<Float>("dpi-scaling")};
}
EmscriptenApplication::~EmscriptenApplication() {
emscripten_webgl_make_context_current(0);
}
void EmscriptenApplication::create() {
create(Configuration{});
}
void EmscriptenApplication::create(const Configuration& configuration) {
if(!tryCreate(configuration)) exit(1);
}
#ifdef MAGNUM_TARGET_GL
void EmscriptenApplication::create(const Configuration& configuration, const GLConfiguration& glConfiguration) {
if(!tryCreate(configuration, glConfiguration)) exit(32);
}
#endif
Vector2 EmscriptenApplication::dpiScaling(const Configuration& configuration) const {
std::ostream* verbose = _verboseLog ? Debug::output() : nullptr;
/* Use values from the configuration only if not overriden on command line.
In any case explicit scaling has a precedence before the policy. */
if(!_commandLineDpiScaling.isZero()) {
Debug{verbose} << "Platform::EmscriptenApplication: user-defined DPI scaling" << _commandLineDpiScaling.x();
return _commandLineDpiScaling;
} else if(!configuration.dpiScaling().isZero()) {
Debug{verbose} << "Platform::EmscriptenApplication: app-defined DPI scaling" << _commandLineDpiScaling.x();
return configuration.dpiScaling();
}
/* Take device pixel ratio on Emscripten */
const Vector2 dpiScaling{Implementation::emscriptenDpiScaling()};
Debug{verbose} << "Platform::EmscriptenApplication: physical DPI scaling" << dpiScaling.x();
return dpiScaling;
}
bool EmscriptenApplication::tryCreate(const Configuration& configuration) {
#ifdef MAGNUM_TARGET_GL
if(!(configuration.windowFlags() & Configuration::WindowFlag::Contextless)) {
return tryCreate(configuration, GLConfiguration{});
}
#endif
if(configuration.windowFlags() & Configuration::WindowFlag::Resizable) {
_flags |= Flag::Resizable;
}
_dpiScaling = dpiScaling(configuration);
/* Resize window and match it to the selected format */
const Vector2i canvasSizei{windowSize()};
_lastKnownCanvasSize = canvasSizei;
const Vector2i size = _dpiScaling*canvasSizei;
emscripten_set_canvas_element_size("#canvas", size.x(), size.y());
setupCallbacks();
return true;
}
#ifdef MAGNUM_TARGET_GL
bool EmscriptenApplication::tryCreate(const Configuration& configuration, const GLConfiguration& glConfiguration) {
CORRADE_ASSERT(_context->version() == GL::Version::None, "Platform::EmscriptenApplication::tryCreate(): window with OpenGL context already created", false);
if(configuration.windowFlags() & Configuration::WindowFlag::Resizable) {
_flags |= Flag::Resizable;
}
_dpiScaling = dpiScaling(configuration);
/* Create emscripten WebGL context */
EmscriptenWebGLContextAttributes attrs;
emscripten_webgl_init_context_attributes(&attrs);
attrs.alpha = glConfiguration.colorBufferSize().a() > 0;
attrs.depth = glConfiguration.depthBufferSize() > 0;
attrs.stencil = glConfiguration.stencilBufferSize() > 0;
attrs.antialias = glConfiguration.sampleCount() > 0;
attrs.premultipliedAlpha =
!!(glConfiguration.flags() & GLConfiguration::Flag::PremultipliedAlpha);
attrs.preserveDrawingBuffer =
!!(glConfiguration.flags() & GLConfiguration::Flag::PreserveDrawingBuffer);
/* powerPreference replaced preferLowPowerToHighPerformance in emscripten
version 1.38.26 */
#ifdef EM_WEBGL_POWERPREFERENCE_LOW_POWER
attrs.powerPreference =
!!(glConfiguration.flags() & GLConfiguration::Flag::PreferLowPowerToHighPerformance)
? EM_WEBGL_POWERPREFERENCE_LOW_POWER : EM_WEBGL_POWERPREFERENCE_HIGH_PERFORMANCE;
#else
attrs.preferLowPowerToHighPerformance =
!!(glConfiguration.flags() & GLConfiguration::Flag::PreferLowPowerToHighPerformance);
#endif
attrs.explicitSwapControl =
!!(glConfiguration.flags() & GLConfiguration::Flag::ExplicitSwapControl);
attrs.failIfMajorPerformanceCaveat =
!!(glConfiguration.flags() & GLConfiguration::Flag::FailIfMajorPerformanceCaveat);
attrs.enableExtensionsByDefault =
!!(glConfiguration.flags() & GLConfiguration::Flag::EnableExtensionsByDefault);
#ifdef MAGNUM_TARGET_GLES3
/* WebGL 2 */
attrs.majorVersion = 2;
#elif defined(MAGNUM_TARGET_GLES2)
/* WebGL 1 */
attrs.minorVersion = 1;
#else
#error unsupported OpenGL ES version
#endif
/* Resize window and match it to the selected format */
const Vector2i canvasSizei{windowSize()};
_lastKnownCanvasSize = canvasSizei;
const Vector2i size = _dpiScaling*canvasSizei;
emscripten_set_canvas_element_size("#canvas", size.x(), size.y());
/* Create surface and context */
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context =
emscripten_webgl_create_context("#canvas", &attrs);
if(!context) {
/* When context creation fails, `context` is a negative integer matching
EMSCRIPTEN_RESULT_* defines */
Error{} << "Platform::EmscriptenApplication::tryCreate(): cannot create WebGL context (EMSCRIPTEN_RESULT"
<< context << Debug::nospace << ")";
return false;
}
/* Make the context current */
CORRADE_INTERNAL_ASSERT_OUTPUT(
emscripten_webgl_make_context_current(context) == EMSCRIPTEN_RESULT_SUCCESS);
setupCallbacks();
/* Return true if the initialization succeeds */
return _context->tryCreate();
}
#endif
Vector2i EmscriptenApplication::windowSize() const {
double w, h;
emscripten_get_element_css_size("#canvas", &w, &h);
return {Int(w), Int(h)};
}
void EmscriptenApplication::swapBuffers() {
emscripten_webgl_commit_frame();
}
void EmscriptenApplication::setupCallbacks() {
emscripten_set_mousedown_callback("#canvas", this, false,
[](int, const EmscriptenMouseEvent* event, void* userData) -> Int {
MouseEvent e{event};
reinterpret_cast<EmscriptenApplication*>(userData)->mousePressEvent(e);
return e.isAccepted();
});
emscripten_set_mouseup_callback("#canvas", this, false,
[](int, const EmscriptenMouseEvent* event, void* userData) -> Int {
MouseEvent e{event};
reinterpret_cast<EmscriptenApplication*>(userData)->mouseReleaseEvent(e);
return e.isAccepted();
});
emscripten_set_mousemove_callback("#canvas", this, false,
[](int, const EmscriptenMouseEvent* event, void* userData) -> Int {
MouseMoveEvent e{event};
reinterpret_cast<EmscriptenApplication*>(userData)->mouseMoveEvent(e);
return e.isAccepted();
});
emscripten_set_wheel_callback("#canvas", this, false,
[](int, const EmscriptenWheelEvent* event, void* userData) -> Int {
MouseScrollEvent e{event};
reinterpret_cast<EmscriptenApplication*>(userData)->mouseScrollEvent(e);
return e.isAccepted();
});
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
/* document and window are 'specialEventTargets' in emscripten, matching
EMSCRIPTEN_EVENT_TARGET_DOCUMENT and EMSCRIPTEN_EVENT_TARGET_WINDOW.
As the lookup happens with the passed parameter and arrays support
element lookup via strings, we can unify the code by returning string of
1 or 2 if the target is document or window. This changed in Emscripten
1.38.27 depending on -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1
but we don't want to force this flag on the users so the behavior
handles both. */
#ifdef EMSCRIPTEN_EVENT_TARGET_DOCUMENT
char* const keyboardListeningElement = reinterpret_cast<char*>(EM_ASM_INT({
var element = Module['keyboardListeningElement'] || document;
if(element === document) return 1;
if(element === window) return 2;
if('id' in element)
return allocate(intArrayFromString(element.id), 'i8', ALLOC_NORMAL);
return 0;
}));
#else
char* const keyboardListeningElement = reinterpret_cast<char*>(EM_ASM_INT({
var element = Module['keyboardListeningElement'] || document;
if(element === document) element = {id: '#document'};
if(element === window) element = {id: '#window'};
if('id' in element)
return allocate(intArrayFromString(element.id), 'i8', ALLOC_NORMAL);
return 0;
}));
#endif
#pragma GCC diagnostic pop
/* Happens only if keyboardListeningElement was set, but did not have an
`id` attribute. Instead it should be either null or undefined, a DOM
element, `window` or `document`. */
CORRADE_ASSERT(keyboardListeningElement,
"EmscriptenApplication::setupCallbacks(): invalid value for Module['keyboardListeningElement']", );
/* keypress_callback does not fire for most of the keys and the modifiers
don't seem to work, keydown on the other hand works fine for all */
emscripten_set_keydown_callback(keyboardListeningElement, this, false,
[](int, const EmscriptenKeyboardEvent* event, void* userData) -> Int {
EmscriptenApplication* app = reinterpret_cast<EmscriptenApplication*>(userData);
if(app->isTextInputActive() && std::strlen(event->key) == 1) {
TextInputEvent e{{event->key, 1}};
app->textInputEvent(e);
return e.isAccepted();
}
KeyEvent e{event};
app->keyPressEvent(e);
return e.isAccepted();
});
emscripten_set_keyup_callback(keyboardListeningElement, this, false,
[](int, const EmscriptenKeyboardEvent* event, void* userData) -> Int {
KeyEvent e{event};
reinterpret_cast<EmscriptenApplication*>(userData)->keyReleaseEvent(e);
return e.isAccepted();
});
#ifdef EMSCRIPTEN_EVENT_TARGET_DOCUMENT
if(keyboardListeningElement != EMSCRIPTEN_EVENT_TARGET_DOCUMENT &&
keyboardListeningElement != EMSCRIPTEN_EVENT_TARGET_WINDOW)
#endif
{
std::free(keyboardListeningElement);
}
}
void EmscriptenApplication::startTextInput() {
_flags |= Flag::TextInputActive;
}
void EmscriptenApplication::stopTextInput() {
_flags &= ~Flag::TextInputActive;
}
void EmscriptenApplication::setTextInputRect(const Range2Di&) {
// TODO: Place a hidden input field at given rect
}
void EmscriptenApplication::viewportEvent(ViewportEvent&) {}
void EmscriptenApplication::keyPressEvent(KeyEvent&) {}
void EmscriptenApplication::keyReleaseEvent(KeyEvent&) {}
void EmscriptenApplication::mousePressEvent(MouseEvent&) {}
void EmscriptenApplication::mouseReleaseEvent(MouseEvent&) {}
void EmscriptenApplication::mouseMoveEvent(MouseMoveEvent&) {}
void EmscriptenApplication::mouseScrollEvent(MouseScrollEvent&) {}
void EmscriptenApplication::textInputEvent(TextInputEvent&) {}
EmscriptenApplication::GLConfiguration::GLConfiguration():
_colorBufferSize{8, 8, 8, 0}, _depthBufferSize{24}, _stencilBufferSize{0} {}
void EmscriptenApplication::mainLoopIteration() {
/* The resize event is not fired on window resize, so poll for the canvas
size here. But only if the window was requested to be resizable, to
avoid resizing the canvas when the user doesn't want that. Related
issue: https://github.com/kripken/emscripten/issues/1731
As this is caused by the DOM3 events spec only requiring browsers to
fire the resize event for `window` not generally for all DOM elemenets,
it also applies to `emscripten_set_resize_callback`. */
if(_flags & Flag::Resizable) {
/* Emscripten 1.38.27 changed to generic CSS selectors from element
IDs depending on -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1
being set (which we can't detect at compile time). See above for the
reason why we hardcode #canvas here. */
const Vector2i canvasSizei{windowSize()};
if(canvasSizei != _lastKnownCanvasSize) {
_lastKnownCanvasSize = canvasSizei;
const Vector2i size = _dpiScaling*canvasSizei;
emscripten_set_canvas_element_size("#canvas", size.x(), size.y());
#ifdef MAGNUM_TARGET_GL
ViewportEvent e{size, size, _dpiScaling};
#else
ViewportEvent e{size, _dpiScaling};
#endif
viewportEvent(e);
_flags |= Flag::Redraw;
}
}
if(_flags & Flag::Redraw) {
_flags &= ~Flag::Redraw;
drawEvent();
}
}
void EmscriptenApplication::exec() {
emscripten_set_main_loop_arg([](void* arg) {
static_cast<EmscriptenApplication*>(arg)->mainLoopIteration();
}, this, 0, true);
}
void EmscriptenApplication::exit(int) {
emscripten_cancel_main_loop();
}
EmscriptenApplication::MouseEvent::Button EmscriptenApplication::MouseEvent::button() const {
return Button(1 << _event->button);
}
Vector2i EmscriptenApplication::MouseEvent::position() const {
return {Int(_event->canvasX), Int(_event->canvasY)};
}
EmscriptenApplication::MouseEvent::Modifiers EmscriptenApplication::MouseEvent::modifiers() const {
Modifiers m;
if(_event->ctrlKey) m |= Modifier::Ctrl;
if(_event->shiftKey) m |= Modifier::Shift;
if(_event->altKey) m |= Modifier::Alt;
if(_event->metaKey) m |= Modifier::Super;
return m;
}
EmscriptenApplication::MouseMoveEvent::Buttons EmscriptenApplication::MouseMoveEvent::buttons() const {
return EmscriptenApplication::MouseMoveEvent::Button(_event->buttons);
}
Vector2i EmscriptenApplication::MouseMoveEvent::position() const {
return {Int(_event->canvasX), Int(_event->canvasY)};
}
EmscriptenApplication::MouseMoveEvent::Modifiers EmscriptenApplication::MouseMoveEvent::modifiers() const {
Modifiers m;
if(_event->ctrlKey) m |= Modifier::Ctrl;
if(_event->shiftKey) m |= Modifier::Shift;
if(_event->altKey) m |= Modifier::Alt;
if(_event->metaKey) m |= Modifier::Super;
return m;
}
Vector2 EmscriptenApplication::MouseScrollEvent::offset() const {
/* From emscripten's Browser.getMouseWheelDelta() function in
library_browser.js:
DOM_DELTA_PIXEL => 100 pixels = 1 step
DOM_DELTA_LINE => 3 lines = 1 step
DOM_DELTA_PAGE => 1 page = 80 steps
*/
const Float f = (_event->deltaMode == DOM_DELTA_PIXEL) ? -0.01f :
((_event->deltaMode == DOM_DELTA_LINE) ? -1.0f/3.0f : -80.0f);
return {f*Float(_event->deltaX), f*Float(_event->deltaY)};
}
Vector2i EmscriptenApplication::MouseScrollEvent::position() const {
return {Int(_event->mouse.canvasX), Int(_event->mouse.canvasY)};
}
EmscriptenApplication::InputEvent::Modifiers EmscriptenApplication::MouseScrollEvent::modifiers() const {
Modifiers m;
if(_event->mouse.ctrlKey) m |= Modifier::Ctrl;
if(_event->mouse.shiftKey) m |= Modifier::Shift;
if(_event->mouse.altKey) m |= Modifier::Alt;
if(_event->mouse.metaKey) m |= Modifier::Super;
return m;
}
Key EmscriptenApplication::KeyEvent::key() const {
return toKey(_event->key, _event->code);
}
std::string EmscriptenApplication::KeyEvent::keyName() const {
if((_event->key[0] >= 'a' && _event->key[0] <= 'z') ||
(_event->key[0] >= 'A' && _event->key[0] <= 'Z')) {
return _event->key;
}
return _event->code;
}
EmscriptenApplication::InputEvent::Modifiers EmscriptenApplication::KeyEvent::modifiers() const {
Modifiers m;
if(_event->ctrlKey) m |= Modifier::Ctrl;
if(_event->shiftKey) m |= Modifier::Shift;
if(_event->altKey) m |= Modifier::Alt;
if(_event->metaKey) m |= Modifier::Super;
return m;
}
}}

1372
src/Magnum/Platform/EmscriptenApplication.h

File diff suppressed because it is too large Load Diff

33
src/Magnum/Platform/Test/CMakeLists.txt

@ -23,6 +23,8 @@
# DEALINGS IN THE SOFTWARE.
#
set(ADDITIONAL_WEB_FILES "")
if(WITH_ANDROIDAPPLICATION)
add_library(PlatformAndroidApplicationTest SHARED AndroidApplicationTest.cpp)
target_link_libraries(PlatformAndroidApplicationTest PRIVATE MagnumAndroidApplication)
@ -32,6 +34,14 @@ if(WITH_ANDROIDAPPLICATION)
endif()
endif()
if(WITH_EMSCRIPTENAPPLICATION)
add_executable(PlatformEmscriptenApplicationTest EmscriptenApplicationTest.cpp)
target_link_libraries(PlatformEmscriptenApplicationTest PRIVATE MagnumEmscriptenApplication MagnumGL)
set_target_properties(PlatformEmscriptenApplicationTest PROPERTIES FOLDER "Magnum/Platform/Test")
# So we can spin up a webserver in the build dir for easy testing
list(APPEND ADDITIONAL_WEB_FILES EmscriptenApplicationTest.html)
endif()
if(WITH_GLFWAPPLICATION)
add_executable(PlatformGlfwApplicationTest GlfwApplicationTest.cpp)
# HiDPi.manifest not needed, as GLFW sets that on its own
@ -57,12 +67,7 @@ if(WITH_SDL2APPLICATION)
target_link_libraries(PlatformSdl2ApplicationTest PRIVATE MagnumSdl2Application)
set_target_properties(PlatformSdl2ApplicationTest PROPERTIES FOLDER "Magnum/Platform/Test")
if(CORRADE_TARGET_EMSCRIPTEN)
# So we can spin up a webserver in the build dir for easy testing
file(COPY
../EmscriptenApplication.js
../WebApplication.css
Sdl2ApplicationTest.html
DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
list(APPEND ADDITIONAL_WEB_FILES Sdl2ApplicationTest.html)
elseif(CORRADE_TARGET_IOS OR CORRADE_TARGET_APPLE)
# The plist is needed in order to mark the app as DPI-aware
set_target_properties(PlatformSdl2ApplicationTest PROPERTIES
@ -90,11 +95,9 @@ if(WITH_WINDOWLESSEGLAPPLICATION)
set_target_properties(PlatformWindowlessEglApplicationTest PROPERTIES FOLDER "Magnum/Platform/Test")
if(CORRADE_TARGET_EMSCRIPTEN)
# So we can spin up a webserver in the build dir for easy testing
file(COPY
list(APPEND ADDITIONAL_WEB_FILES
../WindowlessEmscriptenApplication.js
../WebApplication.css
WindowlessEglApplicationTest.html
DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
WindowlessEglApplicationTest.html)
endif()
endif()
@ -124,3 +127,13 @@ if(WITH_WINDOWLESSWINDOWSEGLAPPLICATION)
target_link_libraries(PlatformWindowlessWindowsEglApplicationTest PRIVATE MagnumWindowlessWindowsEglApplication)
set_target_properties(PlatformWindowlessWindowsEglApplicationTest PROPERTIES FOLDER "Magnum/Platform/Test")
endif()
if(WITH_EMSCRIPTENAPPLICATION OR (WITH_SDL2APPLICATION AND CORRADE_TARGET_EMSCRIPTEN))
list(APPEND ADDITIONAL_WEB_FILES ../EmscriptenApplication.js)
endif()
if(WITH_EMSCRIPTENAPPLICATION OR (WITH_SDL2APPLICATION AND CORRADE_TARGET_EMSCRIPTEN) OR WITH_WINDOWLESSWINDOWSEGLAPPLICATION)
# So we can spin up a webserver in the build dir for easy testing
file(COPY ../WebApplication.css ${ADDITIONAL_WEB_FILES}
DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
endif()

86
src/Magnum/Platform/Test/EmscriptenApplicationTest.cpp

@ -0,0 +1,86 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018
Vladimír Vondruš <mosra@centrum.cz>
Copyright © 2018, 2019 Jonathan Hale <squareys@googlemail.com>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#include "Magnum/Platform/EmscriptenApplication.h"
#include "Magnum/GL/Renderer.h"
#include "Magnum/GL/DefaultFramebuffer.h"
namespace Magnum { namespace Platform { namespace Test {
struct EmscriptenApplicationTest: Platform::Application {
/* For testing resize events */
explicit EmscriptenApplicationTest(const Arguments& arguments):
Platform::Application{arguments, Configuration{}
.setWindowFlags(Configuration::WindowFlag::Resizable)} {
}
virtual void drawEvent() override {
GL::defaultFramebuffer.clear(GL::FramebufferClear::Color);
swapBuffers();
redraw();
}
#ifdef MAGNUM_TARGET_GL
/* For testing HiDPI resize events */
void viewportEvent(ViewportEvent& event) override {
Debug{} << "viewport event" << event.windowSize() << event.framebufferSize() << event.dpiScaling();
}
#endif
/* For testing event coordinates */
void mousePressEvent(MouseEvent& event) override {
Debug{} << "press" << event.position() << Int(event.button());
}
/* For testing event coordinates */
void mouseReleaseEvent(MouseEvent& event) override {
Debug{} << "release" << event.position() << Int(event.button());
}
/* For testing event coordinates */
void mouseMoveEvent(MouseMoveEvent& event) override {
Debug{} << "move" << event.position();
}
/* For testing keyboard capture */
void keyPressEvent(KeyEvent& event) override {
if(event.key() != KeyEvent::Key::Unknown) {
Debug{} << "keyPressEvent(" << event.keyName().c_str() << "): ✓";
} else {
Debug{} << "keyPressEvent(" << event.keyName().c_str() << "): x";
}
if(event.modifiers() & KeyEvent::Modifier::Shift) Debug{} << "Shift";
if(event.modifiers() & KeyEvent::Modifier::Ctrl) Debug{} << "Ctrl";
if(event.modifiers() & KeyEvent::Modifier::Alt) Debug{} << "Alt";
if(event.modifiers() & KeyEvent::Modifier::Super) Debug{} << "Super";
}
};
}}}
MAGNUM_APPLICATION_MAIN(Magnum::Platform::Test::EmscriptenApplicationTest)

28
src/Magnum/Platform/Test/EmscriptenApplicationTest.html

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Magnum EmscriptenApplication Test</title>
<link rel="stylesheet" href="WebApplication.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<h1>Magnum EmscriptenApplication Test</h1>
<div id="container" class="">
<div id="sizer"><div id="expander"><div id="listener">
<canvas id="canvas" tabindex="0"></canvas>
<div id="status">Initialization...</div>
<div id="status-description"></div>
<script src="EmscriptenApplication.js"></script>
<script async="async" src="PlatformEmscriptenApplicationTest.js"></script>
<script>
/* To test keyboard capture directly on the canvas */
Module.keyboardListeningElement = Module.canvas;
Module.canvas.addEventListener('mousedown', function(event) {
event.target.focus();
});
</script>
</div></div></div>
</div>
</body>
</html>
Loading…
Cancel
Save