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
parent
c675c1c56d
commit
fa27a59ec3
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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 |
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -5,20 +5,16 @@
|
|||
#include "qwasmwindow.h"
|
||||
#include "qwasmcompositor.h"
|
||||
|
||||
#include <QtOpenGL/qopengltexture.h>
|
||||
#include <QtGui/qmatrix4x4.h>
|
||||
#include <QtGui/qpainter.h>
|
||||
#include <private/qguiapplication_p.h>
|
||||
#include <qpa/qplatformscreen.h>
|
||||
#include <QtGui/qoffscreensurface.h>
|
||||
#include <QtGui/qbackingstore.h>
|
||||
|
||||
#include <emscripten.h>
|
||||
#include <emscripten/wire.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
QWasmBackingStore::QWasmBackingStore(QWasmCompositor *compositor, QWindow *window)
|
||||
: QPlatformBackingStore(window)
|
||||
, m_compositor(compositor)
|
||||
, m_texture(new QOpenGLTexture(QOpenGLTexture::Target2D))
|
||||
: QPlatformBackingStore(window), m_compositor(compositor)
|
||||
{
|
||||
QWasmWindow *wasmWindow = static_cast<QWasmWindow *>(window->handle());
|
||||
if (wasmWindow)
|
||||
|
|
@ -29,29 +25,11 @@ QWasmBackingStore::~QWasmBackingStore()
|
|||
{
|
||||
auto window = this->window();
|
||||
QWasmIntegration::get()->removeBackingStore(window);
|
||||
destroy();
|
||||
QWasmWindow *wasmWindow = static_cast<QWasmWindow *>(window->handle());
|
||||
if (wasmWindow)
|
||||
wasmWindow->setBackingStore(nullptr);
|
||||
}
|
||||
|
||||
void QWasmBackingStore::destroy()
|
||||
{
|
||||
if (m_texture->isCreated()) {
|
||||
auto context = m_compositor->context();
|
||||
auto currentContext = QOpenGLContext::currentContext();
|
||||
if (!currentContext || !QOpenGLContext::areSharing(context, currentContext)) {
|
||||
QOffscreenSurface offScreenSurface(m_compositor->screen()->screen());
|
||||
offScreenSurface.setFormat(context->format());
|
||||
offScreenSurface.create();
|
||||
context->makeCurrent(&offScreenSurface);
|
||||
m_texture->destroy();
|
||||
} else {
|
||||
m_texture->destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QPaintDevice *QWasmBackingStore::paintDevice()
|
||||
{
|
||||
return &m_image;
|
||||
|
|
@ -64,33 +42,24 @@ void QWasmBackingStore::flush(QWindow *window, const QRegion ®ion, const QPoi
|
|||
Q_UNUSED(offset);
|
||||
|
||||
m_dirty |= region;
|
||||
m_compositor->handleBackingStoreFlush();
|
||||
m_compositor->handleBackingStoreFlush(window);
|
||||
}
|
||||
|
||||
void QWasmBackingStore::updateTexture()
|
||||
void QWasmBackingStore::updateTexture(QWasmWindow *window)
|
||||
{
|
||||
if (m_dirty.isNull())
|
||||
return;
|
||||
|
||||
if (m_recreateTexture) {
|
||||
m_recreateTexture = false;
|
||||
destroy();
|
||||
if (m_webImageDataArray.isUndefined()) {
|
||||
m_webImageDataArray = window->context2d().call<emscripten::val>(
|
||||
"createImageData", emscripten::val(m_image.width()),
|
||||
emscripten::val(m_image.height()));
|
||||
}
|
||||
|
||||
if (!m_texture->isCreated()) {
|
||||
m_texture->setMinificationFilter(QOpenGLTexture::Nearest);
|
||||
m_texture->setMagnificationFilter(QOpenGLTexture::Nearest);
|
||||
m_texture->setWrapMode(QOpenGLTexture::ClampToEdge);
|
||||
m_texture->setData(m_image, QOpenGLTexture::DontGenerateMipMaps);
|
||||
m_texture->create();
|
||||
}
|
||||
m_texture->bind();
|
||||
|
||||
QRegion fixed;
|
||||
QRegion clippedDpiScaledRegion;
|
||||
QRect imageRect = m_image.rect();
|
||||
|
||||
for (const QRect &rect : m_dirty) {
|
||||
|
||||
// Convert device-independent dirty region to device region
|
||||
qreal dpr = m_image.devicePixelRatio();
|
||||
QRect deviceRect = QRect(rect.topLeft() * dpr, rect.size() * dpr);
|
||||
|
|
@ -103,21 +72,40 @@ void QWasmBackingStore::updateTexture()
|
|||
r.setWidth(imageRect.width());
|
||||
}
|
||||
|
||||
fixed |= r;
|
||||
clippedDpiScaledRegion |= r;
|
||||
}
|
||||
|
||||
for (const QRect &rect : fixed) {
|
||||
// if the sub-rect is full-width we can pass the image data directly to
|
||||
// OpenGL instead of copying, since there is no gap between scanlines
|
||||
if (rect.width() == imageRect.width()) {
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, rect.y(), rect.width(), rect.height(), GL_RGBA, GL_UNSIGNED_BYTE,
|
||||
m_image.constScanLine(rect.y()));
|
||||
for (const QRect &dirtyRect : clippedDpiScaledRegion) {
|
||||
constexpr int BytesPerColor = 4;
|
||||
if (dirtyRect.width() == imageRect.width()) {
|
||||
// Copy a contiguous chunk of memory
|
||||
// ...............
|
||||
// OOOOOOOOOOOOOOO
|
||||
// OOOOOOOOOOOOOOO -> image data
|
||||
// OOOOOOOOOOOOOOO
|
||||
// ...............
|
||||
auto imageMemory = emscripten::typed_memory_view(dirtyRect.width() * dirtyRect.height()
|
||||
* BytesPerColor,
|
||||
m_image.constScanLine(dirtyRect.y()));
|
||||
m_webImageDataArray["data"].call<void>("set", imageMemory);
|
||||
} else {
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, rect.x(), rect.y(), rect.width(), rect.height(), GL_RGBA, GL_UNSIGNED_BYTE,
|
||||
m_image.copy(rect).constBits());
|
||||
// Go through the scanlines manually to set the individual lines in bulk. This is
|
||||
// marginally less performant than the above.
|
||||
// ...............
|
||||
// ...OOOOOOOOO... r = 0 -> image data
|
||||
// ...OOOOOOOOO... r = 1 -> image data
|
||||
// ...OOOOOOOOO... r = 2 -> image data
|
||||
// ...............
|
||||
for (int r = 0; r < dirtyRect.height(); ++r) {
|
||||
auto scanlineMemory = emscripten::typed_memory_view(
|
||||
dirtyRect.width() * 4,
|
||||
m_image.constScanLine(r) + BytesPerColor * dirtyRect.x());
|
||||
m_webImageDataArray["data"].call<void>("set", scanlineMemory,
|
||||
(r * dirtyRect.width() + dirtyRect.x())
|
||||
* BytesPerColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* End of code taken from QEGLPlatformBackingStore */
|
||||
|
||||
m_dirty = QRegion();
|
||||
}
|
||||
|
|
@ -143,12 +131,11 @@ void QWasmBackingStore::resize(const QSize &size, const QRegion &staticContents)
|
|||
{
|
||||
Q_UNUSED(staticContents);
|
||||
|
||||
QImage::Format format = window()->format().hasAlpha() ?
|
||||
QImage::Format_ARGB32_Premultiplied : QImage::Format_RGB32;
|
||||
QImage::Format format = QImage::Format_RGBA8888;
|
||||
const auto platformScreenDPR = window()->handle()->devicePixelRatio();
|
||||
m_image = QImage(size * platformScreenDPR, format);
|
||||
m_image.setDevicePixelRatio(platformScreenDPR);
|
||||
m_recreateTexture = true;
|
||||
m_webImageDataArray = emscripten::val::undefined();
|
||||
}
|
||||
|
||||
QImage QWasmBackingStore::toImage() const
|
||||
|
|
@ -162,10 +149,10 @@ const QImage &QWasmBackingStore::getImageRef() const
|
|||
return m_image;
|
||||
}
|
||||
|
||||
const QOpenGLTexture *QWasmBackingStore::getUpdatedTexture()
|
||||
emscripten::val QWasmBackingStore::getUpdatedWebImage(QWasmWindow *window)
|
||||
{
|
||||
updateTexture();
|
||||
return m_texture.data();
|
||||
updateTexture(window);
|
||||
return m_webImageDataArray;
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,9 +11,6 @@ QWasmOffscrenSurface::QWasmOffscrenSurface(QOffscreenSurface *offscreenSurface)
|
|||
|
||||
}
|
||||
|
||||
QWasmOffscrenSurface::~QWasmOffscrenSurface()
|
||||
{
|
||||
|
||||
}
|
||||
QWasmOffscrenSurface::~QWasmOffscrenSurface() = default;
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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({
|
||||
|
|
|
|||
Loading…
Reference in New Issue