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>
bb10
Mikolaj Boc 2022-11-14 09:12:34 +01:00
parent c675c1c56d
commit fa27a59ec3
28 changed files with 940 additions and 974 deletions

View File

@ -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

View File

@ -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);
}

View File

@ -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")

View File

@ -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>

Before

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -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");

View File

@ -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

View File

@ -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

View File

@ -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"),

View File

@ -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);
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(canvasSelector.constData(), (void *)this, UseCapture, &focus_cb);
emscripten_set_focus_callback(screenElementSelector.constData(), (void *)this, UseCapture,
&focus_cb);
emscripten_set_wheel_callback(canvasSelector.constData(), (void *)this, UseCapture, &wheel_cb);
emscripten_set_wheel_callback(screenElementSelector.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);
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);
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()))));
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 (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(); });
}
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();
}
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;
}

View File

@ -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;
};

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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");

View File

@ -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;

View File

@ -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();
}

View File

@ -11,9 +11,6 @@ QWasmOffscrenSurface::QWasmOffscrenSurface(QOffscreenSurface *offscreenSurface)
}
QWasmOffscrenSurface::~QWasmOffscrenSurface()
{
}
QWasmOffscrenSurface::~QWasmOffscrenSurface() = default;
QT_END_NAMESPACE

View File

@ -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)
return false;
if (m_context)
return m_screen == screen;
if (m_context && m_surface == surface)
return true;
m_context = createEmscriptenContext(QWasmScreen::get(screen)->canvasTargetId(), m_requestedFormat);
m_screen = screen;
// TODO(mikolajboc): Use OffscreenCanvas if available.
if (surface->surface()->surfaceClass() == QSurface::Offscreen)
return false;
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

View File

@ -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;
};

View File

@ -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
emscripten::val QWasmScreen::element() const
{
return m_container;
return m_shadowContainer;
}
emscripten::val QWasmScreen::canvas() const
{
return m_canvas;
}
// 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

View File

@ -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;
};

View File

@ -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)
enum class IconType {
Maximize,
First = Maximize,
QtLogo,
Restore,
X,
Size,
};
void syncCSSClassWith(emscripten::val element, std::string cssClassName, bool flag)
{
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);
if (flag) {
element["classList"].call<void>("add", emscripten::val(std::move(cssClassName)));
return;
}
return result;
}
QPalette makePalette()
element["classList"].call<void>("remove", emscripten::val(std::move(cssClassName)));
}
} // namespace
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;
invalidate();
window()->setWindowState(Qt::WindowMaximized);
}
int QWasmWindow::titleHeight() const
void QWasmWindow::onCloseClicked()
{
return 18. * (qreal(qt_defaultDpiX()) / 96.0);//dpiScaled(18.);
window()->close();
}
int QWasmWindow::borderWidth() const
void QWasmWindow::onInteraction()
{
return 4. * (qreal(qt_defaultDpiX()) / 96.0);// dpiScaled(4.);
if (!isActive())
requestActivateWindow();
}
QMarginsF QWasmWindow::borderMargins() const
{
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,228 +498,85 @@ 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::setWindowTitle(const QString &title)
{
m_label.set("innerText", emscripten::val(title.toStdString()));
}
void QWasmWindow::setWindowIcon(const QIcon &icon)
{
const auto dpi = screen()->devicePixelRatio();
auto pixmap = icon.pixmap(10 * dpi, 10 * dpi);
if (pixmap.isNull()) {
m_icon->setImage(IconType::QtLogo);
return;
}
QByteArray bytes;
QBuffer buffer(&bytes);
pixmap.save(&buffer, "png");
m_icon->setImage(bytes.toBase64().toStdString(), "png");
}
void QWasmWindow::applyWindowState()
{
QRect newGeom;
if (m_windowState.testFlag(Qt::WindowFullScreen))
const bool isFullscreen = m_state.testFlag(Qt::WindowFullScreen);
const bool isMaximized = m_state.testFlag(Qt::WindowMaximized);
if (isFullscreen)
newGeom = platformScreen()->geometry();
else if (m_windowState.testFlag(Qt::WindowMaximized))
newGeom = platformScreen()->availableGeometry();
else if (isMaximized)
newGeom = platformScreen()->availableGeometry().marginsRemoved(frameMargins());
else
newGeom = normalGeometry();
QWindowSystemInterface::handleWindowStateChanged(window(), m_windowState, m_previousWindowState);
syncCSSClassWith(m_qtWindow, "has-title-bar", hasTitleBar());
m_restore->setVisible(isMaximized);
m_maximize->setVisible(!isMaximized);
if (isVisible())
QWindowSystemInterface::handleWindowStateChanged(window(), m_state, m_previousWindowState);
setGeometry(newGeom);
}
void QWasmWindow::drawTitleBar(QPainter *painter) const
{
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)));
}
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)));
}
}
}
QWasmWindow::TitleBarOptions QWasmWindow::makeTitleBarOptions() const
{
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);
if (activeTitleBarControl() != SC_None)
titleBarOptions.subControls |= activeTitleBarControl();
if (!window()->title().isEmpty())
titleBarOptions.titleBarOptionsString = window()->title();
titleBarOptions.windowIcon = window()->icon();
return titleBarOptions;
}
QRect QWasmWindow::normalGeometry() const
{
return m_normalGeometry;
@ -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

View File

@ -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;
class WebImageButton;
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;
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

View File

@ -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>

After

Width:  |  Height:  |  Size: 201 B

View File

@ -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>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -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>

After

Width:  |  Height:  |  Size: 328 B

View File

@ -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>

After

Width:  |  Height:  |  Size: 599 B

View File

@ -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({