summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorMikolaj Boc <mikolaj.boc@qt.io>2022-11-14 09:12:34 +0100
committerMikolaj Boc <mikolaj.boc@qt.io>2022-11-26 11:23:13 +0100
commitfa27a59ec3cfc173d5ff202feb23e8b6e398dac9 (patch)
treea34f7a199611118e826a029a5bebf93f9425b32d /src
parentc675c1c56d3c00b3c1de5aee8c7c6f7eeeb70bc1 (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')
-rw-r--r--src/corelib/doc/src/cmake/qt_finalize_target.qdoc2
-rw-r--r--src/corelib/platform/wasm/qstdweb.cpp6
-rw-r--r--src/plugins/platforms/wasm/CMakeLists.txt20
-rw-r--r--src/plugins/platforms/wasm/qtlogo.svg40
-rw-r--r--src/plugins/platforms/wasm/qwasmaccessibility.cpp7
-rw-r--r--src/plugins/platforms/wasm/qwasmbackingstore.cpp103
-rw-r--r--src/plugins/platforms/wasm/qwasmbackingstore.h11
-rw-r--r--src/plugins/platforms/wasm/qwasmclipboard.cpp4
-rw-r--r--src/plugins/platforms/wasm/qwasmcompositor.cpp397
-rw-r--r--src/plugins/platforms/wasm/qwasmcompositor.h30
-rw-r--r--src/plugins/platforms/wasm/qwasmcssstyle.cpp181
-rw-r--r--src/plugins/platforms/wasm/qwasmcssstyle.h18
-rw-r--r--src/plugins/platforms/wasm/qwasmcursor.cpp5
-rw-r--r--src/plugins/platforms/wasm/qwasminputcontext.cpp14
-rw-r--r--src/plugins/platforms/wasm/qwasminputcontext.h3
-rw-r--r--src/plugins/platforms/wasm/qwasmintegration.cpp60
-rw-r--r--src/plugins/platforms/wasm/qwasmoffscreensurface.cpp5
-rw-r--r--src/plugins/platforms/wasm/qwasmopenglcontext.cpp63
-rw-r--r--src/plugins/platforms/wasm/qwasmopenglcontext.h5
-rw-r--r--src/plugins/platforms/wasm/qwasmscreen.cpp151
-rw-r--r--src/plugins/platforms/wasm/qwasmscreen.h11
-rw-r--r--src/plugins/platforms/wasm/qwasmwindow.cpp666
-rw-r--r--src/plugins/platforms/wasm/qwasmwindow.h99
-rw-r--r--src/plugins/platforms/wasm/resources/maximize.svg1
-rw-r--r--src/plugins/platforms/wasm/resources/qtlogo.svg1
-rw-r--r--src/plugins/platforms/wasm/resources/restore.svg1
-rw-r--r--src/plugins/platforms/wasm/resources/x.svg1
-rw-r--r--src/plugins/platforms/wasm/wasm_shell.html9
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 &region, 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({