diff options
author | Mikolaj Boc <mikolaj.boc@qt.io> | 2022-11-14 09:12:34 +0100 |
---|---|---|
committer | Mikolaj Boc <mikolaj.boc@qt.io> | 2022-11-26 11:23:13 +0100 |
commit | fa27a59ec3cfc173d5ff202feb23e8b6e398dac9 (patch) | |
tree | a34f7a199611118e826a029a5bebf93f9425b32d /src | |
parent | c675c1c56d3c00b3c1de5aee8c7c6f7eeeb70bc1 (diff) |
Use the browser compositor for drawing windows on WASM
Make the browser compositor draw the window non-client area (using css
+ html). Get rid of OpenGL usage in non-OpenGL windows and use canvas
2d context instead to blit the texture (QImage).
Also, as part of the change, remove the deprecated canvas element support
in QScreen.
Fixes: QTBUG-107116
Fixes: QTBUG-107219
Change-Id: I65f0d91831c806315685ca681ac0e416673f5cd5
Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
Reviewed-by: Aleksandr Reviakin <aleksandr.reviakin@qt.io>
Reviewed-by: David Skoland <david.skoland@qt.io>
Diffstat (limited to 'src')
28 files changed, 940 insertions, 974 deletions
diff --git a/src/corelib/doc/src/cmake/qt_finalize_target.qdoc b/src/corelib/doc/src/cmake/qt_finalize_target.qdoc index 277c32ea57..4b71248a1e 100644 --- a/src/corelib/doc/src/cmake/qt_finalize_target.qdoc +++ b/src/corelib/doc/src/cmake/qt_finalize_target.qdoc @@ -61,7 +61,7 @@ CMake version earlier than 3.21. \section2 WASM Create \c{${target}.html} (a target-specific \c{wasm_shell.html} file), -\c{qtloader.js} and \c{qtlogo.svg} files in the \c{CMAKE_CURRENT_BINARY_DIR}. +\c{qtloader.js}, and \c{qtlogo.svg} files in the \c{CMAKE_CURRENT_BINARY_DIR}. \section2 iOS diff --git a/src/corelib/platform/wasm/qstdweb.cpp b/src/corelib/platform/wasm/qstdweb.cpp index 59d8298bb6..363a21ee8d 100644 --- a/src/corelib/platform/wasm/qstdweb.cpp +++ b/src/corelib/platform/wasm/qstdweb.cpp @@ -626,7 +626,11 @@ void EventCallback::activate(emscripten::val event) { emscripten::val target = event["target"]; std::string eventName = event["type"].as<std::string>(); - EventCallback *that = reinterpret_cast<EventCallback *>(target[contextPropertyName(eventName).c_str()].as<intptr_t>()); + emscripten::val property = target[contextPropertyName(eventName)]; + // This might happen when the event bubbles + if (property.isUndefined()) + return; + EventCallback *that = reinterpret_cast<EventCallback *>(property.as<intptr_t>()); that->m_fn(event); } diff --git a/src/plugins/platforms/wasm/CMakeLists.txt b/src/plugins/platforms/wasm/CMakeLists.txt index 869014d2e5..962c289640 100644 --- a/src/plugins/platforms/wasm/CMakeLists.txt +++ b/src/plugins/platforms/wasm/CMakeLists.txt @@ -17,6 +17,7 @@ qt_internal_add_plugin(QWasmIntegrationPlugin qwasmaccessibility.cpp qwasmaccessibility.h qwasmclipboard.cpp qwasmclipboard.h qwasmcompositor.cpp qwasmcompositor.h + qwasmcssstyle.cpp qwasmcssstyle.h qwasmcursor.cpp qwasmcursor.h qwasmevent.cpp qwasmevent.h qwasmeventdispatcher.cpp qwasmeventdispatcher.h @@ -60,6 +61,7 @@ qt_internal_add_resource(QWasmIntegrationPlugin "wasmfonts" FILES ${wasmfonts_resource_files} ) + qt_internal_extend_target(QWasmIntegrationPlugin CONDITION QT_FEATURE_opengl SOURCES qwasmbackingstore.cpp qwasmbackingstore.h @@ -74,7 +76,23 @@ qt_internal_extend_target(QWasmIntegrationPlugin CONDITION QT_FEATURE_opengl set(wasm_support_files wasm_shell.html qtloader.js - qtlogo.svg + resources/qtlogo.svg +) + +set(wasmwindow_resource_files + "resources/maximize.svg" + "resources/qtlogo.svg" + "resources/restore.svg" + "resources/x.svg" +) + +qt_internal_add_resource(QWasmIntegrationPlugin "wasmwindow" + PREFIX + "/wasm-window" + BASE + "resources" + FILES + ${wasmwindow_resource_files} ) qt_path_join(destination ${QT_INSTALL_DIR} "plugins/platforms") diff --git a/src/plugins/platforms/wasm/qtlogo.svg b/src/plugins/platforms/wasm/qtlogo.svg deleted file mode 100644 index ad7c7776bf..0000000000 --- a/src/plugins/platforms/wasm/qtlogo.svg +++ /dev/null @@ -1,40 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - width="462pt" - height="339pt" - viewBox="0 0 462 339" - version="1.1"> - <metadata - id="metadata20"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title></dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <path - fill="#41cd52" - d=" M 63.50 0.00 L 462.00 0.00 L 462.00 274.79 C 440.60 296.26 419.13 317.66 397.61 339.00 L 0.00 339.00 L 0.00 63.39 C 21.08 42.18 42.34 21.13 63.50 0.00 Z" - id="path6" /> - <path - d=" M 122.37 71.33 C 137.50 61.32 156.21 58.79 174.00 58.95 C 190.94 59.16 208.72 62.13 222.76 72.24 C 232.96 79.41 239.59 90.48 244.01 101.93 C 251.16 120.73 253.26 141.03 253.50 161.01 C 253.53 181.13 252.62 201.69 245.96 220.86 C 241.50 233.90 233.01 245.48 221.81 253.52 C 229.87 266.58 238.09 279.54 246.15 292.60 C 236.02 297.27 225.92 301.97 215.78 306.62 C 207.15 292.38 198.56 278.11 189.90 263.89 C 178.19 265.81 166.21 265.66 154.44 264.36 C 140.34 262.67 125.97 258.37 115.09 248.88 C 106.73 241.64 101.48 231.51 97.89 221.21 C 92.01 203.79 90.43 185.25 90.16 166.97 C 90.02 147.21 91.28 127.14 97.24 108.18 C 101.85 93.92 109.48 79.69 122.37 71.33 Z" - id="path8" - fill="#ffffff" /> - <path - d=" M 294.13 70.69 C 304.73 70.68 315.33 70.68 325.93 70.69 C 325.96 84.71 325.92 98.72 325.95 112.74 C 339.50 112.76 353.05 112.74 366.60 112.75 C 366.37 121.85 366.12 130.95 365.86 140.05 C 352.32 140.08 338.79 140.04 325.25 140.07 C 325.28 163.05 325.18 186.03 325.30 209.01 C 325.56 215.30 325.42 221.94 328.19 227.75 C 330.21 232.23 335.65 233.38 340.08 233.53 C 348.43 233.50 356.77 233.01 365.12 232.86 C 365.63 241.22 366.12 249.59 366.60 257.95 C 349.99 260.74 332.56 264.08 316.06 258.86 C 309.11 256.80 302.63 252.19 299.81 245.32 C 294.76 233.63 294.35 220.62 294.13 208.07 C 294.11 185.40 294.13 162.74 294.12 140.07 C 286.73 140.05 279.34 140.08 271.95 140.05 C 271.93 130.96 271.93 121.86 271.95 112.76 C 279.34 112.73 286.72 112.77 294.11 112.74 C 294.14 98.72 294.10 84.71 294.13 70.69 Z" - id="path10" - fill="#ffffff" /> - <path - fill="#41cd52" - d=" M 160.51 87.70 C 170.80 86.36 181.60 86.72 191.34 90.61 C 199.23 93.73 205.93 99.84 209.47 107.58 C 214.90 119.31 216.98 132.26 218.03 145.05 C 219.17 162.07 219.01 179.25 216.66 196.17 C 215.01 206.24 212.66 216.85 205.84 224.79 C 198.92 232.76 188.25 236.18 178.01 236.98 C 167.21 237.77 155.82 236.98 146.07 231.87 C 140.38 228.84 135.55 224.09 132.73 218.27 C 129.31 211.30 127.43 203.69 126.11 196.07 C 122.13 171.91 121.17 146.91 126.61 122.89 C 128.85 113.83 132.11 104.53 138.73 97.70 C 144.49 91.85 152.51 88.83 160.51 87.70 Z" - id="path12" /> -</svg> diff --git a/src/plugins/platforms/wasm/qwasmaccessibility.cpp b/src/plugins/platforms/wasm/qwasmaccessibility.cpp index 75e5b0a674..eb3e30b140 100644 --- a/src/plugins/platforms/wasm/qwasmaccessibility.cpp +++ b/src/plugins/platforms/wasm/qwasmaccessibility.cpp @@ -38,7 +38,7 @@ emscripten::val QWasmAccessibility::getContainer(QAccessibleInterface *iface) QWasmScreen *screen = QWasmScreen::get(window->screen()); if (!screen) return emscripten::val::undefined(); - return screen->container(); + return screen->element(); } emscripten::val QWasmAccessibility::getDocument(const emscripten::val &container) @@ -142,11 +142,12 @@ void QWasmAccessibility::setHtmlElementGeometry(QAccessibleInterface *iface) void QWasmAccessibility::setHtmlElementGeometry(QAccessibleInterface *iface, emscripten::val element) { // Position the element using "position: absolute" in order to place - // it under the corresponding Qt element on the canvas. + // it under the corresponding Qt element in the screen. QRect geometry = iface->rect(); emscripten::val style = element["style"]; style.set("position", std::string("absolute")); - style.set("z-index", std::string("-1")); // FIXME: "0" should be sufficient to order beheind the canvas, but isn't + style.set("z-index", std::string("-1")); // FIXME: "0" should be sufficient to order beheind the + // screen element, but isn't style.set("left", std::to_string(geometry.x()) + "px"); style.set("top", std::to_string(geometry.y()) + "px"); style.set("width", std::to_string(geometry.width()) + "px"); diff --git a/src/plugins/platforms/wasm/qwasmbackingstore.cpp b/src/plugins/platforms/wasm/qwasmbackingstore.cpp index 05a5a1bbdb..e962592862 100644 --- a/src/plugins/platforms/wasm/qwasmbackingstore.cpp +++ b/src/plugins/platforms/wasm/qwasmbackingstore.cpp @@ -5,20 +5,16 @@ #include "qwasmwindow.h" #include "qwasmcompositor.h" -#include <QtOpenGL/qopengltexture.h> -#include <QtGui/qmatrix4x4.h> #include <QtGui/qpainter.h> -#include <private/qguiapplication_p.h> -#include <qpa/qplatformscreen.h> -#include <QtGui/qoffscreensurface.h> #include <QtGui/qbackingstore.h> +#include <emscripten.h> +#include <emscripten/wire.h> + QT_BEGIN_NAMESPACE QWasmBackingStore::QWasmBackingStore(QWasmCompositor *compositor, QWindow *window) - : QPlatformBackingStore(window) - , m_compositor(compositor) - , m_texture(new QOpenGLTexture(QOpenGLTexture::Target2D)) + : QPlatformBackingStore(window), m_compositor(compositor) { QWasmWindow *wasmWindow = static_cast<QWasmWindow *>(window->handle()); if (wasmWindow) @@ -29,29 +25,11 @@ QWasmBackingStore::~QWasmBackingStore() { auto window = this->window(); QWasmIntegration::get()->removeBackingStore(window); - destroy(); QWasmWindow *wasmWindow = static_cast<QWasmWindow *>(window->handle()); if (wasmWindow) wasmWindow->setBackingStore(nullptr); } -void QWasmBackingStore::destroy() -{ - if (m_texture->isCreated()) { - auto context = m_compositor->context(); - auto currentContext = QOpenGLContext::currentContext(); - if (!currentContext || !QOpenGLContext::areSharing(context, currentContext)) { - QOffscreenSurface offScreenSurface(m_compositor->screen()->screen()); - offScreenSurface.setFormat(context->format()); - offScreenSurface.create(); - context->makeCurrent(&offScreenSurface); - m_texture->destroy(); - } else { - m_texture->destroy(); - } - } -} - QPaintDevice *QWasmBackingStore::paintDevice() { return &m_image; @@ -64,33 +42,24 @@ void QWasmBackingStore::flush(QWindow *window, const QRegion ®ion, const QPoi Q_UNUSED(offset); m_dirty |= region; - m_compositor->handleBackingStoreFlush(); + m_compositor->handleBackingStoreFlush(window); } -void QWasmBackingStore::updateTexture() +void QWasmBackingStore::updateTexture(QWasmWindow *window) { if (m_dirty.isNull()) return; - if (m_recreateTexture) { - m_recreateTexture = false; - destroy(); + if (m_webImageDataArray.isUndefined()) { + m_webImageDataArray = window->context2d().call<emscripten::val>( + "createImageData", emscripten::val(m_image.width()), + emscripten::val(m_image.height())); } - if (!m_texture->isCreated()) { - m_texture->setMinificationFilter(QOpenGLTexture::Nearest); - m_texture->setMagnificationFilter(QOpenGLTexture::Nearest); - m_texture->setWrapMode(QOpenGLTexture::ClampToEdge); - m_texture->setData(m_image, QOpenGLTexture::DontGenerateMipMaps); - m_texture->create(); - } - m_texture->bind(); - - QRegion fixed; + QRegion clippedDpiScaledRegion; QRect imageRect = m_image.rect(); for (const QRect &rect : m_dirty) { - // Convert device-independent dirty region to device region qreal dpr = m_image.devicePixelRatio(); QRect deviceRect = QRect(rect.topLeft() * dpr, rect.size() * dpr); @@ -103,21 +72,40 @@ void QWasmBackingStore::updateTexture() r.setWidth(imageRect.width()); } - fixed |= r; + clippedDpiScaledRegion |= r; } - for (const QRect &rect : fixed) { - // if the sub-rect is full-width we can pass the image data directly to - // OpenGL instead of copying, since there is no gap between scanlines - if (rect.width() == imageRect.width()) { - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, rect.y(), rect.width(), rect.height(), GL_RGBA, GL_UNSIGNED_BYTE, - m_image.constScanLine(rect.y())); + for (const QRect &dirtyRect : clippedDpiScaledRegion) { + constexpr int BytesPerColor = 4; + if (dirtyRect.width() == imageRect.width()) { + // Copy a contiguous chunk of memory + // ............... + // OOOOOOOOOOOOOOO + // OOOOOOOOOOOOOOO -> image data + // OOOOOOOOOOOOOOO + // ............... + auto imageMemory = emscripten::typed_memory_view(dirtyRect.width() * dirtyRect.height() + * BytesPerColor, + m_image.constScanLine(dirtyRect.y())); + m_webImageDataArray["data"].call<void>("set", imageMemory); } else { - glTexSubImage2D(GL_TEXTURE_2D, 0, rect.x(), rect.y(), rect.width(), rect.height(), GL_RGBA, GL_UNSIGNED_BYTE, - m_image.copy(rect).constBits()); + // Go through the scanlines manually to set the individual lines in bulk. This is + // marginally less performant than the above. + // ............... + // ...OOOOOOOOO... r = 0 -> image data + // ...OOOOOOOOO... r = 1 -> image data + // ...OOOOOOOOO... r = 2 -> image data + // ............... + for (int r = 0; r < dirtyRect.height(); ++r) { + auto scanlineMemory = emscripten::typed_memory_view( + dirtyRect.width() * 4, + m_image.constScanLine(r) + BytesPerColor * dirtyRect.x()); + m_webImageDataArray["data"].call<void>("set", scanlineMemory, + (r * dirtyRect.width() + dirtyRect.x()) + * BytesPerColor); + } } } - /* End of code taken from QEGLPlatformBackingStore */ m_dirty = QRegion(); } @@ -143,12 +131,11 @@ void QWasmBackingStore::resize(const QSize &size, const QRegion &staticContents) { Q_UNUSED(staticContents); - QImage::Format format = window()->format().hasAlpha() ? - QImage::Format_ARGB32_Premultiplied : QImage::Format_RGB32; + QImage::Format format = QImage::Format_RGBA8888; const auto platformScreenDPR = window()->handle()->devicePixelRatio(); m_image = QImage(size * platformScreenDPR, format); m_image.setDevicePixelRatio(platformScreenDPR); - m_recreateTexture = true; + m_webImageDataArray = emscripten::val::undefined(); } QImage QWasmBackingStore::toImage() const @@ -162,10 +149,10 @@ const QImage &QWasmBackingStore::getImageRef() const return m_image; } -const QOpenGLTexture *QWasmBackingStore::getUpdatedTexture() +emscripten::val QWasmBackingStore::getUpdatedWebImage(QWasmWindow *window) { - updateTexture(); - return m_texture.data(); + updateTexture(window); + return m_webImageDataArray; } QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmbackingstore.h b/src/plugins/platforms/wasm/qwasmbackingstore.h index b5c5e2e406..54e9fe4cb3 100644 --- a/src/plugins/platforms/wasm/qwasmbackingstore.h +++ b/src/plugins/platforms/wasm/qwasmbackingstore.h @@ -7,18 +7,20 @@ #include <qpa/qplatformbackingstore.h> #include <QtGui/qimage.h> +#include <emscripten/val.h> + QT_BEGIN_NAMESPACE class QOpenGLTexture; class QRegion; class QWasmCompositor; +class QWasmWindow; class QWasmBackingStore : public QPlatformBackingStore { public: QWasmBackingStore(QWasmCompositor *compositor, QWindow *window); ~QWasmBackingStore(); - void destroy(); QPaintDevice *paintDevice() override; @@ -28,17 +30,16 @@ public: QImage toImage() const override; const QImage &getImageRef() const; - const QOpenGLTexture *getUpdatedTexture(); + emscripten::val getUpdatedWebImage(QWasmWindow *window); protected: - void updateTexture(); + void updateTexture(QWasmWindow *window); private: QWasmCompositor *m_compositor; QImage m_image; - QScopedPointer<QOpenGLTexture> m_texture; QRegion m_dirty; - bool m_recreateTexture = false; + emscripten::val m_webImageDataArray = emscripten::val::undefined(); }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmclipboard.cpp b/src/plugins/platforms/wasm/qwasmclipboard.cpp index e2dae22149..c646f6b453 100644 --- a/src/plugins/platforms/wasm/qwasmclipboard.cpp +++ b/src/plugins/platforms/wasm/qwasmclipboard.cpp @@ -225,14 +225,14 @@ void QWasmClipboard::initClipboardPermissions() })()); } -void QWasmClipboard::installEventHandlers(const emscripten::val &canvas) +void QWasmClipboard::installEventHandlers(const emscripten::val &screenElement) { emscripten::val cContext = val::undefined(); emscripten::val isChromium = val::global("window")["chrome"]; if (!isChromium.isUndefined()) { cContext = val::global("document"); } else { - cContext = canvas; + cContext = screenElement; } // Fallback path for browsers which do not support direct clipboard access cContext.call<void>("addEventListener", val("cut"), diff --git a/src/plugins/platforms/wasm/qwasmcompositor.cpp b/src/plugins/platforms/wasm/qwasmcompositor.cpp index b4d09da22f..ad8a4508ce 100644 --- a/src/plugins/platforms/wasm/qwasmcompositor.cpp +++ b/src/plugins/platforms/wasm/qwasmcompositor.cpp @@ -8,14 +8,7 @@ #include "qwasmclipboard.h" #include "qwasmevent.h" -#include <QtOpenGL/qopenglpixeltransferoptions.h> -#include <QtOpenGL/qopengltexture.h> - #include <QtGui/private/qwindow_p.h> -#include <QtGui/qopenglcontext.h> -#include <QtGui/qopenglfunctions.h> -#include <QtGui/qoffscreensurface.h> -#include <QtGui/qpainter.h> #include <private/qguiapplication_p.h> @@ -25,8 +18,6 @@ #include <emscripten/bind.h> -#include <GL/gl.h> - namespace { QWasmWindow *asWasmWindow(QWindow *window) { @@ -48,14 +39,13 @@ static void mouseWheelEvent(emscripten::val event) } EMSCRIPTEN_BINDINGS(qtMouseModule) { - function("qtMouseWheelEvent", &mouseWheelEvent); + function("qtMouseWheelEvent", &mouseWheelEvent); } QWasmCompositor::QWasmCompositor(QWasmScreen *screen) : QObject(screen), m_windowManipulation(screen), m_windowStack(std::bind(&QWasmCompositor::onTopWindowChanged, this)), - m_blitter(new QOpenGLTextureBlitter), m_eventTranslator(std::make_unique<QWasmEventTranslator>()) { m_touchDevice = std::make_unique<QPointingDevice>( @@ -80,90 +70,83 @@ QWasmCompositor::~QWasmCompositor() void QWasmCompositor::deregisterEventHandlers() { - QByteArray canvasSelector = screen()->canvasTargetId().toUtf8(); - emscripten_set_keydown_callback(canvasSelector.constData(), 0, 0, NULL); - emscripten_set_keyup_callback(canvasSelector.constData(), 0, 0, NULL); + QByteArray screenElementSelector = screen()->eventTargetId().toUtf8(); + emscripten_set_keydown_callback(screenElementSelector.constData(), 0, 0, NULL); + emscripten_set_keyup_callback(screenElementSelector.constData(), 0, 0, NULL); - emscripten_set_focus_callback(canvasSelector.constData(), 0, 0, NULL); + emscripten_set_focus_callback(screenElementSelector.constData(), 0, 0, NULL); - emscripten_set_wheel_callback(canvasSelector.constData(), 0, 0, NULL); + emscripten_set_wheel_callback(screenElementSelector.constData(), 0, 0, NULL); - emscripten_set_touchstart_callback(canvasSelector.constData(), 0, 0, NULL); - emscripten_set_touchend_callback(canvasSelector.constData(), 0, 0, NULL); - emscripten_set_touchmove_callback(canvasSelector.constData(), 0, 0, NULL); - emscripten_set_touchcancel_callback(canvasSelector.constData(), 0, 0, NULL); + emscripten_set_touchstart_callback(screenElementSelector.constData(), 0, 0, NULL); + emscripten_set_touchend_callback(screenElementSelector.constData(), 0, 0, NULL); + emscripten_set_touchmove_callback(screenElementSelector.constData(), 0, 0, NULL); + emscripten_set_touchcancel_callback(screenElementSelector.constData(), 0, 0, NULL); - val canvas = screen()->canvas(); - canvas.call<void>("removeEventListener", - std::string("drop"), - val::module_property("qtDrop"), val(true)); + screen()->element().call<void>("removeEventListener", std::string("drop"), + val::module_property("qtDrop"), val(true)); } void QWasmCompositor::destroy() { - // Destroy OpenGL resources. This is done here in a separate function - // which can be called while screen() still returns a valid screen - // (which it might not, during destruction). A valid QScreen is - // a requirement for QOffscreenSurface on Wasm since the native - // context is tied to a single canvas. - if (m_context) { - QOffscreenSurface offScreenSurface(screen()->screen()); - offScreenSurface.setFormat(m_context->format()); - offScreenSurface.create(); - m_context->makeCurrent(&offScreenSurface); - for (QWasmWindow *window : m_windowStack) - window->destroy(); - m_blitter.reset(nullptr); - m_context.reset(nullptr); - } - + // TODO(mikolaj.boc): Investigate if m_isEnabled is needed at all. It seems like a frame should + // not be generated after this instead. m_isEnabled = false; // prevent frame() from creating a new m_context } void QWasmCompositor::initEventHandlers() { - QByteArray canvasSelector = screen()->canvasTargetId().toUtf8(); - if (platform() == Platform::MacOS) { if (!emscripten::val::global("window")["safari"].isUndefined()) { - val canvas = screen()->canvas(); - canvas.call<void>("addEventListener", - val("wheel"), - val::module_property("qtMouseWheelEvent")); + screen()->element().call<void>("addEventListener", val("wheel"), + val::module_property("qtMouseWheelEvent")); } } constexpr EM_BOOL UseCapture = 1; - emscripten_set_keydown_callback(canvasSelector.constData(), (void *)this, UseCapture, &keyboard_cb); - emscripten_set_keyup_callback(canvasSelector.constData(), (void *)this, UseCapture, &keyboard_cb); + const QByteArray screenElementSelector = screen()->eventTargetId().toUtf8(); + emscripten_set_keydown_callback(screenElementSelector.constData(), (void *)this, UseCapture, + &keyboard_cb); + emscripten_set_keyup_callback(screenElementSelector.constData(), (void *)this, UseCapture, + &keyboard_cb); - val canvas = screen()->canvas(); + val screenElement = screen()->element(); const auto callback = std::function([this](emscripten::val event) { if (processPointer(*PointerEvent::fromWeb(event))) event.call<void>("preventDefault"); }); - m_pointerDownCallback = std::make_unique<qstdweb::EventCallback>(canvas, "pointerdown", callback); - m_pointerMoveCallback = std::make_unique<qstdweb::EventCallback>(canvas, "pointermove", callback); - m_pointerUpCallback = std::make_unique<qstdweb::EventCallback>(canvas, "pointerup", callback); - m_pointerEnterCallback = std::make_unique<qstdweb::EventCallback>(canvas, "pointerenter", callback); - m_pointerLeaveCallback = std::make_unique<qstdweb::EventCallback>(canvas, "pointerleave", callback); - - emscripten_set_focus_callback(canvasSelector.constData(), (void *)this, UseCapture, &focus_cb); - - emscripten_set_wheel_callback(canvasSelector.constData(), (void *)this, UseCapture, &wheel_cb); - - emscripten_set_touchstart_callback(canvasSelector.constData(), (void *)this, UseCapture, &touchCallback); - emscripten_set_touchend_callback(canvasSelector.constData(), (void *)this, UseCapture, &touchCallback); - emscripten_set_touchmove_callback(canvasSelector.constData(), (void *)this, UseCapture, &touchCallback); - emscripten_set_touchcancel_callback(canvasSelector.constData(), (void *)this, UseCapture, &touchCallback); - - canvas.call<void>("addEventListener", - std::string("drop"), - val::module_property("qtDrop"), val(true)); - canvas.set("data-qtdropcontext", // ? unique - emscripten::val(quintptr(reinterpret_cast<void *>(screen())))); + m_pointerDownCallback = + std::make_unique<qstdweb::EventCallback>(screenElement, "pointerdown", callback); + m_pointerMoveCallback = + std::make_unique<qstdweb::EventCallback>(screenElement, "pointermove", callback); + m_pointerUpCallback = + std::make_unique<qstdweb::EventCallback>(screenElement, "pointerup", callback); + m_pointerEnterCallback = + std::make_unique<qstdweb::EventCallback>(screenElement, "pointerenter", callback); + m_pointerLeaveCallback = + std::make_unique<qstdweb::EventCallback>(screenElement, "pointerleave", callback); + + emscripten_set_focus_callback(screenElementSelector.constData(), (void *)this, UseCapture, + &focus_cb); + + emscripten_set_wheel_callback(screenElementSelector.constData(), (void *)this, UseCapture, + &wheel_cb); + + emscripten_set_touchstart_callback(screenElementSelector.constData(), (void *)this, UseCapture, + &touchCallback); + emscripten_set_touchend_callback(screenElementSelector.constData(), (void *)this, UseCapture, + &touchCallback); + emscripten_set_touchmove_callback(screenElementSelector.constData(), (void *)this, UseCapture, + &touchCallback); + emscripten_set_touchcancel_callback(screenElementSelector.constData(), (void *)this, UseCapture, + &touchCallback); + + screenElement.call<void>("addEventListener", std::string("drop"), + val::module_property("qtDrop"), val(true)); + screenElement.set("data-qtdropcontext", // ? unique + emscripten::val(quintptr(reinterpret_cast<void *>(screen())))); } void QWasmCompositor::setEnabled(bool enabled) @@ -178,31 +161,18 @@ void QWasmCompositor::startResize(Qt::Edges edges) void QWasmCompositor::addWindow(QWasmWindow *window) { - m_windowVisibility.insert(window, false); m_windowStack.pushWindow(window); m_windowStack.topWindow()->requestActivateWindow(); } void QWasmCompositor::removeWindow(QWasmWindow *window) { - m_windowVisibility.remove(window); m_requestUpdateWindows.remove(window); m_windowStack.removeWindow(window); if (m_windowStack.topWindow()) m_windowStack.topWindow()->requestActivateWindow(); } -void QWasmCompositor::setVisible(QWasmWindow *window, bool visible) -{ - const bool wasVisible = m_windowVisibility[window]; - if (wasVisible == visible) - return; - - m_windowVisibility[window] = visible; - - requestUpdateWindow(window, QWasmCompositor::ExposeEventDelivery); -} - void QWasmCompositor::raise(QWasmWindow *window) { m_windowStack.raise(window); @@ -217,11 +187,11 @@ QWindow *QWasmCompositor::windowAt(QPoint targetPointInScreenCoords, int padding { const auto found = std::find_if( m_windowStack.begin(), m_windowStack.end(), - [this, padding, &targetPointInScreenCoords](const QWasmWindow *window) { + [padding, &targetPointInScreenCoords](const QWasmWindow *window) { const QRect geometry = window->windowFrameGeometry().adjusted(-padding, -padding, padding, padding); - return m_windowVisibility[window] && geometry.contains(targetPointInScreenCoords); + return window->isVisible() && geometry.contains(targetPointInScreenCoords); }); return found != m_windowStack.end() ? (*found)->window() : nullptr; } @@ -231,37 +201,6 @@ QWindow *QWasmCompositor::keyWindow() const return m_windowStack.topWindow() ? m_windowStack.topWindow()->window() : nullptr; } -void QWasmCompositor::blit(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, const QOpenGLTexture *texture, QRect targetGeometry) -{ - QMatrix4x4 m; - m.translate(-1.0f, -1.0f); - - m.scale(2.0f / (float)screen->geometry().width(), - 2.0f / (float)screen->geometry().height()); - - m.translate((float)targetGeometry.width() / 2.0f, - (float)-targetGeometry.height() / 2.0f); - - m.translate(targetGeometry.x(), screen->geometry().height() - targetGeometry.y()); - - m.scale(0.5f * (float)targetGeometry.width(), - 0.5f * (float)targetGeometry.height()); - - blitter->blit(texture->textureId(), m, QOpenGLTextureBlitter::OriginTopLeft); -} - -void QWasmCompositor::drawWindowContent(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, - const QWasmWindow *window) -{ - QWasmBackingStore *backingStore = window->backingStore(); - if (!backingStore) - return; - - QOpenGLTexture const *texture = backingStore->getUpdatedTexture(); - QRect windowCanvasGeometry = window->geometry().translated(-screen->geometry().topLeft()); - blit(blitter, screen, texture, windowCanvasGeometry); -} - void QWasmCompositor::requestUpdateAllWindows() { m_requestUpdateAllWindows = true; @@ -291,9 +230,12 @@ void QWasmCompositor::requestUpdate() static auto frame = [](double frameTime, void *context) -> int { Q_UNUSED(frameTime); + QWasmCompositor *compositor = reinterpret_cast<QWasmCompositor *>(context); + compositor->m_requestAnimationFrameId = -1; compositor->deliverUpdateRequests(); + return 0; }; m_requestAnimationFrameId = emscripten_request_animation_frame(frame, this); @@ -309,8 +251,9 @@ void QWasmCompositor::deliverUpdateRequests() bool requestUpdateAllWindows = m_requestUpdateAllWindows; m_requestUpdateAllWindows = false; - // Update window content, either all windows or a spesific set of windows. Use the correct update - // type: QWindow subclasses expect that requested and delivered updateRequests matches exactly. + // Update window content, either all windows or a spesific set of windows. Use the correct + // update type: QWindow subclasses expect that requested and delivered updateRequests matches + // exactly. m_inDeliverUpdateRequest = true; if (requestUpdateAllWindows) { for (QWasmWindow *window : m_windowStack) { @@ -327,9 +270,7 @@ void QWasmCompositor::deliverUpdateRequests() } } m_inDeliverUpdateRequest = false; - - // Compose window content - frame(); + frame(requestUpdateAllWindows, requestUpdateWindows.keys()); } void QWasmCompositor::deliverUpdateRequest(QWasmWindow *window, UpdateRequestDeliveryType updateType) @@ -344,13 +285,12 @@ void QWasmCompositor::deliverUpdateRequest(QWasmWindow *window, UpdateRequestDel } } -void QWasmCompositor::handleBackingStoreFlush() +void QWasmCompositor::handleBackingStoreFlush(QWindow *window) { - // Request update to flush the updated backing store content, - // unless we are currently processing an update, in which case - // the new content will flushed as a part of that update. + // Request update to flush the updated backing store content, unless we are currently + // processing an update, in which case the new content will flushed as a part of that update. if (!m_inDeliverUpdateRequest) - requestUpdate(); + requestUpdateWindow(asWasmWindow(window)); } int dpiScaled(qreal value) @@ -358,150 +298,17 @@ int dpiScaled(qreal value) return value * (qreal(qt_defaultDpiX()) / 96.0); } -void QWasmCompositor::drawWindowDecorations(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, - const QWasmWindow *window) -{ - int width = window->windowFrameGeometry().width(); - int height = window->windowFrameGeometry().height(); - qreal dpr = window->devicePixelRatio(); - - QImage image(QSize(width * dpr, height * dpr), QImage::Format_ARGB32_Premultiplied); - image.setDevicePixelRatio(dpr); - QPainter painter(&image); - painter.fillRect(QRect(0, 0, width, height), painter.background()); - - window->drawTitleBar(&painter); - - QWasmFrameOptions frameOptions; - frameOptions.rect = QRect(0, 0, width, height); - frameOptions.lineWidth = dpiScaled(4.); - - drawFrameWindow(frameOptions, &painter); - - painter.end(); - - QOpenGLTexture texture(QOpenGLTexture::Target2D); - texture.setMinificationFilter(QOpenGLTexture::Nearest); - texture.setMagnificationFilter(QOpenGLTexture::Nearest); - texture.setWrapMode(QOpenGLTexture::ClampToEdge); - texture.setFormat(QOpenGLTexture::RGBAFormat); - texture.setSize(image.width(), image.height()); - texture.setMipLevels(1); - texture.allocateStorage(QOpenGLTexture::RGBA, QOpenGLTexture::UInt8); - - QOpenGLPixelTransferOptions uploadOptions; - uploadOptions.setAlignment(1); - - texture.create(); - texture.bind(); - - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, image.width(), image.height(), GL_RGBA, - GL_UNSIGNED_BYTE, image.constScanLine(0)); - - QRect windowCanvasGeometry = window->windowFrameGeometry().translated(-screen->geometry().topLeft()); - blit(blitter, screen, &texture, windowCanvasGeometry); -} - -void QWasmCompositor::drawFrameWindow(QWasmFrameOptions options, QPainter *painter) -{ - int x = options.rect.x(); - int y = options.rect.y(); - int w = options.rect.width(); - int h = options.rect.height(); - const QColor &c1 = options.palette.light().color(); - const QColor &c2 = options.palette.shadow().color(); - const QColor &c3 = options.palette.midlight().color(); - const QColor &c4 = options.palette.dark().color(); - const QBrush *fill = nullptr; - - const qreal devicePixelRatio = painter->device()->devicePixelRatio(); - if (!qFuzzyCompare(devicePixelRatio, qreal(1))) { - const qreal inverseScale = qreal(1) / devicePixelRatio; - painter->scale(inverseScale, inverseScale); - x = qRound(devicePixelRatio * x); - y = qRound(devicePixelRatio * y); - w = qRound(devicePixelRatio * w); - h = qRound(devicePixelRatio * h); - } - - QPen oldPen = painter->pen(); - QPoint a[3] = { QPoint(x, y+h-2), QPoint(x, y), QPoint(x+w-2, y) }; - painter->setPen(c1); - painter->drawPolyline(a, 3); - QPoint b[3] = { QPoint(x, y+h-1), QPoint(x+w-1, y+h-1), QPoint(x+w-1, y) }; - painter->setPen(c2); - painter->drawPolyline(b, 3); - if (w > 4 && h > 4) { - QPoint c[3] = { QPoint(x+1, y+h-3), QPoint(x+1, y+1), QPoint(x+w-3, y+1) }; - painter->setPen(c3); - painter->drawPolyline(c, 3); - QPoint d[3] = { QPoint(x+1, y+h-2), QPoint(x+w-2, y+h-2), QPoint(x+w-2, y+1) }; - painter->setPen(c4); - painter->drawPolyline(d, 3); - if (fill) - painter->fillRect(QRect(x+2, y+2, w-4, h-4), *fill); - } - painter->setPen(oldPen); -} - -void QWasmCompositor::drawWindow(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, - const QWasmWindow *window) -{ - if (window->window()->type() != Qt::Popup && !(window->m_windowState & Qt::WindowFullScreen)) - drawWindowDecorations(blitter, screen, window); - drawWindowContent(blitter, screen, window); -} - -void QWasmCompositor::frame() +void QWasmCompositor::frame(bool all, const QList<QWasmWindow *> &windows) { if (!m_isEnabled || m_windowStack.empty() || !screen()) return; - QWasmWindow *someWindow = nullptr; - - for (QWasmWindow *window : m_windowStack) { - if (window->window()->surfaceClass() == QSurface::Window - && qt_window_private(window->window())->receivedExpose) { - someWindow = window; - break; - } - } - - if (!someWindow) - return; - - if (m_context.isNull()) { - m_context.reset(new QOpenGLContext()); - m_context->setFormat(someWindow->window()->requestedFormat()); - m_context->setScreen(screen()->screen()); - m_context->create(); + if (all) { + std::for_each(m_windowStack.rbegin(), m_windowStack.rend(), + [](QWasmWindow *window) { window->paint(); }); + } else { + std::for_each(windows.begin(), windows.end(), [](QWasmWindow *window) { window->paint(); }); } - - bool ok = m_context->makeCurrent(someWindow->window()); - if (!ok) - return; - - if (!m_blitter->isCreated()) - m_blitter->create(); - - qreal dpr = screen()->devicePixelRatio(); - glViewport(0, 0, screen()->geometry().width() * dpr, screen()->geometry().height() * dpr); - - m_context->functions()->glClearColor(0.2, 0.2, 0.2, 1.0); - m_context->functions()->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); - - m_blitter->bind(); - m_blitter->setRedBlueSwizzle(true); - - std::for_each(m_windowStack.rbegin(), m_windowStack.rend(), [this](const QWasmWindow *window) { - if (m_windowVisibility[window]) - drawWindow(m_blitter.data(), screen(), window); - }); - - m_blitter->release(); - - if (someWindow && someWindow->window()->surfaceType() == QSurface::OpenGLSurface) - m_context->swapBuffers(someWindow->window()); } void QWasmCompositor::WindowManipulation::resizeWindow(const QPoint& amount) @@ -533,7 +340,20 @@ void QWasmCompositor::WindowManipulation::resizeWindow(const QPoint& amount) void QWasmCompositor::onTopWindowChanged() { - requestUpdate(); + constexpr int zOrderForElementInFrontOfScreen = 3; + int z = zOrderForElementInFrontOfScreen; + std::for_each(m_windowStack.rbegin(), m_windowStack.rend(), + [&z](QWasmWindow *window) { window->setZOrder(z++); }); + + auto it = m_windowStack.begin(); + if (it == m_windowStack.end()) { + return; + } + (*it)->onActivationChanged(true); + ++it; + for (; it != m_windowStack.end(); ++it) { + (*it)->onActivationChanged(false); + } } QWasmScreen *QWasmCompositor::screen() @@ -541,11 +361,6 @@ QWasmScreen *QWasmCompositor::screen() return static_cast<QWasmScreen *>(parent()); } -QOpenGLContext *QWasmCompositor::context() -{ - return m_context.data(); -} - int QWasmCompositor::keyboard_cb(int eventType, const EmscriptenKeyboardEvent *keyEvent, void *userData) { QWasmCompositor *wasmCompositor = reinterpret_cast<QWasmCompositor *>(userData); @@ -594,7 +409,8 @@ bool QWasmCompositor::processPointer(const PointerEvent& event) const bool pointerIsWithinTargetWindowBounds = targetWindow->geometry().contains(event.point); const bool isTargetWindowBlocked = QGuiApplicationPrivate::instance()->isWindowBlocked(targetWindow); - if (m_mouseInCanvas && m_windowUnderMouse != targetWindow && pointerIsWithinTargetWindowBounds) { + if (m_mouseInScreen && m_windowUnderMouse != targetWindow + && pointerIsWithinTargetWindowBounds) { // delayed mouse enter enterWindow(targetWindow, pointInTargetWindowCoords, event.point); m_windowUnderMouse = targetWindow; @@ -607,36 +423,19 @@ bool QWasmCompositor::processPointer(const PointerEvent& event) switch (event.type) { case EventType::PointerDown: { - screen()->canvas().call<void>("setPointerCapture", event.pointerId); + screen()->element().call<void>("setPointerCapture", event.pointerId); if (targetWindow) targetWindow->requestActivate(); - m_pressedWindow = targetWindow; - m_windowManipulation.onPointerDown(event, targetWindow); - - wasmTargetWindow->injectMousePressed(pointInTargetWindowCoords, event.point, - event.mouseButton, event.modifiers); break; } case EventType::PointerUp: { - screen()->canvas().call<void>("releasePointerCapture", event.pointerId); + screen()->element().call<void>("releasePointerCapture", event.pointerId); m_windowManipulation.onPointerUp(event); - - if (m_pressedWindow) { - // Always deliver the released event to the same window that was pressed - asWasmWindow(m_pressedWindow) - ->injectMouseReleased(pointInTargetWindowCoords, event.point, event.mouseButton, - event.modifiers); - if (event.mouseButton == Qt::MouseButton::LeftButton) - m_pressedWindow = nullptr; - } else { - wasmTargetWindow->injectMouseReleased(pointInTargetWindowCoords, event.point, - event.mouseButton, event.modifiers); - } break; } case EventType::PointerMove: @@ -853,7 +652,7 @@ void QWasmCompositor::WindowManipulation::startResize(Qt::Edges edges) window->maximumHeight() - window->geometry().height()), }, }); - m_screen->canvas().call<void>("setPointerCapture", m_systemDragInitData.lastMousePointerId); + m_screen->element().call<void>("setPointerCapture", m_systemDragInitData.lastMousePointerId); } bool QWasmCompositor::processKeyboard(int eventType, const EmscriptenKeyboardEvent *emKeyEvent) @@ -906,8 +705,9 @@ bool QWasmCompositor::processWheel(int eventType, const EmscriptenWheelEvent *wh scrollFactor = -scrollFactor; // Web scroll deltas are inverted from Qt deltas. Qt::KeyboardModifiers modifiers = KeyboardModifier::getForEvent(*mouseEvent); - QPoint targetPointInCanvasCoords(mouseEvent->targetX, mouseEvent->targetY); - QPoint targetPointInScreenCoords = screen()->geometry().topLeft() + targetPointInCanvasCoords; + QPoint targetPointInScreenElementCoords(mouseEvent->targetX, mouseEvent->targetY); + QPoint targetPointInScreenCoords = + screen()->geometry().topLeft() + targetPointInScreenElementCoords; QWindow *targetWindow = screen()->compositor()->windowAt(targetPointInScreenCoords, 5); if (!targetWindow) @@ -939,8 +739,9 @@ bool QWasmCompositor::processTouch(int eventType, const EmscriptenTouchEvent *to const EmscriptenTouchPoint *touches = &touchEvent->touches[i]; - QPoint targetPointInCanvasCoords(touches->targetX, touches->targetY); - QPoint targetPointInScreenCoords = screen()->geometry().topLeft() + targetPointInCanvasCoords; + QPoint targetPointInScreenElementCoords(touches->targetX, touches->targetY); + QPoint targetPointInScreenCoords = + screen()->geometry().topLeft() + targetPointInScreenElementCoords; targetWindow = screen()->compositor()->windowAt(targetPointInScreenCoords, 5); if (targetWindow == nullptr) @@ -1033,13 +834,13 @@ void QWasmCompositor::enterWindow(QWindow *window, const QPoint &pointInTargetWi bool QWasmCompositor::processMouseEnter(const EmscriptenMouseEvent *mouseEvent) { Q_UNUSED(mouseEvent) - // mouse has entered the canvas area - m_mouseInCanvas = true; + // mouse has entered the screen area + m_mouseInScreen = true; return true; } bool QWasmCompositor::processMouseLeave() { - m_mouseInCanvas = false; + m_mouseInScreen = false; return true; } diff --git a/src/plugins/platforms/wasm/qwasmcompositor.h b/src/plugins/platforms/wasm/qwasmcompositor.h index 225bf90fe4..9ea0761771 100644 --- a/src/plugins/platforms/wasm/qwasmcompositor.h +++ b/src/plugins/platforms/wasm/qwasmcompositor.h @@ -6,13 +6,9 @@ #include "qwasmwindowstack.h" -#include <QtGui/qregion.h> #include <qpa/qplatformwindow.h> #include <QMap> -#include <QtOpenGL/qopengltextureblitter.h> -#include <QtGui/qpalette.h> -#include <QtGui/qpainter.h> #include <QtGui/qinputdevice.h> #include <QtCore/private/qstdweb_p.h> @@ -61,7 +57,6 @@ public: QWindow *keyWindow() const; QWasmScreen *screen(); - QOpenGLContext *context(); enum UpdateRequestDeliveryType { ExposeEventDelivery, UpdateRequestDelivery }; void requestUpdateAllWindows(); @@ -70,10 +65,7 @@ public: void setCapture(QWasmWindow *window); void releaseCapture(); - void handleBackingStoreFlush(); - -private slots: - void frame(); + void handleBackingStoreFlush(QWindow *window); private: class WindowManipulation { @@ -125,7 +117,10 @@ private: std::unique_ptr<OperationState> m_state; }; + void frame(bool all, const QList<QWasmWindow *> &windows); + void onTopWindowChanged(); + void deregisterEventHandlers(); void destroy(); @@ -133,16 +128,6 @@ private: void deliverUpdateRequests(); void deliverUpdateRequest(QWasmWindow *window, UpdateRequestDeliveryType updateType); - void drawWindow(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, const QWasmWindow *window); - void drawWindowContent(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, - const QWasmWindow *window); - void blit(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, const QOpenGLTexture *texture, QRect targetGeometry); - - void drawWindowDecorations(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, - const QWasmWindow *window); - - void drawFrameWindow(QWasmFrameOptions options, QPainter *painter); - static int keyboard_cb(int eventType, const EmscriptenKeyboardEvent *keyEvent, void *userData); static int focus_cb(int eventType, const EmscriptenFocusEvent *focusEvent, void *userData); static int wheel_cb(int eventType, const EmscriptenWheelEvent *wheelEvent, void *userData); @@ -164,10 +149,6 @@ private: WindowManipulation m_windowManipulation; QWasmWindowStack m_windowStack; - QScopedPointer<QOpenGLContext> m_context; - QScopedPointer<QOpenGLTextureBlitter> m_blitter; - - QHash<const QWasmWindow *, bool> m_windowVisibility; bool m_isEnabled = true; QSize m_targetSize; qreal m_targetDevicePixelRatio = 1; @@ -176,7 +157,6 @@ private: int m_requestAnimationFrameId = -1; bool m_inDeliverUpdateRequest = false; - QPointer<QWindow> m_pressedWindow; QPointer<QWindow> m_lastMouseTargetWindow; QPointer<QWindow> m_mouseCaptureWindow; @@ -194,7 +174,7 @@ private: std::unique_ptr<QWasmEventTranslator> m_eventTranslator; - bool m_mouseInCanvas = false; + bool m_mouseInScreen = false; QPointer<QWindow> m_windowUnderMouse; }; diff --git a/src/plugins/platforms/wasm/qwasmcssstyle.cpp b/src/plugins/platforms/wasm/qwasmcssstyle.cpp new file mode 100644 index 0000000000..0f31d8abde --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmcssstyle.cpp @@ -0,0 +1,181 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "qwasmcssstyle.h" + +#include <QtCore/qstring.h> +#include <QtCore/qfile.h> + +QT_BEGIN_NAMESPACE + +namespace { +const char *Style = R"css( +.qt-screen { + --border-width: 4px; + + position: relative; + border: none; + caret-color: transparent; + cursor: default; + width: 100%; + height: 100%; + overflow: hidden; + outline: none; +} + +.qt-window { + box-shadow: rgb(0 0 0 / 20%) 0px 10px 16px 0px, rgb(0 0 0 / 19%) 0px 6px 20px 0px; + pointer-events: none; + position: absolute; + background-color: lightgray; +} + +.qt-window.has-title-bar { + border: var(--border-width) solid lightgray; + caret-color: transparent; +} + +.title-bar { + display: none; + align-items: center; + overflow: hidden; + height: 18px; + padding-bottom: 4px; +} + +.qt-window.has-title-bar .title-bar { + display: flex; +} + +.title-bar .window-name { + font-family: 'Lucida Grande'; + white-space: nowrap; + user-select: none; + overflow: hidden; +} + +.title-bar .spacer { + flex-grow: 1 +} + +.qt-window.inactive .title-bar { + opacity: 0.35; +} + +.qt-window-canvas-container { + display: flex; +} + +.title-bar .image-button { + width: 18px; + height: 18px; + display: flex; + justify-content: center; + user-select: none; + align-items: center; +} + +.title-bar .image-button span { + width: 10px; + height: 10px; + user-select: none; + pointer-events: none; + -webkit-user-drag: none; + background-size: 10px 10px; +} + +.title-bar .image-button span[qt-builtin-image-type=x] { + background-image: url("data:image/svg+xml;base64,$close_icon"); +} + +.title-bar .image-button span[qt-builtin-image-type=qt-logo] { + background-image: url("qtlogo.svg"); +} + +.title-bar .image-button span[qt-builtin-image-type=restore] { + background-image: url("data:image/svg+xml;base64,$restore_icon"); +} + +.title-bar .image-button span[qt-builtin-image-type=maximize] { + background-image: url("data:image/svg+xml;base64,$maximize_icon"); +} +.title-bar .action-button { + pointer-events: all; + align-self: end; +} + +.qt-window.blocked .title-bar .action-button { + pointer-events: none; +} + +.title-bar .action-button span { + transition: filter 0.08s ease-out; +} + +.title-bar .action-button:hover span { + filter: invert(0.45); +} + +.title-bar .action-button:active span { + filter: invert(0.6); +} + +)css"; + +class Base64IconStore +{ +public: + enum class IconType { + Maximize, + First = Maximize, + QtLogo, + Restore, + X, + Size, + }; + + Base64IconStore() + { + QString iconSources[static_cast<size_t>(IconType::Size)] = { + QStringLiteral(":/wasm-window/maximize.svg"), + QStringLiteral(":/wasm-window/qtlogo.svg"), QStringLiteral(":/wasm-window/restore.svg"), + QStringLiteral(":/wasm-window/x.svg") + }; + + for (size_t iconType = static_cast<size_t>(IconType::First); + iconType < static_cast<size_t>(IconType::Size); ++iconType) { + QFile svgFile(iconSources[static_cast<size_t>(iconType)]); + if (!svgFile.open(QIODevice::ReadOnly)) + Q_ASSERT(false); // A resource should always be opened. + m_storage[static_cast<size_t>(iconType)] = svgFile.readAll().toBase64(); + } + } + ~Base64IconStore() = default; + + std::string_view getIcon(IconType type) const { return m_storage[static_cast<size_t>(type)]; } + +private: + std::string m_storage[static_cast<size_t>(IconType::Size)]; +}; + +void replace(std::string &str, const std::string &from, const std::string_view &to) +{ + str.replace(str.find(from), from.length(), to); +} +} // namespace + +emscripten::val QWasmCSSStyle::createStyleElement(emscripten::val parent) +{ + Base64IconStore store; + auto document = parent["ownerDocument"]; + auto screenStyle = document.call<emscripten::val>("createElement", emscripten::val("style")); + auto text = std::string(Style); + replace(text, "$close_icon", store.getIcon(Base64IconStore::IconType::X)); + replace(text, "$restore_icon", store.getIcon(Base64IconStore::IconType::Restore)); + replace(text, "$maximize_icon", store.getIcon(Base64IconStore::IconType::Maximize)); + + screenStyle.set("textContent", text); + return screenStyle; +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmcssstyle.h b/src/plugins/platforms/wasm/qwasmcssstyle.h new file mode 100644 index 0000000000..fc4cc2d54c --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmcssstyle.h @@ -0,0 +1,18 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef QWASMCSSSTYLE_H +#define QWASMCSSSTYLE_H + +#include <QtCore/qglobal.h> + +#include <emscripten/val.h> + +QT_BEGIN_NAMESPACE + +namespace QWasmCSSStyle { +emscripten::val createStyleElement(emscripten::val parent); +} + +QT_END_NAMESPACE +#endif // QWASMINLINESTYLEREGISTRY_H diff --git a/src/plugins/platforms/wasm/qwasmcursor.cpp b/src/plugins/platforms/wasm/qwasmcursor.cpp index f954e005ce..e159b8fe7d 100644 --- a/src/plugins/platforms/wasm/qwasmcursor.cpp +++ b/src/plugins/platforms/wasm/qwasmcursor.cpp @@ -117,10 +117,7 @@ QByteArray QWasmCursor::cursorShapeToHtml(Qt::CursorShape shape) void QWasmCursor::setWasmCursor(QScreen *screen, const QByteArray &name) { - // Set cursor on the canvas - val canvas = QWasmScreen::get(screen)->canvas(); - val canvasStyle = canvas["style"]; - canvasStyle.set("cursor", val(name.constData())); + QWasmScreen::get(screen)->element()["style"].set("cursor", val(name.constData())); } void QWasmCursor::setOverrideWasmCursor(const QCursor &windowCursor, QScreen *screen) diff --git a/src/plugins/platforms/wasm/qwasminputcontext.cpp b/src/plugins/platforms/wasm/qwasminputcontext.cpp index 94809a3b17..1bf8b5f168 100644 --- a/src/plugins/platforms/wasm/qwasminputcontext.cpp +++ b/src/plugins/platforms/wasm/qwasminputcontext.cpp @@ -89,14 +89,14 @@ void QWasmInputContext::focusWindowChanged(QWindow *focusWindow) m_focusWindow = focusWindow; } -emscripten::val QWasmInputContext::focusCanvas() +emscripten::val QWasmInputContext::focusScreenElement() { if (!m_focusWindow) return emscripten::val::undefined(); QScreen *screen = m_focusWindow->screen(); if (!screen) return emscripten::val::undefined(); - return QWasmScreen::get(screen)->canvas(); + return QWasmScreen::get(screen)->element(); } void QWasmInputContext::update(Qt::InputMethodQueries queries) @@ -111,20 +111,20 @@ void QWasmInputContext::showInputPanel() return; // this is called each time the keyboard is touched - // Add the input element as a child of the canvas for the + // Add the input element as a child of the screen for the // currently focused window and give it focus. The browser // will not display the input element, but mobile browsers // should display the virtual keyboard. Key events will be // captured by the keyboard event handler installed on the - // canvas. + // screen element. if (platform() == Platform::MacOS // keep for compatibility || platform() == Platform::iPhone || platform() == Platform::Windows) { - emscripten::val canvas = focusCanvas(); - if (canvas == emscripten::val::undefined()) + emscripten::val screenElement = focusScreenElement(); + if (screenElement.isUndefined()) return; - canvas.call<void>("appendChild", m_inputElement); + screenElement.call<void>("appendChild", m_inputElement); } m_inputElement.call<void>("focus"); diff --git a/src/plugins/platforms/wasm/qwasminputcontext.h b/src/plugins/platforms/wasm/qwasminputcontext.h index 58f3920c30..0886ae8d84 100644 --- a/src/plugins/platforms/wasm/qwasminputcontext.h +++ b/src/plugins/platforms/wasm/qwasminputcontext.h @@ -29,10 +29,11 @@ public: bool isValid() const override { return true; } void focusWindowChanged(QWindow *focusWindow); - emscripten::val focusCanvas(); void inputStringChanged(QString &, QWasmInputContext *context); private: + emscripten::val focusScreenElement(); + bool m_inputPanelVisible = false; QPointer<QWindow> m_focusWindow; diff --git a/src/plugins/platforms/wasm/qwasmintegration.cpp b/src/plugins/platforms/wasm/qwasmintegration.cpp index 39eea05ad3..5eb5030e63 100644 --- a/src/plugins/platforms/wasm/qwasmintegration.cpp +++ b/src/plugins/platforms/wasm/qwasmintegration.cpp @@ -85,58 +85,44 @@ QWasmIntegration::QWasmIntegration() { s_instance = this; - touchPoints = emscripten::val::global("navigator")["maxTouchPoints"].as<int>(); + touchPoints = emscripten::val::global("navigator")["maxTouchPoints"].as<int>(); - // Create screens for container elements. Each container element can be a div element (preferred), - // or a canvas element (legacy). Qt versions prior to 6.x read the "qtCanvasElements" module property, - // which we continue to do to preserve compatibility. The preferred property is now "qtContainerElements". + // Create screens for container elements. Each container element will ultimately become a + // div element. Qt historically supported supplying canvas for screen elements - these elements + // will be transformed into divs and warnings about deprecation will be printed. See + // QWasmScreen ctor. emscripten::val qtContainerElements = val::module_property("qtContainerElements"); - emscripten::val qtCanvasElements = val::module_property("qtCanvasElements"); - if (!qtContainerElements.isUndefined()) { - emscripten::val length = qtContainerElements["length"]; - int count = length.as<int>(); - if (length.isUndefined()) - qWarning("qtContainerElements does not have the length property set. Qt expects an array of html elements (possibly containing one element only)"); - for (int i = 0; i < count; ++i) { + if (qtContainerElements.isArray()) { + for (int i = 0; i < qtContainerElements["length"].as<int>(); ++i) { emscripten::val element = qtContainerElements[i].as<emscripten::val>(); - if (element.isNull() ||element.isUndefined()) { - qWarning() << "Skipping null or undefined element in qtContainerElements"; - } else { + if (element.isNull() || element.isUndefined()) + qWarning() << "Skipping null or undefined element in qtContainerElements"; + else addScreen(element); - } } - } else if (!qtCanvasElements.isUndefined()) { - qWarning() << "The qtCanvaseElements property is deprecated. Qt will stop reading" - << "it in some future version, please use qtContainerElements instead"; - emscripten::val length = qtCanvasElements["length"]; - int count = length.as<int>(); - for (int i = 0; i < count; ++i) - addScreen(qtCanvasElements[i].as<emscripten::val>()); } else { // No screens, which may or may not be intended - qWarning() << "Note: The qtContainerElements module property was not set. Proceeding with no screens."; + qWarning() << "The qtContainerElements module property was not set or is invalid. " + "Proceeding with no screens."; } // install browser window resize handler - auto onWindowResize = [](int eventType, const EmscriptenUiEvent *e, void *userData) -> int { - Q_UNUSED(eventType); - Q_UNUSED(e); - Q_UNUSED(userData); - - // This resize event is called when the HTML window is resized. Depending - // on the page layout the canvas(es) might also have been resized, so we - // update the Qt screen sizes (and canvas render sizes). - if (QWasmIntegration *integration = QWasmIntegration::get()) - integration->resizeAllScreens(); - return 0; - }; - emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, EM_TRUE, onWindowResize); + emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, EM_TRUE, + [](int, const EmscriptenUiEvent *, void *) -> int { + // This resize event is called when the HTML window is + // resized. Depending on the page layout the elements might + // also have been resized, so we update the Qt screen sizes + // (and canvas render sizes). + if (QWasmIntegration *integration = QWasmIntegration::get()) + integration->resizeAllScreens(); + return 0; + }); // install visualViewport resize handler which picks up size and scale change on mobile. emscripten::val visualViewport = emscripten::val::global("window")["visualViewport"]; if (!visualViewport.isUndefined()) { visualViewport.call<void>("addEventListener", val("resize"), - val::module_property("qtResizeAllScreens")); + val::module_property("qtResizeAllScreens")); } m_drag = new QWasmDrag(); } diff --git a/src/plugins/platforms/wasm/qwasmoffscreensurface.cpp b/src/plugins/platforms/wasm/qwasmoffscreensurface.cpp index 79ec9af62b..4a1fc16eca 100644 --- a/src/plugins/platforms/wasm/qwasmoffscreensurface.cpp +++ b/src/plugins/platforms/wasm/qwasmoffscreensurface.cpp @@ -11,9 +11,6 @@ QWasmOffscrenSurface::QWasmOffscrenSurface(QOffscreenSurface *offscreenSurface) } -QWasmOffscrenSurface::~QWasmOffscrenSurface() -{ - -} +QWasmOffscrenSurface::~QWasmOffscrenSurface() = default; QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmopenglcontext.cpp b/src/plugins/platforms/wasm/qwasmopenglcontext.cpp index df72397211..3dfb201367 100644 --- a/src/plugins/platforms/wasm/qwasmopenglcontext.cpp +++ b/src/plugins/platforms/wasm/qwasmopenglcontext.cpp @@ -34,18 +34,19 @@ QWasmOpenGLContext::QWasmOpenGLContext(const QSurfaceFormat &format) QWasmOpenGLContext::~QWasmOpenGLContext() { - if (m_context) { - // Destroy GL context. Work around bug in emscripten_webgl_destroy_context - // which removes all event handlers on the canvas by temporarily replacing the function - // that does the removal with a function that does nothing. - emscripten::val jsEvents = emscripten::val::module_property("JSEvents"); - emscripten::val savedRemoveAllHandlersOnTargetFunction = - jsEvents["removeAllHandlersOnTarget"]; - jsEvents.set("removeAllHandlersOnTarget", emscripten::val::module_property("qtDoNothing")); - emscripten_webgl_destroy_context(m_context); - jsEvents.set("removeAllHandlersOnTarget", savedRemoveAllHandlersOnTargetFunction); - m_context = 0; - } + if (!m_context) + return; + + // Destroy GL context. Work around bug in emscripten_webgl_destroy_context + // which removes all event handlers on the canvas by temporarily replacing the function + // that does the removal with a function that does nothing. + emscripten::val jsEvents = emscripten::val::module_property("JSEvents"); + emscripten::val savedRemoveAllHandlersOnTargetFunction = + jsEvents["removeAllHandlersOnTarget"]; + jsEvents.set("removeAllHandlersOnTarget", emscripten::val::module_property("qtDoNothing")); + emscripten_webgl_destroy_context(m_context); + jsEvents.set("removeAllHandlersOnTarget", savedRemoveAllHandlersOnTargetFunction); + m_context = 0; } bool QWasmOpenGLContext::isOpenGLVersionSupported(QSurfaceFormat format) @@ -60,25 +61,23 @@ bool QWasmOpenGLContext::isOpenGLVersionSupported(QSurfaceFormat format) bool QWasmOpenGLContext::maybeCreateEmscriptenContext(QPlatformSurface *surface) { - // Native emscripten/WebGL contexts are tied to a single screen/canvas. The first - // call to this function creates a native canvas for the given screen, subsequent - // calls verify that the surface is on/off the same screen. - QPlatformScreen *screen = surface->screen(); - if (m_context && !screen) - return false; // Alternative: return true to support makeCurrent on QOffScreenSurface with - // no screen. However, Qt likes to substitute QGuiApplication::primaryScreen() - // for null screens, which foils this plan. - if (!screen) + if (m_context && m_surface == surface) + return true; + + // TODO(mikolajboc): Use OffscreenCanvas if available. + if (surface->surface()->surfaceClass() == QSurface::Offscreen) return false; - if (m_context) - return m_screen == screen; - m_context = createEmscriptenContext(QWasmScreen::get(screen)->canvasTargetId(), m_requestedFormat); - m_screen = screen; + m_surface = surface; + + auto *window = static_cast<QWasmWindow *>(surface); + m_context = createEmscriptenContext(window->canvasSelector(), m_requestedFormat); return true; } -EMSCRIPTEN_WEBGL_CONTEXT_HANDLE QWasmOpenGLContext::createEmscriptenContext(const QString &canvasTargetId, QSurfaceFormat format) +EMSCRIPTEN_WEBGL_CONTEXT_HANDLE +QWasmOpenGLContext::createEmscriptenContext(const std::string &canvasSelector, + QSurfaceFormat format) { EmscriptenWebGLContextAttributes attributes; emscripten_webgl_init_context_attributes(&attributes); // Populate with default attributes @@ -92,17 +91,14 @@ EMSCRIPTEN_WEBGL_CONTEXT_HANDLE QWasmOpenGLContext::createEmscriptenContext(cons // WebGL doesn't allow separate attach buffers to STENCIL_ATTACHMENT and DEPTH_ATTACHMENT // we need both or none - bool useDepthStencil = (format.depthBufferSize() > 0 || format.stencilBufferSize() > 0); + const bool useDepthStencil = (format.depthBufferSize() > 0 || format.stencilBufferSize() > 0); // WebGL offers enable/disable control but not size control for these attributes.alpha = format.alphaBufferSize() > 0; attributes.depth = useDepthStencil; attributes.stencil = useDepthStencil; - QByteArray convasSelector = canvasTargetId.toUtf8(); - EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context = emscripten_webgl_create_context(convasSelector.constData(), &attributes); - - return context; + return emscripten_webgl_create_context(canvasSelector.c_str(), &attributes); } QSurfaceFormat QWasmOpenGLContext::format() const @@ -117,8 +113,7 @@ GLuint QWasmOpenGLContext::defaultFramebufferObject(QPlatformSurface *surface) c bool QWasmOpenGLContext::makeCurrent(QPlatformSurface *surface) { - bool ok = maybeCreateEmscriptenContext(surface); - if (!ok) + if (!maybeCreateEmscriptenContext(surface)) return false; return emscripten_webgl_make_context_current(m_context) == EMSCRIPTEN_RESULT_SUCCESS; @@ -142,7 +137,7 @@ bool QWasmOpenGLContext::isSharing() const bool QWasmOpenGLContext::isValid() const { - if (!(isOpenGLVersionSupported(m_requestedFormat))) + if (!isOpenGLVersionSupported(m_requestedFormat)) return false; // Note: we get isValid() calls before we see the surface and can diff --git a/src/plugins/platforms/wasm/qwasmopenglcontext.h b/src/plugins/platforms/wasm/qwasmopenglcontext.h index 9cd10f3ea6..ac456d90e4 100644 --- a/src/plugins/platforms/wasm/qwasmopenglcontext.h +++ b/src/plugins/platforms/wasm/qwasmopenglcontext.h @@ -27,10 +27,11 @@ public: private: static bool isOpenGLVersionSupported(QSurfaceFormat format); bool maybeCreateEmscriptenContext(QPlatformSurface *surface); - static EMSCRIPTEN_WEBGL_CONTEXT_HANDLE createEmscriptenContext(const QString &canvasId, QSurfaceFormat format); + static EMSCRIPTEN_WEBGL_CONTEXT_HANDLE + createEmscriptenContext(const std::string &canvasSelector, QSurfaceFormat format); QSurfaceFormat m_requestedFormat; - QPlatformScreen *m_screen = nullptr; + QPlatformSurface *m_surface = nullptr; EMSCRIPTEN_WEBGL_CONTEXT_HANDLE m_context = 0; }; diff --git a/src/plugins/platforms/wasm/qwasmscreen.cpp b/src/plugins/platforms/wasm/qwasmscreen.cpp index 4a7cd988c3..578afb75cf 100644 --- a/src/plugins/platforms/wasm/qwasmscreen.cpp +++ b/src/plugins/platforms/wasm/qwasmscreen.cpp @@ -7,6 +7,7 @@ #include "qwasmcompositor.h" #include "qwasmintegration.h" #include "qwasmstring.h" +#include "qwasmcssstyle.h" #include <emscripten/bind.h> #include <emscripten/val.h> @@ -22,73 +23,71 @@ QT_BEGIN_NAMESPACE using namespace emscripten; -const char * QWasmScreen::m_canvasResizeObserverCallbackContextPropertyName = "data-qtCanvasResizeObserverCallbackContext"; +const char *QWasmScreen::m_canvasResizeObserverCallbackContextPropertyName = + "data-qtCanvasResizeObserverCallbackContext"; QWasmScreen::QWasmScreen(const emscripten::val &containerOrCanvas) - : m_container(containerOrCanvas) - , m_canvas(emscripten::val::undefined()) - , m_compositor(new QWasmCompositor(this)) - , m_eventTranslator(new QWasmEventTranslator()) + : m_container(containerOrCanvas), + m_shadowContainer(emscripten::val::undefined()), + m_compositor(new QWasmCompositor(this)), + m_eventTranslator(new QWasmEventTranslator()) { - // Each screen is backed by a html canvas element. Use either - // a user-supplied canvas or create one as a child of the user- - // supplied root element. - std::string tagName = containerOrCanvas["tagName"].as<std::string>(); - if (tagName == "CANVAS" || tagName == "canvas") { - m_canvas = containerOrCanvas; - } else { - // Create the canvas (for the correct document) as a child of the container - m_canvas = containerOrCanvas["ownerDocument"].call<emscripten::val>("createElement", std::string("canvas")); - containerOrCanvas.call<void>("appendChild", m_canvas); - std::string screenId = std::string("qtcanvas_") + std::to_string(uintptr_t(this)); - m_canvas.set("id", screenId); - - // Make the canvas occupy 100% of parent - emscripten::val style = m_canvas["style"]; - style.set("width", std::string("100%")); - style.set("height", std::string("100%")); + auto document = m_container["ownerDocument"]; + // Each screen is represented by a div container. All of the windows exist therein as + // its children. Qt versions < 6.5 used to represent screens as canvas. Support that by + // transforming the canvas into a div. + if (m_container["tagName"].call<std::string>("toLowerCase") == "canvas") { + qWarning() << "Support for canvas elements as an element backing screen is deprecated. The " + "canvas provided for the screen will be transformed into a div."; + auto container = document.call<emscripten::val>("createElement", emscripten::val("div")); + m_container["parentNode"].call<void>("replaceChild", m_container, container); + m_container = container; } + auto shadowOptions = emscripten::val::object(); + shadowOptions.set("mode", "open"); + auto shadow = m_container.call<emscripten::val>("attachShadow", shadowOptions); - emscripten::val style = m_canvas["style"]; + m_shadowContainer = document.call<emscripten::val>("createElement", emscripten::val("div")); - // Configure container and canvas for accessibility support: set "position: relative" - // so that a11y child elements can be positioned with "position: absolute", and hide - // the canvas from screen readers. - m_container["style"].set("position", std::string("relative")); - m_canvas.call<void>("setAttribute", std::string("aria-hidden"), std::string("true")); // FIXME make the canvas non-focusable, as required by the aria-hidden role - style.set("z-index", std::string("1")); // a11y elements are at 0 + shadow.call<void>("appendChild", QWasmCSSStyle::createStyleElement(m_shadowContainer)); - style.set("border", std::string("0px none")); - style.set("background-color", std::string("white")); + shadow.call<void>("appendChild", m_shadowContainer); + + m_shadowContainer.set("id", std::string("qt-screen-") + std::to_string(uintptr_t(this))); + + m_shadowContainer["classList"].call<void>("add", std::string("qt-screen")); // Set contenteditable so that the canvas gets clipboard events, // then hide the resulting focus frame, and reset the cursor. - m_canvas.set("contentEditable", std::string("true")); + m_shadowContainer.set("contentEditable", std::string("true")); // set inputmode to none to stop mobile keyboard opening // when user clicks anywhere on the canvas. - m_canvas.set("inputmode", std::string("none")); - style.set("outline", std::string("0px solid transparent")); - style.set("caret-color", std::string("transparent")); - style.set("cursor", std::string("default")); + m_shadowContainer.set("inputmode", std::string("none")); + + // Hide the canvas from screen readers. + m_shadowContainer.call<void>("setAttribute", std::string("aria-hidden"), std::string("true")); // Disable the default context menu; Qt applications typically // provide custom right-click behavior. - m_onContextMenu = std::make_unique<qstdweb::EventCallback>(m_canvas, "contextmenu", [](emscripten::val event){ - event.call<void>("preventDefault"); - }); + m_onContextMenu = std::make_unique<qstdweb::EventCallback>( + m_shadowContainer, "contextmenu", + [](emscripten::val event) { event.call<void>("preventDefault"); }); // Create "specialHTMLTargets" mapping for the canvas - the element might be unreachable based // on its id only under some conditions, like the target being embedded in a shadow DOM or a // subframe. emscripten::val::module_property("specialHTMLTargets") - .set(canvasTargetId().toStdString(), m_canvas); + .set(eventTargetId().toStdString(), m_shadowContainer); + + emscripten::val::module_property("specialHTMLTargets") + .set(outerScreenId().toStdString(), m_container); // Install event handlers on the container/canvas. This must be // done after the canvas has been created above. m_compositor->initEventHandlers(); updateQScreenAndCanvasRenderSize(); - m_canvas.call<void>("focus"); + m_shadowContainer.call<void>("focus"); } QWasmScreen::~QWasmScreen() @@ -96,9 +95,10 @@ QWasmScreen::~QWasmScreen() Q_ASSERT(!m_compositor); // deleteScreen should have been called to remove this screen emscripten::val::module_property("specialHTMLTargets") - .set(canvasTargetId().toStdString(), emscripten::val::undefined()); + .set(eventTargetId().toStdString(), emscripten::val::undefined()); - m_canvas.set(m_canvasResizeObserverCallbackContextPropertyName, emscripten::val(intptr_t(0))); + m_shadowContainer.set(m_canvasResizeObserverCallbackContextPropertyName, + emscripten::val(intptr_t(0))); } void QWasmScreen::deleteScreen() @@ -132,29 +132,23 @@ QWasmEventTranslator *QWasmScreen::eventTranslator() return m_eventTranslator.get(); } -emscripten::val QWasmScreen::container() const -{ - return m_container; -} - -emscripten::val QWasmScreen::canvas() const +emscripten::val QWasmScreen::element() const { - return m_canvas; + return m_shadowContainer; } -// Returns the html element id for the screen's canvas. -QString QWasmScreen::canvasId() const -{ - return QWasmString::toQString(m_canvas["id"]); -} - -QString QWasmScreen::canvasTargetId() const +QString QWasmScreen::eventTargetId() const { // Return a globally unique id for the canvas. We can choose any string, // as long as it starts with a "!". return QString("!qtcanvas_%1").arg(uintptr_t(this)); } +QString QWasmScreen::outerScreenId() const +{ + return QString("!outerscreen_%1").arg(uintptr_t(this)); +} + QRect QWasmScreen::geometry() const { return m_geometry; @@ -204,7 +198,7 @@ qreal QWasmScreen::devicePixelRatio() const QString QWasmScreen::name() const { - return canvasId(); + return QWasmString::toQString(m_shadowContainer["id"]); } QPlatformCursor *QWasmScreen::cursor() const @@ -244,7 +238,8 @@ void QWasmScreen::invalidateSize() void QWasmScreen::setGeometry(const QRect &rect) { m_geometry = rect; - QWindowSystemInterface::handleScreenGeometryChange(QPlatformScreen::screen(), geometry(), availableGeometry()); + QWindowSystemInterface::handleScreenGeometryChange(QPlatformScreen::screen(), geometry(), + availableGeometry()); resizeMaximizedWindows(); } @@ -256,27 +251,26 @@ void QWasmScreen::updateQScreenAndCanvasRenderSize() // size must be set manually and is not auto-updated on CSS size change. // Setting the render size to a value larger than the CSS size enables high-dpi // rendering. - - QByteArray canvasSelector = canvasTargetId().toUtf8(); double css_width; double css_height; - emscripten_get_element_css_size(canvasSelector.constData(), &css_width, &css_height); + emscripten_get_element_css_size(outerScreenId().toUtf8().constData(), &css_width, &css_height); QSizeF cssSize(css_width, css_height); QSizeF canvasSize = cssSize * devicePixelRatio(); - m_canvas.set("width", canvasSize.width()); - m_canvas.set("height", canvasSize.height()); + m_shadowContainer.set("width", canvasSize.width()); + m_shadowContainer.set("height", canvasSize.height()); // Returns the html elements document/body position auto getElementBodyPosition = [](const emscripten::val &element) -> QPoint { - emscripten::val bodyRect = element["ownerDocument"]["body"].call<emscripten::val>("getBoundingClientRect"); + emscripten::val bodyRect = + element["ownerDocument"]["body"].call<emscripten::val>("getBoundingClientRect"); emscripten::val canvasRect = element.call<emscripten::val>("getBoundingClientRect"); - return QPoint (canvasRect["left"].as<int>() - bodyRect["left"].as<int>(), - canvasRect["top"].as<int>() - bodyRect["top"].as<int>()); + return QPoint(canvasRect["left"].as<int>() - bodyRect["left"].as<int>(), + canvasRect["top"].as<int>() - bodyRect["top"].as<int>()); }; - setGeometry(QRect(getElementBodyPosition(m_canvas), cssSize.toSize())); + setGeometry(QRect(getElementBodyPosition(m_shadowContainer), cssSize.toSize())); m_compositor->requestUpdateAllWindows(); } @@ -286,20 +280,23 @@ void QWasmScreen::canvasResizeObserverCallback(emscripten::val entries, emscript if (count == 0) return; emscripten::val entry = entries[0]; - QWasmScreen *screen = - reinterpret_cast<QWasmScreen *>(entry["target"][m_canvasResizeObserverCallbackContextPropertyName].as<intptr_t>()); + QWasmScreen *screen = reinterpret_cast<QWasmScreen *>( + entry["target"][m_canvasResizeObserverCallbackContextPropertyName].as<intptr_t>()); if (!screen) { qWarning() << "QWasmScreen::canvasResizeObserverCallback: missing screen pointer"; return; } // We could access contentBoxSize|contentRect|devicePixelContentBoxSize on the entry here, but - // these are not universally supported across all browsers. Get the sizes from the canvas instead. + // these are not universally supported across all browsers. Get the sizes from the canvas + // instead. screen->updateQScreenAndCanvasRenderSize(); } -EMSCRIPTEN_BINDINGS(qtCanvasResizeObserverCallback) { - emscripten::function("qtCanvasResizeObserverCallback", &QWasmScreen::canvasResizeObserverCallback); +EMSCRIPTEN_BINDINGS(qtCanvasResizeObserverCallback) +{ + emscripten::function("qtCanvasResizeObserverCallback", + &QWasmScreen::canvasResizeObserverCallback); } void QWasmScreen::installCanvasResizeObserver() @@ -307,15 +304,17 @@ void QWasmScreen::installCanvasResizeObserver() emscripten::val ResizeObserver = emscripten::val::global("ResizeObserver"); if (ResizeObserver == emscripten::val::undefined()) return; // ResizeObserver API is not available - emscripten::val resizeObserver = ResizeObserver.new_(emscripten::val::module_property("qtCanvasResizeObserverCallback")); + emscripten::val resizeObserver = + ResizeObserver.new_(emscripten::val::module_property("qtCanvasResizeObserverCallback")); if (resizeObserver == emscripten::val::undefined()) return; // Something went horribly wrong // We need to get back to this instance from the (static) resize callback; // set a "data-" property on the canvas element. - m_canvas.set(m_canvasResizeObserverCallbackContextPropertyName, emscripten::val(intptr_t(this))); + m_shadowContainer.set(m_canvasResizeObserverCallbackContextPropertyName, + emscripten::val(intptr_t(this))); - resizeObserver.call<void>("observe", m_canvas); + resizeObserver.call<void>("observe", m_shadowContainer); } QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmscreen.h b/src/plugins/platforms/wasm/qwasmscreen.h index 72f727da3a..fd573059e6 100644 --- a/src/plugins/platforms/wasm/qwasmscreen.h +++ b/src/plugins/platforms/wasm/qwasmscreen.h @@ -33,10 +33,9 @@ public: static QWasmScreen *get(QPlatformScreen *screen); static QWasmScreen *get(QScreen *screen); - emscripten::val container() const; - emscripten::val canvas() const; - QString canvasId() const; - QString canvasTargetId() const; + emscripten::val element() const; + QString eventTargetId() const; + QString outerScreenId() const; QWasmCompositor *compositor(); QWasmEventTranslator *eventTranslator(); @@ -65,14 +64,14 @@ public slots: private: emscripten::val m_container; - emscripten::val m_canvas; + emscripten::val m_shadowContainer; std::unique_ptr<QWasmCompositor> m_compositor; std::unique_ptr<QWasmEventTranslator> m_eventTranslator; QRect m_geometry = QRect(0, 0, 100, 100); int m_depth = 32; QImage::Format m_format = QImage::Format_RGB32; QWasmCursor m_cursor; - static const char * m_canvasResizeObserverCallbackContextPropertyName; + static const char *m_canvasResizeObserverCallbackContextPropertyName; std::unique_ptr<qstdweb::EventCallback> m_onContextMenu; }; diff --git a/src/plugins/platforms/wasm/qwasmwindow.cpp b/src/plugins/platforms/wasm/qwasmwindow.cpp index 445f5ed809..182ae38fea 100644 --- a/src/plugins/platforms/wasm/qwasmwindow.cpp +++ b/src/plugins/platforms/wasm/qwasmwindow.cpp @@ -3,88 +3,242 @@ #include <qpa/qwindowsysteminterface.h> #include <private/qguiapplication_p.h> -#include <QtGui/private/qopenglcontext_p.h> +#include <QtCore/qfile.h> #include <QtGui/private/qwindow_p.h> -#include <QtGui/qopenglcontext.h> #include <private/qpixmapcache_p.h> +#include <QtGui/qopenglfunctions.h> +#include <QBuffer> #include "qwasmwindow.h" #include "qwasmscreen.h" #include "qwasmstylepixmaps_p.h" #include "qwasmcompositor.h" +#include "qwasmevent.h" #include "qwasmeventdispatcher.h" +#include "qwasmstring.h" #include <iostream> +#include <emscripten/val.h> +#include <GL/gl.h> QT_BEGIN_NAMESPACE Q_GUI_EXPORT int qt_defaultDpiX(); namespace { -// from commonstyle.cpp -static QPixmap cachedPixmapFromXPM(const char *const *xpm) -{ - QPixmap result; - const QString tag = QString::asprintf("xpm:0x%p", static_cast<const void *>(xpm)); - if (!QPixmapCache::find(tag, &result)) { - result = QPixmap(xpm); - QPixmapCache::insert(tag, result); +enum class IconType { + Maximize, + First = Maximize, + QtLogo, + Restore, + X, + Size, +}; + +void syncCSSClassWith(emscripten::val element, std::string cssClassName, bool flag) +{ + if (flag) { + element["classList"].call<void>("add", emscripten::val(std::move(cssClassName))); + return; } - return result; + + element["classList"].call<void>("remove", emscripten::val(std::move(cssClassName))); } +} // namespace -QPalette makePalette() +class QWasmWindow::WebImageButton { - QPalette palette; - palette.setColor(QPalette::Active, QPalette::Highlight, - palette.color(QPalette::Active, QPalette::Highlight)); - palette.setColor(QPalette::Active, QPalette::Base, - palette.color(QPalette::Active, QPalette::Highlight)); - palette.setColor(QPalette::Inactive, QPalette::Highlight, - palette.color(QPalette::Inactive, QPalette::Dark)); - palette.setColor(QPalette::Inactive, QPalette::Base, - palette.color(QPalette::Inactive, QPalette::Dark)); - palette.setColor(QPalette::Inactive, QPalette::HighlightedText, - palette.color(QPalette::Inactive, QPalette::Window)); +public: + class Callbacks + { + public: + Callbacks() = default; + Callbacks(std::function<void()> onInteraction, std::function<void()> onClick) + : m_onInteraction(std::move(onInteraction)), m_onClick(std::move(onClick)) + { + Q_ASSERT_X(!!m_onInteraction == !!m_onClick, Q_FUNC_INFO, + "Both callbacks need to be either null or non-null"); + } + ~Callbacks() = default; - return palette; -} + Callbacks(const Callbacks &) = delete; + Callbacks(Callbacks &&) = default; + Callbacks &operator=(const Callbacks &) = delete; + Callbacks &operator=(Callbacks &&) = default; -void drawItemPixmap(QPainter *painter, const QRect &rect, int alignment, const QPixmap &pixmap) -{ - qreal scale = pixmap.devicePixelRatio(); - QSize size = pixmap.size() / scale; - int x = rect.x(); - int y = rect.y(); - int w = size.width(); - int h = size.height(); - if ((alignment & Qt::AlignVCenter) == Qt::AlignVCenter) - y += rect.size().height() / 2 - h / 2; - else if ((alignment & Qt::AlignBottom) == Qt::AlignBottom) - y += rect.size().height() - h; - if ((alignment & Qt::AlignRight) == Qt::AlignRight) - x += rect.size().width() - w; - else if ((alignment & Qt::AlignHCenter) == Qt::AlignHCenter) - x += rect.size().width() / 2 - w / 2; + operator bool() const { return !!m_onInteraction; } - QRect aligned = QRect(x, y, w, h); - QRect inter = aligned.intersected(rect); + void onInteraction() { return m_onInteraction(); } + void onClick() { return m_onClick(); } - painter->drawPixmap(inter.x(), inter.y(), pixmap, inter.x() - aligned.x(), - inter.y() - aligned.y(), inter.width() * scale, inter.height() * scale); -} -} + private: + std::function<void()> m_onInteraction; + std::function<void()> m_onClick; + }; + + WebImageButton() + : m_containerElement( + emscripten::val::global("document") + .call<emscripten::val>("createElement", emscripten::val("div"))), + m_imageHolderElement( + emscripten::val::global("document") + .call<emscripten::val>("createElement", emscripten::val("span"))) + { + m_imageHolderElement.set("draggable", false); + + m_containerElement["classList"].call<void>("add", emscripten::val("image-button")); + m_containerElement.call<void>("appendChild", m_imageHolderElement); + } + + ~WebImageButton() = default; + + void setCallbacks(Callbacks callbacks) + { + if (callbacks) { + if (!m_webClickEventCallback) { + m_webMouseDownEventCallback = std::make_unique<qstdweb::EventCallback>( + m_containerElement, "mousedown", [this](emscripten::val event) { + event.call<void>("preventDefault"); + event.call<void>("stopPropagation"); + m_callbacks.onInteraction(); + }); + m_webClickEventCallback = std::make_unique<qstdweb::EventCallback>( + m_containerElement, "click", + [this](emscripten::val) { m_callbacks.onClick(); }); + } + } else { + m_webMouseDownEventCallback.reset(); + m_webClickEventCallback.reset(); + } + syncCSSClassWith(m_containerElement, "action-button", !!callbacks); + m_callbacks = std::move(callbacks); + } + + void setImage(std::string_view imageData, std::string_view format) + { + m_imageHolderElement.call<void>("removeAttribute", + emscripten::val("qt-builtin-image-type")); + m_imageHolderElement["style"].set("backgroundImage", + "url('data:image/" + std::string(format) + ";base64," + + std::string(imageData) + "')"); + } + + void setImage(IconType type) + { + m_imageHolderElement["style"].set("backgroundImage", emscripten::val::undefined()); + const auto imageType = ([type]() { + switch (type) { + case IconType::QtLogo: + return "qt-logo"; + case IconType::X: + return "x"; + case IconType::Restore: + return "restore"; + case IconType::Maximize: + return "maximize"; + default: + return "err"; + } + })(); + m_imageHolderElement.call<void>("setAttribute", emscripten::val("qt-builtin-image-type"), + emscripten::val(imageType)); + } + + void setVisible(bool visible) + { + m_containerElement["style"].set("display", visible ? "flex" : "none"); + } + + emscripten::val htmlElement() const { return m_containerElement; } + emscripten::val imageElement() const { return m_imageHolderElement; } + +private: + emscripten::val m_containerElement; + emscripten::val m_imageHolderElement; + std::unique_ptr<qstdweb::EventCallback> m_webMouseMoveEventCallback; + std::unique_ptr<qstdweb::EventCallback> m_webMouseDownEventCallback; + std::unique_ptr<qstdweb::EventCallback> m_webClickEventCallback; + + Callbacks m_callbacks; +}; QWasmWindow::QWasmWindow(QWindow *w, QWasmCompositor *compositor, QWasmBackingStore *backingStore) : QPlatformWindow(w), m_window(w), m_compositor(compositor), - m_backingStore(backingStore) + m_backingStore(backingStore), + m_document(emscripten::val::global("document")), + m_qtWindow(m_document.call<emscripten::val>("createElement", emscripten::val("div"))), + m_windowContents(m_document.call<emscripten::val>("createElement", emscripten::val("div"))), + m_titleBar(m_document.call<emscripten::val>("createElement", emscripten::val("div"))), + m_label(m_document.call<emscripten::val>("createElement", emscripten::val("div"))), + m_canvasContainer(m_document.call<emscripten::val>("createElement", emscripten::val("div"))), + m_canvas(m_document.call<emscripten::val>("createElement", emscripten::val("canvas"))) { + m_qtWindow.set("className", "qt-window"); + m_qtWindow["style"].set("display", std::string("none")); + + m_qtWindow.call<void>("appendChild", m_windowContents); + + m_icon = std::make_unique<WebImageButton>(); + m_icon->setImage(IconType::QtLogo); + + m_titleBar.call<void>("appendChild", m_icon->htmlElement()); + m_titleBar.set("className", "title-bar"); + + auto spacer = m_document.call<emscripten::val>("createElement", emscripten::val("div")); + spacer["style"].set("width", "4px"); + m_titleBar.call<void>("appendChild", spacer); + + m_label.set("innerText", emscripten::val(window()->title().toStdString())); + m_label.set("className", "window-name"); + + m_titleBar.call<void>("appendChild", m_label); + + spacer = m_document.call<emscripten::val>("createElement", emscripten::val("div")); + spacer.set("className", "spacer"); + m_titleBar.call<void>("appendChild", spacer); + + m_restore = std::make_unique<WebImageButton>(); + m_restore->setImage(IconType::Restore); + m_restore->setCallbacks(WebImageButton::Callbacks([this]() { onInteraction(); }, + [this]() { onRestoreClicked(); })); + + m_titleBar.call<void>("appendChild", m_restore->htmlElement()); + + m_maximize = std::make_unique<WebImageButton>(); + m_maximize->setImage(IconType::Maximize); + m_maximize->setCallbacks(WebImageButton::Callbacks([this]() { onInteraction(); }, + [this]() { onMaximizeClicked(); })); + + m_titleBar.call<void>("appendChild", m_maximize->htmlElement()); + + m_close = std::make_unique<WebImageButton>(); + m_close->setImage(IconType::X); + m_close->setCallbacks(WebImageButton::Callbacks([this]() { onInteraction(); }, + [this]() { onCloseClicked(); })); + + m_titleBar.call<void>("appendChild", m_close->htmlElement()); + + m_windowContents.call<void>("appendChild", m_titleBar); + + m_canvas["classList"].call<void>("add", emscripten::val("qt-window-content")); + + m_windowContents.call<void>("appendChild", m_canvasContainer); + + m_canvasContainer["classList"].call<void>("add", emscripten::val("qt-window-canvas-container")); + m_canvasContainer.call<void>("appendChild", m_canvas); + + compositor->screen()->element().call<void>("appendChild", m_qtWindow); + m_needsCompositor = w->surfaceType() != QSurface::OpenGLSurface; + if (m_needsCompositor) + m_context2d = m_canvas.call<emscripten::val>("getContext", emscripten::val("2d")); static int serialNo = 0; - m_winid = ++serialNo; + m_winId = ++serialNo; + m_qtWindow.set("id", "qt-window-" + std::to_string(m_winId)); + emscripten::val::module_property("specialHTMLTargets").set(canvasSelector(), m_canvas); m_compositor->addWindow(this); @@ -94,6 +248,8 @@ QWasmWindow::QWasmWindow(QWindow *w, QWasmCompositor *compositor, QWasmBackingSt QWasmWindow::~QWasmWindow() { + emscripten::val::module_property("specialHTMLTargets").delete_(canvasSelector()); + destroy(); m_compositor->removeWindow(this); if (m_requestAnimationFrameId > -1) emscripten_cancel_animation_frame(m_requestAnimationFrameId); @@ -101,8 +257,10 @@ QWasmWindow::~QWasmWindow() void QWasmWindow::destroy() { - if (m_backingStore) - m_backingStore->destroy(); + m_qtWindow["parentElement"].call<emscripten::val>("removeChild", m_qtWindow); + + m_canvasContainer.call<void>("removeChild", m_canvas); + m_context2d = emscripten::val::undefined(); } void QWasmWindow::initialize() @@ -137,23 +295,59 @@ QWasmScreen *QWasmWindow::platformScreen() const return static_cast<QWasmScreen *>(window()->screen()->handle()); } +void QWasmWindow::paint() +{ + if (!m_backingStore || !isVisible()) + return; + + auto image = m_backingStore->getUpdatedWebImage(this); + if (image.isUndefined()) + return; + m_context2d.call<void>("putImageData", image, emscripten::val(0), emscripten::val(0)); +} + +void QWasmWindow::setZOrder(int z) +{ + m_qtWindow["style"].set("zIndex", std::to_string(z)); +} + void QWasmWindow::setGeometry(const QRect &rect) { - const QRect clientAreaRect = ([this, &rect]() { - if (!m_needsCompositor) - return rect; + const auto margins = frameMargins(); + + const QRect clientAreaRect = ([this, &rect, &margins]() { + if (m_state.testFlag(Qt::WindowFullScreen)) + return platformScreen()->geometry(); + if (m_state.testFlag(Qt::WindowMaximized)) + return platformScreen()->availableGeometry().marginsRemoved(frameMargins()); - const int captionHeight = window()->geometry().top() - window()->frameGeometry().top(); const auto screenGeometry = screen()->geometry(); QRect result(rect); result.moveTop(std::max(std::min(rect.y(), screenGeometry.bottom()), - screenGeometry.y() + captionHeight)); + screenGeometry.y() + margins.top())); return result; })(); + const auto frameRect = + clientAreaRect + .adjusted(-margins.left(), -margins.top(), margins.right(), margins.bottom()) + .translated(-screen()->geometry().topLeft()); + + m_qtWindow["style"].set("left", std::to_string(frameRect.left()) + "px"); + m_qtWindow["style"].set("top", std::to_string(frameRect.top()) + "px"); + m_canvasContainer["style"].set("width", std::to_string(clientAreaRect.width()) + "px"); + m_canvasContainer["style"].set("height", std::to_string(clientAreaRect.height()) + "px"); + + // Important for the title flexbox to shrink correctly + m_windowContents["style"].set("width", std::to_string(clientAreaRect.width()) + "px"); + + QSizeF canvasSize = clientAreaRect.size() * devicePixelRatio(); + + m_canvas.set("width", canvasSize.width()); + m_canvas.set("height", canvasSize.height()); + bool shouldInvalidate = true; - if (!m_windowState.testFlag(Qt::WindowFullScreen) - && !m_windowState.testFlag(Qt::WindowMaximized)) { + if (!m_state.testFlag(Qt::WindowFullScreen) && !m_state.testFlag(Qt::WindowMaximized)) { shouldInvalidate = m_normalGeometry.size() != clientAreaRect.size(); m_normalGeometry = clientAreaRect; } @@ -166,28 +360,31 @@ void QWasmWindow::setGeometry(const QRect &rect) void QWasmWindow::setVisible(bool visible) { + // TODO(mikolajboc): isVisible()? + const bool nowVisible = m_qtWindow["style"]["display"].as<std::string>() == "block"; + if (visible == nowVisible) + return; + + m_compositor->requestUpdateWindow(this, QWasmCompositor::ExposeEventDelivery); + m_qtWindow["style"].set("display", visible ? "block" : "none"); if (visible) applyWindowState(); - m_compositor->setVisible(this, visible); } -bool QWasmWindow::isVisible() +bool QWasmWindow::isVisible() const { return window()->isVisible(); } QMargins QWasmWindow::frameMargins() const { - int border = hasTitleBar() ? 4. * (qreal(qt_defaultDpiX()) / 96.0) : 0; - int titleBarHeight = hasTitleBar() ? titleHeight() : 0; + const auto border = borderMargins(); + const auto titleBarBounds = + QRectF::fromDOMRect(m_titleBar.call<emscripten::val>("getBoundingClientRect")); - QMargins margins; - margins.setLeft(border); - margins.setRight(border); - margins.setTop(2*border + titleBarHeight); - margins.setBottom(border); - - return margins; + return QMarginsF(border.left(), border.top() + titleBarBounds.height(), border.right(), + border.bottom()) + .toMargins(); } void QWasmWindow::raise() @@ -204,7 +401,7 @@ void QWasmWindow::lower() WId QWasmWindow::winId() const { - return m_winid; + return m_winId; } void QWasmWindow::propagateSizeHints() @@ -224,83 +421,51 @@ bool QWasmWindow::startSystemResize(Qt::Edges edges) return true; } -void QWasmWindow::injectMousePressed(const QPoint &local, const QPoint &global, - Qt::MouseButton button, Qt::KeyboardModifiers mods) +void QWasmWindow::onRestoreClicked() { - Q_UNUSED(local); - Q_UNUSED(mods); - - if (!hasTitleBar() || button != Qt::LeftButton) - return; - - if (const auto controlHit = titleBarHitTest(global)) - m_activeControl = *controlHit; - - invalidate(); + window()->setWindowState(Qt::WindowNoState); } -void QWasmWindow::injectMouseReleased(const QPoint &local, const QPoint &global, - Qt::MouseButton button, Qt::KeyboardModifiers mods) +void QWasmWindow::onMaximizeClicked() { - Q_UNUSED(local); - Q_UNUSED(mods); - - if (!hasTitleBar() || button != Qt::LeftButton) - return; - - if (const auto controlHit = titleBarHitTest(global)) { - if (m_activeControl == *controlHit) { - switch (*controlHit) { - case SC_TitleBarCloseButton: - window()->close(); - break; - case SC_TitleBarMaxButton: - window()->setWindowState(Qt::WindowMaximized); - break; - case SC_TitleBarNormalButton: - window()->setWindowState(Qt::WindowNoState); - break; - case SC_None: - case SC_TitleBarLabel: - case SC_TitleBarSysMenu: - Q_ASSERT(false); // These types are not clickable - return; - } - } - } - - m_activeControl = SC_None; + window()->setWindowState(Qt::WindowMaximized); +} - invalidate(); +void QWasmWindow::onCloseClicked() +{ + window()->close(); } -int QWasmWindow::titleHeight() const +void QWasmWindow::onInteraction() { - return 18. * (qreal(qt_defaultDpiX()) / 96.0);//dpiScaled(18.); + if (!isActive()) + requestActivateWindow(); } -int QWasmWindow::borderWidth() const +QMarginsF QWasmWindow::borderMargins() const { - return 4. * (qreal(qt_defaultDpiX()) / 96.0);// dpiScaled(4.); + const auto frameRect = + QRectF::fromDOMRect(m_qtWindow.call<emscripten::val>("getBoundingClientRect")); + const auto canvasRect = + QRectF::fromDOMRect(m_windowContents.call<emscripten::val>("getBoundingClientRect")); + return QMarginsF(canvasRect.left() - frameRect.left(), canvasRect.top() - frameRect.top(), + frameRect.right() - canvasRect.right(), + frameRect.bottom() - canvasRect.bottom()); } QRegion QWasmWindow::resizeRegion() const { - int border = borderWidth(); - QRegion result(window()->frameGeometry().adjusted(-border, -border, border, border)); - result -= window()->frameGeometry().adjusted(border, border, -border, -border); + QMargins margins = borderMargins().toMargins(); + QRegion result(window()->frameGeometry().marginsAdded(margins)); + result -= window()->frameGeometry().marginsRemoved(margins); return result; } bool QWasmWindow::isPointOnTitle(QPoint globalPoint) const { - const auto pointInFrameCoords = globalPoint - windowFrameGeometry().topLeft(); - if (const auto titleRect = - getTitleBarControlRect(makeTitleBarOptions(), TitleBarControl::SC_TitleBarLabel)) { - return titleRect->contains(pointInFrameCoords); - } - return false; + return QRectF::fromDOMRect(m_titleBar.call<emscripten::val>("getBoundingClientRect")) + .contains(globalPoint); } bool QWasmWindow::isPointOnResizeRegion(QPoint point) const @@ -333,226 +498,83 @@ Qt::Edges QWasmWindow::resizeEdgesAtPoint(QPoint point) const return edges | (right.contains(point) ? Qt::Edge::RightEdge : Qt::Edge(0)); } -std::optional<QRect> QWasmWindow::getTitleBarControlRect(const TitleBarOptions &tb, - TitleBarControl control) const -{ - const auto leftToRightRect = getTitleBarControlRectLeftToRight(tb, control); - if (!leftToRightRect) - return std::nullopt; - return qApp->layoutDirection() == Qt::LeftToRight - ? leftToRightRect - : leftToRightRect->translated(2 * (tb.rect.right() - leftToRightRect->right()) - + leftToRightRect->width() - tb.rect.width(), - 0); -} - -bool QWasmWindow::TitleBarOptions::hasControl(TitleBarControl control) const -{ - return subControls.testFlag(control); -} - -std::optional<QRect> QWasmWindow::getTitleBarControlRectLeftToRight(const TitleBarOptions &tb, - TitleBarControl control) const -{ - if (!tb.hasControl(control)) - return std::nullopt; - - const int controlMargin = 2; - const int controlHeight = tb.rect.height() - controlMargin * 2; - const int controlWidth = controlHeight; - const int delta = controlWidth + controlMargin; - int offsetRight = 0; - - switch (control) { - case SC_TitleBarLabel: { - const int leftOffset = tb.hasControl(SC_TitleBarSysMenu) ? delta : 0; - const int rightOffset = (tb.hasControl(SC_TitleBarCloseButton) ? delta : 0) - + ((tb.hasControl(SC_TitleBarMaxButton) || tb.hasControl(SC_TitleBarNormalButton)) - ? delta - : 0); - - return tb.rect.adjusted(leftOffset, 0, -rightOffset, 0); - } - case SC_TitleBarSysMenu: - return QRect(tb.rect.left() + controlMargin, tb.rect.top() + controlMargin, controlWidth, - controlHeight); - case SC_TitleBarCloseButton: - offsetRight = delta; - break; - case SC_TitleBarMaxButton: - case SC_TitleBarNormalButton: - offsetRight = delta + (tb.hasControl(SC_TitleBarCloseButton) ? delta : 0); - break; - case SC_None: - Q_ASSERT(false); - break; - }; - - return QRect(tb.rect.right() - offsetRight, tb.rect.top() + controlMargin, controlWidth, - controlHeight); -} - void QWasmWindow::invalidate() { m_compositor->requestUpdateWindow(this); } -QWasmWindow::TitleBarControl QWasmWindow::activeTitleBarControl() const +void QWasmWindow::onActivationChanged(bool active) { - return m_activeControl; + syncCSSClassWith(m_qtWindow, "inactive", !active); } -std::optional<QWasmWindow::TitleBarControl> -QWasmWindow::titleBarHitTest(const QPoint &globalPoint) const +void QWasmWindow::setWindowFlags(Qt::WindowFlags flags) { - const auto pointInFrameCoords = globalPoint - windowFrameGeometry().topLeft(); - const auto options = makeTitleBarOptions(); - - static constexpr TitleBarControl Controls[] = { SC_TitleBarMaxButton, SC_TitleBarCloseButton, - SC_TitleBarNormalButton }; - auto found = std::find_if(std::begin(Controls), std::end(Controls), - [this, &pointInFrameCoords, &options](TitleBarControl control) { - auto controlRect = getTitleBarControlRect(options, control); - return controlRect && controlRect->contains(pointInFrameCoords); - }); - return found != std::end(Controls) ? *found : std::optional<TitleBarControl>(); + m_flags = flags; + syncCSSClassWith(m_qtWindow, "has-title-bar", hasTitleBar()); } void QWasmWindow::setWindowState(Qt::WindowStates newState) { - const Qt::WindowStates oldState = m_windowState; - bool isActive = oldState.testFlag(Qt::WindowActive); + const Qt::WindowStates oldState = m_state; if (newState.testFlag(Qt::WindowMinimized)) { newState.setFlag(Qt::WindowMinimized, false); qWarning("Qt::WindowMinimized is not implemented in wasm"); + window()->setWindowStates(newState); + return; } - // Always keep OpenGL apps fullscreen - if (!m_needsCompositor && !newState.testFlag(Qt::WindowFullScreen)) { - newState.setFlag(Qt::WindowFullScreen, true); - qWarning("Qt::WindowFullScreen must be set for OpenGL surfaces"); - } - - // Ignore WindowActive flag in comparison, as we want to preserve it either way - if ((newState & ~Qt::WindowActive) == (oldState & ~Qt::WindowActive)) + if (newState == oldState) return; - newState.setFlag(Qt::WindowActive, isActive); - + m_state = newState; m_previousWindowState = oldState; - m_windowState = newState; - if (isVisible()) { - applyWindowState(); - } + applyWindowState(); } -void QWasmWindow::applyWindowState() +void QWasmWindow::setWindowTitle(const QString &title) { - QRect newGeom; - - if (m_windowState.testFlag(Qt::WindowFullScreen)) - newGeom = platformScreen()->geometry(); - else if (m_windowState.testFlag(Qt::WindowMaximized)) - newGeom = platformScreen()->availableGeometry(); - else - newGeom = normalGeometry(); - - QWindowSystemInterface::handleWindowStateChanged(window(), m_windowState, m_previousWindowState); - setGeometry(newGeom); + m_label.set("innerText", emscripten::val(title.toStdString())); } -void QWasmWindow::drawTitleBar(QPainter *painter) const +void QWasmWindow::setWindowIcon(const QIcon &icon) { - const auto tb = makeTitleBarOptions(); - if (const auto ir = getTitleBarControlRect(tb, SC_TitleBarLabel)) { - QColor left = tb.palette.highlight().color(); - QColor right = tb.palette.base().color(); - - QBrush fillBrush(left); - if (left != right) { - QPoint p1(tb.rect.x(), tb.rect.top() + tb.rect.height() / 2); - QPoint p2(tb.rect.right(), tb.rect.top() + tb.rect.height() / 2); - QLinearGradient lg(p1, p2); - lg.setColorAt(0, left); - lg.setColorAt(1, right); - fillBrush = lg; - } - - painter->fillRect(tb.rect, fillBrush); - painter->setPen(tb.palette.highlightedText().color()); - painter->drawText(ir->x() + 2, ir->y(), ir->width() - 2, ir->height(), - Qt::AlignLeft | Qt::AlignVCenter | Qt::TextSingleLine, - tb.titleBarOptionsString); - } - - if (const auto ir = getTitleBarControlRect(tb, SC_TitleBarCloseButton)) { - drawItemPixmap(painter, *ir, Qt::AlignCenter, - cachedPixmapFromXPM(qt_close_xpm).scaled(QSize(10, 10))); - } - - if (const auto ir = getTitleBarControlRect(tb, SC_TitleBarMaxButton)) { - drawItemPixmap(painter, *ir, Qt::AlignCenter, - cachedPixmapFromXPM(qt_maximize_xpm).scaled(QSize(10, 10))); - } - - if (const auto ir = getTitleBarControlRect(tb, SC_TitleBarNormalButton)) { - drawItemPixmap(painter, *ir, Qt::AlignCenter, - cachedPixmapFromXPM(qt_normalizeup_xpm).scaled(QSize(10, 10))); + const auto dpi = screen()->devicePixelRatio(); + auto pixmap = icon.pixmap(10 * dpi, 10 * dpi); + if (pixmap.isNull()) { + m_icon->setImage(IconType::QtLogo); + return; } - if (const auto ir = getTitleBarControlRect(tb, SC_TitleBarSysMenu)) { - if (!tb.windowIcon.isNull()) { - tb.windowIcon.paint(painter, *ir, Qt::AlignCenter); - } else { - drawItemPixmap(painter, *ir, Qt::AlignCenter, - cachedPixmapFromXPM(qt_menu_xpm).scaled(QSize(10, 10))); - } - } + QByteArray bytes; + QBuffer buffer(&bytes); + pixmap.save(&buffer, "png"); + m_icon->setImage(bytes.toBase64().toStdString(), "png"); } -QWasmWindow::TitleBarOptions QWasmWindow::makeTitleBarOptions() const +void QWasmWindow::applyWindowState() { - int width = windowFrameGeometry().width(); - int border = borderWidth(); - - TitleBarOptions titleBarOptions; - - titleBarOptions.rect = QRect(border, border, width - 2 * border, titleHeight()); - titleBarOptions.flags = window()->flags(); - titleBarOptions.state = window()->windowState(); - - bool isMaximized = - titleBarOptions.state & Qt::WindowMaximized; // this gets reset when maximized - - if (titleBarOptions.flags & (Qt::WindowTitleHint)) - titleBarOptions.subControls |= SC_TitleBarLabel; - if (titleBarOptions.flags & Qt::WindowMaximizeButtonHint) { - if (isMaximized) - titleBarOptions.subControls |= SC_TitleBarNormalButton; - else - titleBarOptions.subControls |= SC_TitleBarMaxButton; - } - if (titleBarOptions.flags & Qt::WindowSystemMenuHint) { - titleBarOptions.subControls |= SC_TitleBarCloseButton; - titleBarOptions.subControls |= SC_TitleBarSysMenu; - } - - titleBarOptions.palette = makePalette(); - - titleBarOptions.palette.setCurrentColorGroup( - QGuiApplication::focusWindow() == window() ? QPalette::Active : QPalette::Inactive); + QRect newGeom; - if (activeTitleBarControl() != SC_None) - titleBarOptions.subControls |= activeTitleBarControl(); + const bool isFullscreen = m_state.testFlag(Qt::WindowFullScreen); + const bool isMaximized = m_state.testFlag(Qt::WindowMaximized); + if (isFullscreen) + newGeom = platformScreen()->geometry(); + else if (isMaximized) + newGeom = platformScreen()->availableGeometry().marginsRemoved(frameMargins()); + else + newGeom = normalGeometry(); - if (!window()->title().isEmpty()) - titleBarOptions.titleBarOptionsString = window()->title(); + syncCSSClassWith(m_qtWindow, "has-title-bar", hasTitleBar()); - titleBarOptions.windowIcon = window()->icon(); + m_restore->setVisible(isMaximized); + m_maximize->setVisible(!isMaximized); - return titleBarOptions; + if (isVisible()) + QWindowSystemInterface::handleWindowStateChanged(window(), m_state, m_previousWindowState); + setGeometry(newGeom); } QRect QWasmWindow::normalGeometry() const @@ -572,17 +594,14 @@ void QWasmWindow::requestUpdate() bool QWasmWindow::hasTitleBar() const { - Qt::WindowFlags flags = window()->flags(); - return !(m_windowState & Qt::WindowFullScreen) - && flags.testFlag(Qt::WindowTitleHint) - && !(windowIsPopupType(flags)) - && m_needsCompositor; + return !m_state.testFlag(Qt::WindowFullScreen) && m_flags.testFlag(Qt::WindowTitleHint) + && !windowIsPopupType(m_flags); } bool QWasmWindow::windowIsPopupType(Qt::WindowFlags flags) const { if (flags.testFlag(Qt::Tool)) - return false; // Qt::Tool has the Popup bit set but isn't + return false; // Qt::Tool has the Popup bit set but isn't an actual Popup window return (flags.testFlag(Qt::Popup)); } @@ -609,4 +628,23 @@ bool QWasmWindow::setMouseGrabEnabled(bool grab) return true; } +bool QWasmWindow::windowEvent(QEvent *event) +{ + switch (event->type()) { + case QEvent::WindowBlocked: + m_qtWindow["classList"].call<void>("add", emscripten::val("blocked")); + return false; // Propagate further + case QEvent::WindowUnblocked:; + m_qtWindow["classList"].call<void>("remove", emscripten::val("blocked")); + return false; // Propagate further + default: + return QPlatformWindow::windowEvent(event); + } +} + +std::string QWasmWindow::canvasSelector() const +{ + return "!qtwindow" + std::to_string(m_winId); +} + QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmwindow.h b/src/plugins/platforms/wasm/qwasmwindow.h index c86026cf1b..183f68a311 100644 --- a/src/plugins/platforms/wasm/qwasmwindow.h +++ b/src/plugins/platforms/wasm/qwasmwindow.h @@ -11,30 +11,30 @@ #include "qwasmscreen.h" #include "qwasmcompositor.h" +#include <QtCore/private/qstdweb_p.h> +#include "QtGui/qopenglcontext.h" +#include <QtOpenGL/qopengltextureblitter.h> + +#include <emscripten/val.h> + QT_BEGIN_NAMESPACE -class QWasmWindow : public QPlatformWindow +class QWasmWindow final : public QPlatformWindow { public: - enum TitleBarControl { - SC_None = 0x00000000, - SC_TitleBarSysMenu = 0x00000001, - SC_TitleBarMaxButton = 0x00000002, - SC_TitleBarCloseButton = 0x00000004, - SC_TitleBarNormalButton = 0x00000008, - SC_TitleBarLabel = 0x00000010 - }; - Q_DECLARE_FLAGS(TitleBarControls, TitleBarControl); - QWasmWindow(QWindow *w, QWasmCompositor *compositor, QWasmBackingStore *backingStore); - ~QWasmWindow(); + ~QWasmWindow() final; void destroy(); void initialize() override; + void paint(); + void setZOrder(int order); + void onActivationChanged(bool active); + void setGeometry(const QRect &) override; void setVisible(bool visible) override; - bool isVisible(); + bool isVisible() const; QMargins frameMargins() const override; WId winId() const override; @@ -52,10 +52,6 @@ public: QWasmBackingStore *backingStore() const { return m_backingStore; } QWindow *window() const { return m_window; } - void injectMousePressed(const QPoint &local, const QPoint &global, - Qt::MouseButton button, Qt::KeyboardModifiers mods); - void injectMouseReleased(const QPoint &local, const QPoint &global, - Qt::MouseButton button, Qt::KeyboardModifiers mods); bool startSystemResize(Qt::Edges edges) final; bool isPointOnTitle(QPoint point) const; @@ -63,55 +59,59 @@ public: Qt::Edges resizeEdgesAtPoint(QPoint point) const; + void setWindowFlags(Qt::WindowFlags flags) override; void setWindowState(Qt::WindowStates state) override; + void setWindowTitle(const QString &title) override; + void setWindowIcon(const QIcon &icon) override; void applyWindowState(); bool setKeyboardGrabEnabled(bool) override { return false; } bool setMouseGrabEnabled(bool grab) final; + bool windowEvent(QEvent *event) final; - void drawTitleBar(QPainter *painter) const; - -protected: - void invalidate(); - bool hasTitleBar() const; + std::string canvasSelector() const; + emscripten::val context2d() { return m_context2d; } private: friend class QWasmScreen; - struct TitleBarOptions - { - bool hasControl(TitleBarControl control) const; - - QRect rect; - Qt::WindowFlags flags; - int state; - QPalette palette; - QString titleBarOptionsString; - TitleBarControls subControls; - QIcon windowIcon; - }; - - TitleBarOptions makeTitleBarOptions() const; - std::optional<QRect> getTitleBarControlRect(const TitleBarOptions &tb, - TitleBarControl control) const; - std::optional<QRect> getTitleBarControlRectLeftToRight(const TitleBarOptions &tb, - TitleBarControl control) const; - QRegion titleControlRegion() const; - QRegion titleGeometry() const; - int borderWidth() const; - int titleHeight() const; + class WebImageButton; + + QMarginsF borderMargins() const; QRegion resizeRegion() const; - TitleBarControl activeTitleBarControl() const; - std::optional<TitleBarControl> titleBarHitTest(const QPoint &globalPoint) const; + + void onRestoreClicked(); + void onMaximizeClicked(); + void onCloseClicked(); + void onInteraction(); + + void invalidate(); + bool hasTitleBar() const; QWindow *m_window = nullptr; QWasmCompositor *m_compositor = nullptr; QWasmBackingStore *m_backingStore = nullptr; QRect m_normalGeometry {0, 0, 0 ,0}; - Qt::WindowStates m_windowState = Qt::WindowNoState; + emscripten::val m_document; + emscripten::val m_qtWindow; + emscripten::val m_windowContents; + emscripten::val m_titleBar; + emscripten::val m_label; + emscripten::val m_canvasContainer; + emscripten::val m_canvas; + emscripten::val m_context2d = emscripten::val::undefined(); + + std::unique_ptr<WebImageButton> m_close; + std::unique_ptr<WebImageButton> m_maximize; + std::unique_ptr<WebImageButton> m_restore; + std::unique_ptr<WebImageButton> m_icon; + + Qt::WindowStates m_state = Qt::WindowNoState; Qt::WindowStates m_previousWindowState = Qt::WindowNoState; - TitleBarControl m_activeControl = SC_None; - WId m_winid = 0; + + Qt::WindowFlags m_flags = Qt::Widget; + + WId m_winId = 0; bool m_hasTitle = false; bool m_needsCompositor = false; long m_requestAnimationFrameId = -1; @@ -120,6 +120,5 @@ private: bool windowIsPopupType(Qt::WindowFlags flags) const; }; -Q_DECLARE_OPERATORS_FOR_FLAGS(QWasmWindow::TitleBarControls); QT_END_NAMESPACE #endif // QWASMWINDOW_H diff --git a/src/plugins/platforms/wasm/resources/maximize.svg b/src/plugins/platforms/wasm/resources/maximize.svg new file mode 100644 index 0000000000..b5fad4f707 --- /dev/null +++ b/src/plugins/platforms/wasm/resources/maximize.svg @@ -0,0 +1 @@ +<svg width="1024" height="1024" xmlns="http://www.w3.org/2000/svg" class="svg-icon"><path stroke="null" d="M-.333-.333h1024.666v1024.666H-.333V-.333M127.75 255.833V896.25h768.5V255.833h-768.5z"/></svg>
\ No newline at end of file diff --git a/src/plugins/platforms/wasm/resources/qtlogo.svg b/src/plugins/platforms/wasm/resources/qtlogo.svg new file mode 100644 index 0000000000..bfe2493f46 --- /dev/null +++ b/src/plugins/platforms/wasm/resources/qtlogo.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="616" height="452" viewBox="0 0 462 339"><path fill="#41cd52" d="M63.5 0H462v274.79c-21.4 21.47-42.87 42.87-64.39 64.21H0V63.39C21.08 42.18 42.34 21.13 63.5 0Z"/><path d="M122.37 71.33C137.5 61.32 156.21 58.79 174 58.95c16.94.21 34.72 3.18 48.76 13.29 10.2 7.17 16.83 18.24 21.25 29.69 7.15 18.8 9.25 39.1 9.49 59.08.03 20.12-.88 40.68-7.54 59.85-4.46 13.04-12.95 24.62-24.15 32.66 8.06 13.06 16.28 26.02 24.34 39.08-10.13 4.67-20.23 9.37-30.37 14.02-8.63-14.24-17.22-28.51-25.88-42.73-11.71 1.92-23.69 1.77-35.46.47-14.1-1.69-28.47-5.99-39.35-15.48-8.36-7.24-13.61-17.37-17.2-27.67-5.88-17.42-7.46-35.96-7.73-54.24-.14-19.76 1.12-39.83 7.08-58.79 4.61-14.26 12.24-28.49 25.13-36.85ZM294.13 70.69c10.6-.01 21.2-.01 31.8 0 .03 14.02-.01 28.03.02 42.05 13.55.02 27.1 0 40.65.01-.23 9.1-.48 18.2-.74 27.3-13.54.03-27.07-.01-40.61.02.03 22.98-.07 45.96.05 68.94.26 6.29.12 12.93 2.89 18.74 2.02 4.48 7.46 5.63 11.89 5.78 8.35-.03 16.69-.52 25.04-.67.51 8.36 1 16.73 1.48 25.09-16.61 2.79-34.04 6.13-50.54.91-6.95-2.06-13.43-6.67-16.25-13.54-5.05-11.69-5.46-24.7-5.68-37.25-.02-22.67 0-45.33-.01-68-7.39-.02-14.78.01-22.17-.02-.02-9.09-.02-18.19 0-27.29 7.39-.03 14.77.01 22.16-.02.03-14.02-.01-28.03.02-42.05Z" fill="#fff"/><path fill="#41cd52" d="M160.51 87.7c10.29-1.34 21.09-.98 30.83 2.91 7.89 3.12 14.59 9.23 18.13 16.97 5.43 11.73 7.51 24.68 8.56 37.47 1.14 17.02.98 34.2-1.37 51.12-1.65 10.07-4 20.68-10.82 28.62-6.92 7.97-17.59 11.39-27.83 12.19-10.8.79-22.19 0-31.94-5.11-5.69-3.03-10.52-7.78-13.34-13.6-3.42-6.97-5.3-14.58-6.62-22.2-3.98-24.16-4.94-49.16.5-73.18 2.24-9.06 5.5-18.36 12.12-25.19 5.76-5.85 13.78-8.87 21.78-10Z"/></svg>
\ No newline at end of file diff --git a/src/plugins/platforms/wasm/resources/restore.svg b/src/plugins/platforms/wasm/resources/restore.svg new file mode 100644 index 0000000000..70ee19170b --- /dev/null +++ b/src/plugins/platforms/wasm/resources/restore.svg @@ -0,0 +1 @@ +<svg width="1024" height="1024" xmlns="http://www.w3.org/2000/svg" class="svg-icon"><path stroke="null" d="M449.191 44.905h535.142v528.951H449.191V44.906m66.893 132.237v330.594H917.44V177.143H516.084z"/><path stroke="null" d="M54.906 453.476h535.141v528.952H54.906V453.476m66.892 132.238V916.31h401.357V585.714H121.798z"/></svg>
\ No newline at end of file diff --git a/src/plugins/platforms/wasm/resources/x.svg b/src/plugins/platforms/wasm/resources/x.svg new file mode 100644 index 0000000000..1d9ba7361a --- /dev/null +++ b/src/plugins/platforms/wasm/resources/x.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 460.775 460.775" style="enable-background:new 0 0 460.775 460.775" xml:space="preserve"><path d="M285.08 230.397 456.218 59.27c6.076-6.077 6.076-15.911 0-21.986L423.511 4.565a15.55 15.55 0 0 0-21.985 0l-171.138 171.14L59.25 4.565a15.551 15.551 0 0 0-21.985 0L4.558 37.284c-6.077 6.075-6.077 15.909 0 21.986l171.138 171.128L4.575 401.505c-6.074 6.077-6.074 15.911 0 21.986l32.709 32.719a15.555 15.555 0 0 0 21.986 0l171.117-171.12 171.118 171.12a15.551 15.551 0 0 0 21.985 0l32.709-32.719c6.074-6.075 6.074-15.909 0-21.986L285.08 230.397z"/></svg>
\ No newline at end of file diff --git a/src/plugins/platforms/wasm/wasm_shell.html b/src/plugins/platforms/wasm/wasm_shell.html index f22f64e017..aaa121981d 100644 --- a/src/plugins/platforms/wasm/wasm_shell.html +++ b/src/plugins/platforms/wasm/wasm_shell.html @@ -12,9 +12,8 @@ <title>@APPNAME@</title> <style> /* Make the html body cover the entire (visual) viewport with no scroll bars. */ - html, body { padding: 0; margin: 0; overflow:hidden; height: 100% } - /* Make the canvas cover the entire body */ - canvas { height:100%; width:100%; } + html, body { padding: 0; margin: 0; overflow: hidden; height: 100% } + #screen { width: 100%; height: 100%; } </style> </head> <body onload="init()"> @@ -26,13 +25,13 @@ <noscript>JavaScript is disabled. Please enable JavaScript to use this application.</noscript> </center> </figure> - <canvas id="qtcanvas"></canvas> + <div id="screen"></div> <script type='text/javascript'> let qtLoader = undefined; function init() { var spinner = document.querySelector('#qtspinner'); - var canvas = document.querySelector('#qtcanvas'); + var canvas = document.querySelector('#screen'); var status = document.querySelector('#qtstatus') qtLoader = new QtLoader({ |