wasm: support rendering to multiple canvases

Qt (via the the qtloader.js API) now supports rendering
to multiple canvases. The application sees each canvas
as a QScreen.

Make qtloader.js support multiple canvases:

  var qtloader = QtLoader({
    canvasElements : [array-of-canvas],
    showCanvas: function() {
      // make canvas(es) visible
    },
 });

The canvases were previously created/returned by showCanvas(),
however this function is called after the Qt app has
been started and adding screens that that point is
too late. (This worked before since there was only one
screen, and no need to connect each screen instance
to specific canvas.)

Remove QWasmScreen, QWasmCompositor, and QWasmEventTranslator
singletons from QWasmIntegration. These are are now
crated per-screen and are owned by the QWasmScreen.

Task-number: QTBUG-64079
Change-Id: I24689929fd5bfb7ff0ba076f66937728fa4bc4e4
Reviewed-by: Lorn Potter <lorn.potter@gmail.com>
This commit is contained in:
Morten Johan Sørvig 2019-02-15 22:04:09 +01:00
parent 452c644c5c
commit caa74f16d4
14 changed files with 221 additions and 153 deletions

View File

@ -50,6 +50,7 @@
// External mode.usage: // External mode.usage:
// //
// var config = { // var config = {
// canvasElements : [$("canvas-id")],
// showLoader: function() { // showLoader: function() {
// loader.style.display = 'block' // loader.style.display = 'block'
// canvas.style.display = 'hidden' // canvas.style.display = 'hidden'
@ -69,6 +70,8 @@
// One or more HTML elements. QtLoader will display loader elements // One or more HTML elements. QtLoader will display loader elements
// on these while loading the applicaton, and replace the loader with a // on these while loading the applicaton, and replace the loader with a
// canvas on load complete. // canvas on load complete.
// canvasElements : [canvas-element, ...]
// One or more canvas elements.
// showLoader : function(status, containerElement) // showLoader : function(status, containerElement)
// Optional loading element constructor function. Implement to create // Optional loading element constructor function. Implement to create
// a custom loading screen. This function may be called multiple times, // a custom loading screen. This function may be called multiple times,
@ -146,8 +149,25 @@ function QtLoader(config)
while (element.firstChild) element.removeChild(element.firstChild); while (element.firstChild) element.removeChild(element.firstChild);
} }
// Set default state handler functions if needed function createCanvas() {
var canvas = document.createElement("canvas");
canvas.className = "QtCanvas";
canvas.style.height = "100%";
canvas.style.width = "100%";
// Set contentEditable in order to enable clipboard events; hide the resulting focus frame.
canvas.contentEditable = true;
canvas.style.outline = "0px solid transparent";
canvas.style.cursor = "default";
return canvas;
}
// Set default state handler functions and create canvases if needed
if (config.containerElements !== undefined) { if (config.containerElements !== undefined) {
config.canvasElements = config.containerElements.map(createCanvas);
config.showError = config.showError || function(errorText, container) { config.showError = config.showError || function(errorText, container) {
removeChildren(container); removeChildren(container);
var errorTextElement = document.createElement("text"); var errorTextElement = document.createElement("text");
@ -164,19 +184,8 @@ function QtLoader(config)
return loadingText; return loadingText;
}; };
config.showCanvas = config.showCanvas || function(container) { config.showCanvas = config.showCanvas || function(canvas, container) {
removeChildren(container); removeChildren(container);
var canvas = document.createElement("canvas");
canvas.className = "QtCanvas"
canvas.style.height = "100%"
canvas.style.width = "100%"
// Set contentEditable in order to enable clipboard events; hide the resulting focus frame.
canvas.contentEditable = true;
canvas.style.outline = "0px solid transparent";
canvas.style.cursor = "default";
return canvas;
} }
config.showExit = config.showExit || function(crashed, exitCode, container) { config.showExit = config.showExit || function(crashed, exitCode, container) {
@ -391,6 +400,8 @@ function QtLoader(config)
Module.mainScriptUrlOrBlob = new Blob([emscriptenModuleSource], {type: 'text/javascript'}); Module.mainScriptUrlOrBlob = new Blob([emscriptenModuleSource], {type: 'text/javascript'});
Module.qtCanvasElements = config.canvasElements;
config.restart = function() { config.restart = function() {
// Restart by reloading the page. This will wipe all state which means // Restart by reloading the page. This will wipe all state which means
@ -445,19 +456,17 @@ function QtLoader(config)
} }
function setCanvasContent() { function setCanvasContent() {
var firstCanvas;
if (config.containerElements === undefined) { if (config.containerElements === undefined) {
firstCanvas = config.showCanvas(); if (config.showCanvas !== undefined)
} else { config.showCanvas();
for (container of config.containerElements) { return;
var canvasElement = config.showCanvas(container);
container.appendChild(canvasElement);
}
firstCanvas = config.containerElements[0].firstChild;
} }
if (Module.canvas === undefined) { for (var i = 0; i < config.containerElements.length; ++i) {
Module.canvas = firstCanvas; var container = config.containerElements[i];
var canvas = config.canvasElements[i];
config.showCanvas(canvas, container);
container.appendChild(canvas);
} }
} }

View File

@ -123,9 +123,11 @@ EMSCRIPTEN_BINDINGS(clipboard_module) {
function("qClipboardPasteTo", &qClipboardPasteTo); function("qClipboardPasteTo", &qClipboardPasteTo);
} }
QWasmClipboard::QWasmClipboard() : QWasmClipboard::QWasmClipboard()
hasClipboardApi(false)
{ {
val clipboard = val::global("navigator")["clipboard"];
hasClipboardApi = (!clipboard.isUndefined() && !clipboard["readText"].isUndefined());
initClipboardEvents(); initClipboardEvents();
} }
@ -177,29 +179,32 @@ void QWasmClipboard::qWasmClipboardPaste(QMimeData *mData)
void QWasmClipboard::initClipboardEvents() void QWasmClipboard::initClipboardEvents()
{ {
val navigator = val::global("navigator"); if (!hasClipboardApi)
val permissions = navigator["permissions"]; return;
val clipboard = navigator["clipboard"];
hasClipboardApi = (!clipboard.isUndefined() && !clipboard["readText"].isUndefined()); val permissions = val::global("navigator")["permissions"];
if (hasClipboardApi) { val readPermissionsMap = val::object();
val readPermissionsMap = val::object(); readPermissionsMap.set("name", val("clipboard-read"));
readPermissionsMap.set("name", val("clipboard-read")); permissions.call<val>("query", readPermissionsMap);
permissions.call<val>("query", readPermissionsMap);
val writePermissionsMap = val::object(); val writePermissionsMap = val::object();
writePermissionsMap.set("name", val("clipboard-write")); writePermissionsMap.set("name", val("clipboard-write"));
permissions.call<val>("query", writePermissionsMap); permissions.call<val>("query", writePermissionsMap);
}
} else { void QWasmClipboard::installEventHandlers(const QString &canvasId)
val canvas = val::module_property("canvas"); {
canvas.call<void>("addEventListener", std::string("cut"), if (hasClipboardApi)
val::module_property("qClipboardCutTo")); return;
canvas.call<void>("addEventListener", std::string("copy"),
val::module_property("qClipboardCopyTo")); // Fallback path for browsers which do not support direct clipboard access
canvas.call<void>("addEventListener", std::string("paste"), val canvas = val::global(canvasId.toUtf8().constData());
val::module_property("qClipboardPasteTo")); canvas.call<void>("addEventListener", std::string("cut"),
} val::module_property("qClipboardCutTo"));
canvas.call<void>("addEventListener", std::string("copy"),
val::module_property("qClipboardCopyTo"));
canvas.call<void>("addEventListener", std::string("paste"),
val::module_property("qClipboardPasteTo"));
} }
void QWasmClipboard::readTextFromClipboard() void QWasmClipboard::readTextFromClipboard()

View File

@ -51,6 +51,7 @@ public:
static void qWasmClipboardPaste(QMimeData *mData); static void qWasmClipboardPaste(QMimeData *mData);
void initClipboardEvents(); void initClipboardEvents();
void installEventHandlers(const QString &canvasId);
bool hasClipboardApi; bool hasClipboardApi;
void readTextFromClipboard(); void readTextFromClipboard();
void writeTextToClipboard(); void writeTextToClipboard();

View File

@ -56,8 +56,9 @@ QWasmCompositedWindow::QWasmCompositedWindow()
{ {
} }
QWasmCompositor::QWasmCompositor() QWasmCompositor::QWasmCompositor(QWasmScreen *screen)
: m_frameBuffer(nullptr) :QObject(screen)
, m_frameBuffer(nullptr)
, m_blitter(new QOpenGLTextureBlitter) , m_blitter(new QOpenGLTextureBlitter)
, m_needComposit(false) , m_needComposit(false)
, m_inFlush(false) , m_inFlush(false)
@ -107,11 +108,6 @@ void QWasmCompositor::removeWindow(QWasmWindow *window)
notifyTopWindowChanged(window); notifyTopWindowChanged(window);
} }
void QWasmCompositor::setScreen(QWasmScreen *screen)
{
m_screen = screen;
}
void QWasmCompositor::setVisible(QWasmWindow *window, bool visible) void QWasmCompositor::setVisible(QWasmWindow *window, bool visible)
{ {
QWasmCompositedWindow &compositedWindow = m_compositedWindows[window]; QWasmCompositedWindow &compositedWindow = m_compositedWindows[window];
@ -654,7 +650,7 @@ void QWasmCompositor::frame()
m_needComposit = false; m_needComposit = false;
if (m_windowStack.empty() || !m_screen) if (m_windowStack.empty() || !screen())
return; return;
QWasmWindow *someWindow = nullptr; QWasmWindow *someWindow = nullptr;
@ -673,7 +669,7 @@ void QWasmCompositor::frame()
if (m_context.isNull()) { if (m_context.isNull()) {
m_context.reset(new QOpenGLContext()); m_context.reset(new QOpenGLContext());
//mContext->setFormat(mScreen->format()); //mContext->setFormat(mScreen->format());
m_context->setScreen(m_screen->screen()); m_context->setScreen(screen()->screen());
m_context->create(); m_context->create();
} }
@ -682,8 +678,8 @@ void QWasmCompositor::frame()
if (!m_blitter->isCreated()) if (!m_blitter->isCreated())
m_blitter->create(); m_blitter->create();
qreal dpr = m_screen->devicePixelRatio(); qreal dpr = screen()->devicePixelRatio();
glViewport(0, 0, m_screen->geometry().width() * dpr, m_screen->geometry().height() * dpr); 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()->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_context->functions()->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
@ -697,7 +693,7 @@ void QWasmCompositor::frame()
if (!compositedWindow.visible) if (!compositedWindow.visible)
continue; continue;
drawWindow(m_blitter.data(), m_screen, window); drawWindow(m_blitter.data(), screen(), window);
} }
m_blitter->release(); m_blitter->release();
@ -719,3 +715,8 @@ void QWasmCompositor::notifyTopWindowChanged(QWasmWindow *window)
requestRedraw(); requestRedraw();
QWindowSystemInterface::handleWindowActivated(window->window()); QWindowSystemInterface::handleWindowActivated(window->window());
} }
QWasmScreen *QWasmCompositor::screen()
{
return static_cast<QWasmScreen *>(parent());
}

View File

@ -62,7 +62,7 @@ class QWasmCompositor : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
QWasmCompositor(); QWasmCompositor(QWasmScreen *screen);
~QWasmCompositor(); ~QWasmCompositor();
enum QWasmSubControl { enum QWasmSubControl {
@ -103,7 +103,6 @@ public:
void addWindow(QWasmWindow *window, QWasmWindow *parentWindow = nullptr); void addWindow(QWasmWindow *window, QWasmWindow *parentWindow = nullptr);
void removeWindow(QWasmWindow *window); void removeWindow(QWasmWindow *window);
void setScreen(QWasmScreen *screen);
void setVisible(QWasmWindow *window, bool visible); void setVisible(QWasmWindow *window, bool visible);
void raise(QWasmWindow *window); void raise(QWasmWindow *window);
@ -129,8 +128,7 @@ private slots:
void frame(); void frame();
private: private:
void createFrameBuffer(); QWasmScreen *screen();
void flushCompletedCallback(int32_t);
void notifyTopWindowChanged(QWasmWindow *window); void notifyTopWindowChanged(QWasmWindow *window);
void drawWindow(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, QWasmWindow *window); void drawWindow(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, QWasmWindow *window);
void drawWindowContent(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, QWasmWindow *window); void drawWindowContent(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, QWasmWindow *window);
@ -142,7 +140,6 @@ private:
QImage *m_frameBuffer; QImage *m_frameBuffer;
QScopedPointer<QOpenGLContext> m_context; QScopedPointer<QOpenGLContext> m_context;
QScopedPointer<QOpenGLTextureBlitter> m_blitter; QScopedPointer<QOpenGLTextureBlitter> m_blitter;
QWasmScreen *m_screen;
QHash<QWasmWindow *, QWasmCompositedWindow> m_compositedWindows; QHash<QWasmWindow *, QWasmCompositedWindow> m_compositedWindows;
QList<QWasmWindow *> m_windowStack; QList<QWasmWindow *> m_windowStack;

View File

@ -28,20 +28,21 @@
****************************************************************************/ ****************************************************************************/
#include "qwasmcursor.h" #include "qwasmcursor.h"
#include "qwasmscreen.h"
#include <QtCore/qdebug.h> #include <QtCore/qdebug.h>
#include <QtGui/qwindow.h>
#include <emscripten/emscripten.h> #include <emscripten/emscripten.h>
#include <emscripten/bind.h> #include <emscripten/bind.h>
void QWasmCursor::changeCursor(QCursor *windowCursor, QWindow *window) void QWasmCursor::changeCursor(QCursor *windowCursor, QWindow *window)
{ {
if (windowCursor == nullptr) if (!windowCursor || !window)
return;
QScreen *screen = window->screen();
if (!screen)
return; return;
// FIXME: The HTML5 plugin sets the cursor on the native canvas; when using multiple windows
// multiple cursors need to be managed taking mouse postion and stacking into account.
Q_UNUSED(window);
// Bitmap and custom cursors are not implemented (will fall back to "auto") // Bitmap and custom cursors are not implemented (will fall back to "auto")
if (windowCursor->shape() == Qt::BitmapCursor || windowCursor->shape() >= Qt::CustomCursor) if (windowCursor->shape() == Qt::BitmapCursor || windowCursor->shape() >= Qt::CustomCursor)
@ -52,8 +53,9 @@ void QWasmCursor::changeCursor(QCursor *windowCursor, QWindow *window)
if (htmlCursorName.isEmpty()) if (htmlCursorName.isEmpty())
htmlCursorName = "auto"; htmlCursorName = "auto";
// Set cursor on the main canvas // Set cursor on the canvas
emscripten::val canvasStyle = emscripten::val::module_property("canvas")["style"]; QString canvasId = QWasmScreen::get(screen)->canvasId();
emscripten::val canvasStyle = emscripten::val::global(canvasId.toUtf8().constData())["style"];
canvasStyle.set("cursor", emscripten::val(htmlCursorName.constData())); canvasStyle.set("cursor", emscripten::val(htmlCursorName.constData()));
} }

View File

@ -320,8 +320,8 @@ EMSCRIPTEN_BINDINGS(mouse_module) {
function("mouseWheelEvent", &mouseWheelEvent); function("mouseWheelEvent", &mouseWheelEvent);
} }
QWasmEventTranslator::QWasmEventTranslator(QObject *parent) QWasmEventTranslator::QWasmEventTranslator(QWasmScreen *screen)
: QObject(parent) : QObject(screen)
, draggedWindow(nullptr) , draggedWindow(nullptr)
, lastWindow(nullptr) , lastWindow(nullptr)
, pressedButtons(Qt::NoButton) , pressedButtons(Qt::NoButton)
@ -332,13 +332,16 @@ QWasmEventTranslator::QWasmEventTranslator(QObject *parent)
touchDevice->setCapabilities(QTouchDevice::Position | QTouchDevice::Area | QTouchDevice::NormalizedPosition); touchDevice->setCapabilities(QTouchDevice::Position | QTouchDevice::Area | QTouchDevice::NormalizedPosition);
QWindowSystemInterface::registerTouchDevice(touchDevice); QWindowSystemInterface::registerTouchDevice(touchDevice);
QWasmScreen *wasmScreen = QWasmIntegration::get()->screen(); initEventHandlers();
initEventHandlers(wasmScreen->m_canvasId);
} }
void QWasmEventTranslator::initEventHandlers(const QString &canvas) void QWasmEventTranslator::initEventHandlers()
{ {
const char *canvasId = canvas.toLocal8Bit().constData(); qDebug() << "QWasmEventTranslator::initEventHandlers";
QByteArray _canvasId = screen()->canvasId().toUtf8();
const char *canvasId = _canvasId.constData();
// The Platform Detect: expand coverage and move as needed // The Platform Detect: expand coverage and move as needed
enum Platform { enum Platform {
GenericPlatform, GenericPlatform,
@ -375,7 +378,7 @@ void QWasmEventTranslator::initEventHandlers(const QString &canvas)
emscripten_set_touchmove_callback(canvasId, (void *)this, 1, &touchCallback); emscripten_set_touchmove_callback(canvasId, (void *)this, 1, &touchCallback);
emscripten_set_touchcancel_callback(canvasId, (void *)this, 1, &touchCallback); emscripten_set_touchcancel_callback(canvasId, (void *)this, 1, &touchCallback);
emscripten_set_resize_callback(canvasId, (void *)this, 1, uiEvent_cb); emscripten_set_resize_callback(nullptr, (void *)this, 1, uiEvent_cb); // Note: handles browser window resize
} }
@ -425,6 +428,11 @@ int QWasmEventTranslator::keyboard_cb(int eventType, const EmscriptenKeyboardEve
return accepted ? 1 : 0; return accepted ? 1 : 0;
} }
QWasmScreen *QWasmEventTranslator::screen()
{
return static_cast<QWasmScreen *>(parent());
}
Qt::Key QWasmEventTranslator::translateEmscriptKey(const EmscriptenKeyboardEvent *emscriptKey) Qt::Key QWasmEventTranslator::translateEmscriptKey(const EmscriptenKeyboardEvent *emscriptKey)
{ {
Qt::Key qtKey = Qt::Key_unknown; Qt::Key qtKey = Qt::Key_unknown;
@ -541,14 +549,14 @@ void resizeWindow(QWindow *window, QWasmWindow::ResizeMode mode,
void QWasmEventTranslator::processMouse(int eventType, const EmscriptenMouseEvent *mouseEvent) void QWasmEventTranslator::processMouse(int eventType, const EmscriptenMouseEvent *mouseEvent)
{ {
auto timestamp = mouseEvent->timestamp; auto timestamp = mouseEvent->timestamp;
QPoint point(mouseEvent->canvasX, mouseEvent->canvasY); QPoint point(mouseEvent->targetX, mouseEvent->targetY);
QEvent::Type buttonEventType = QEvent::None; QEvent::Type buttonEventType = QEvent::None;
Qt::MouseButton button = translateMouseButton(mouseEvent->button); Qt::MouseButton button = translateMouseButton(mouseEvent->button);
Qt::KeyboardModifiers modifiers = translateMouseEventModifier(mouseEvent); Qt::KeyboardModifiers modifiers = translateMouseEventModifier(mouseEvent);
QWindow *window2 = QWasmIntegration::get()->compositor()->windowAt(point, 5); QWindow *window2 = screen()->compositor()->windowAt(point, 5);
if (window2 != nullptr) if (window2 != nullptr)
lastWindow = window2; lastWindow = window2;
@ -645,6 +653,7 @@ int QWasmEventTranslator::wheel_cb(int eventType, const EmscriptenWheelEvent *wh
{ {
Q_UNUSED(eventType) Q_UNUSED(eventType)
QWasmEventTranslator *eventTranslator = static_cast<QWasmEventTranslator *>(userData);
EmscriptenMouseEvent mouseEvent = wheelEvent->mouse; EmscriptenMouseEvent mouseEvent = wheelEvent->mouse;
int scrollFactor = 0; int scrollFactor = 0;
@ -668,7 +677,7 @@ int QWasmEventTranslator::wheel_cb(int eventType, const EmscriptenWheelEvent *wh
auto timestamp = mouseEvent.timestamp; auto timestamp = mouseEvent.timestamp;
QPoint globalPoint(mouseEvent.canvasX, mouseEvent.canvasY); QPoint globalPoint(mouseEvent.canvasX, mouseEvent.canvasY);
QWindow *window2 = QWasmIntegration::get()->compositor()->windowAt(globalPoint, 5); QWindow *window2 = eventTranslator->screen()->compositor()->windowAt(globalPoint, 5);
QPoint localPoint(globalPoint.x() - window2->geometry().x(), globalPoint.y() - window2->geometry().y()); QPoint localPoint(globalPoint.x() - window2->geometry().x(), globalPoint.y() - window2->geometry().y());
@ -686,6 +695,7 @@ int QWasmEventTranslator::wheel_cb(int eventType, const EmscriptenWheelEvent *wh
int QWasmEventTranslator::touchCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData) int QWasmEventTranslator::touchCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData)
{ {
QWasmEventTranslator *eventTranslator = static_cast<QWasmEventTranslator *>(userData);
QList<QWindowSystemInterface::TouchPoint> touchPointList; QList<QWindowSystemInterface::TouchPoint> touchPointList;
touchPointList.reserve(touchEvent->numTouches); touchPointList.reserve(touchEvent->numTouches);
QWindow *window2; QWindow *window2;
@ -695,7 +705,7 @@ int QWasmEventTranslator::touchCallback(int eventType, const EmscriptenTouchEven
const EmscriptenTouchPoint *touches = &touchEvent->touches[i]; const EmscriptenTouchPoint *touches = &touchEvent->touches[i];
QPoint point(touches->canvasX, touches->canvasY); QPoint point(touches->canvasX, touches->canvasY);
window2 = QWasmIntegration::get()->compositor()->windowAt(point, 5); window2 = eventTranslator->screen()->compositor()->windowAt(point, 5);
QWindowSystemInterface::TouchPoint touchPoint; QWindowSystemInterface::TouchPoint touchPoint;
@ -878,15 +888,13 @@ bool QWasmEventTranslator::processKeyboard(int eventType, const EmscriptenKeyboa
int QWasmEventTranslator::uiEvent_cb(int eventType, const EmscriptenUiEvent *e, void *userData) int QWasmEventTranslator::uiEvent_cb(int eventType, const EmscriptenUiEvent *e, void *userData)
{ {
Q_UNUSED(e) Q_UNUSED(e)
Q_UNUSED(userData) QWasmEventTranslator *eventTranslator = static_cast<QWasmEventTranslator *>(userData);
if (eventType == EMSCRIPTEN_EVENT_RESIZE) { if (eventType == EMSCRIPTEN_EVENT_RESIZE) {
// This resize event is called when the HTML window is resized. Depending // This resize event is called when the HTML window is resized. Depending
// on the page layout the the canvas might also have been resized, so we // on the page layout the the canvas might also have been resized, so we
// update the Qt screen size (and canvas render size). // update the Qt screen size (and canvas render size).
QWasmScreen *wasmScreen = QWasmIntegration::get()->screen(); eventTranslator->screen()->updateQScreenAndCanvasRenderSize();
wasmScreen->updateQScreenAndCanvasRenderSize(wasmScreen->m_canvasId.toLocal8Bit().constData());
} }
return 0; return 0;

View File

@ -48,7 +48,7 @@ class QWasmEventTranslator : public QObject
public: public:
explicit QWasmEventTranslator(QObject *parent = 0); explicit QWasmEventTranslator(QWasmScreen *screen);
static int keyboard_cb(int eventType, const EmscriptenKeyboardEvent *keyEvent, void *userData); static int keyboard_cb(int eventType, const EmscriptenKeyboardEvent *keyEvent, void *userData);
static int mouse_cb(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); static int mouse_cb(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData);
@ -60,12 +60,12 @@ public:
static int uiEvent_cb(int eventType, const EmscriptenUiEvent *e, void *userData); static int uiEvent_cb(int eventType, const EmscriptenUiEvent *e, void *userData);
void processEvents(); void processEvents();
void initEventHandlers(const QString &); void initEventHandlers();
Q_SIGNALS: Q_SIGNALS:
void getWindowAt(const QPoint &point, QWindow **window); void getWindowAt(const QPoint &point, QWindow **window);
private: private:
QWasmScreen *screen();
Qt::Key translateEmscriptKey(const EmscriptenKeyboardEvent *emscriptKey); Qt::Key translateEmscriptKey(const EmscriptenKeyboardEvent *emscriptKey);
template <typename Event> template <typename Event>
QFlags<Qt::KeyboardModifier> translatKeyModifier(const Event *event); QFlags<Qt::KeyboardModifier> translatKeyModifier(const Event *event);

View File

@ -70,36 +70,35 @@ QWasmIntegration *QWasmIntegration::s_instance;
QWasmIntegration::QWasmIntegration() QWasmIntegration::QWasmIntegration()
: m_fontDb(nullptr), : m_fontDb(nullptr),
m_compositor(new QWasmCompositor),
m_screen(new QWasmScreen(m_compositor)),
m_eventDispatcher(nullptr), m_eventDispatcher(nullptr),
m_clipboard(new QWasmClipboard) m_clipboard(new QWasmClipboard)
{ {
globalHtml5Integration = this;
s_instance = this; s_instance = this;
emscripten::val defaultCanvasId = emscripten::val::global("canvas"); // We expect that qtloader.js has populated Module.qtCanvasElements with one or more canvases.
canvasIds.append(QString::fromStdString(defaultCanvasId["id"].as<std::string>())); // Also check Module.canvas, which may be set if the emscripen or a custom loader is used.
m_screen->setCanvas(canvasIds.at(0)); emscripten::val qtCanvaseElements = val::module_property("qtCanvasElements");
emscripten::val canvas = val::module_property("canvas");
globalHtml5Integration = this; if (!qtCanvaseElements.isUndefined()) {
int screenCount = qtCanvaseElements["length"].as<int>();
screen()->updateQScreenAndCanvasRenderSize(m_screen->m_canvasId); for (int i = 0; i < screenCount; ++i) {
screenAdded(m_screen); emscripten::val canvas = qtCanvaseElements[i].as<emscripten::val>();
QString canvasId = QString::fromStdString(canvas["id"].as<std::string>());
m_eventTranslator = new QWasmEventTranslator; addScreen(canvasId);
}
} else if (!canvas.isUndefined()){
QString canvasId = QString::fromStdString(canvas["id"].as<std::string>());
addScreen(canvasId);
}
emscripten::val::global("window").set("onbeforeunload", val::module_property("browserBeforeUnload")); emscripten::val::global("window").set("onbeforeunload", val::module_property("browserBeforeUnload"));
} }
QWasmIntegration::~QWasmIntegration() QWasmIntegration::~QWasmIntegration()
{ {
delete m_compositor;
destroyScreen(m_screen);
delete m_fontDb; delete m_fontDb;
delete m_eventTranslator; qDeleteAll(m_screens);
s_instance = nullptr; s_instance = nullptr;
} }
@ -124,13 +123,15 @@ bool QWasmIntegration::hasCapability(QPlatformIntegration::Capability cap) const
QPlatformWindow *QWasmIntegration::createPlatformWindow(QWindow *window) const QPlatformWindow *QWasmIntegration::createPlatformWindow(QWindow *window) const
{ {
return new QWasmWindow(window, m_compositor, m_backingStores.value(window)); QWasmCompositor *compositor = QWasmScreen::get(window->screen())->compositor();
return new QWasmWindow(window, compositor, m_backingStores.value(window));
} }
QPlatformBackingStore *QWasmIntegration::createPlatformBackingStore(QWindow *window) const QPlatformBackingStore *QWasmIntegration::createPlatformBackingStore(QWindow *window) const
{ {
#ifndef QT_NO_OPENGL #ifndef QT_NO_OPENGL
QWasmBackingStore *backingStore = new QWasmBackingStore(m_compositor, window); QWasmCompositor *compositor = QWasmScreen::get(window->screen())->compositor();
QWasmBackingStore *backingStore = new QWasmBackingStore(compositor, window);
m_backingStores.insert(window, backingStore); m_backingStores.insert(window, backingStore);
return backingStore; return backingStore;
#else #else
@ -177,9 +178,20 @@ QPlatformTheme *QWasmIntegration::createPlatformTheme(const QString &name) const
QPlatformClipboard* QWasmIntegration::clipboard() const QPlatformClipboard* QWasmIntegration::clipboard() const
{ {
if (!m_clipboard)
m_clipboard = new QWasmClipboard;
return m_clipboard; return m_clipboard;
} }
QVector<QWasmScreen *> QWasmIntegration::screens()
{
return m_screens;
}
void QWasmIntegration::addScreen(const QString &canvasId)
{
QWasmScreen *screen = new QWasmScreen(canvasId);
m_clipboard->installEventHandlers(canvasId);
m_screens.append(screen);
screenAdded(screen);
}
QT_END_NAMESPACE QT_END_NAMESPACE

View File

@ -71,23 +71,20 @@ public:
QPlatformTheme *createPlatformTheme(const QString &name) const override; QPlatformTheme *createPlatformTheme(const QString &name) const override;
QPlatformClipboard *clipboard() const override; QPlatformClipboard *clipboard() const override;
QWasmScreen *screen() { return m_screen; } QVector<QWasmScreen *>screens();
QWasmCompositor *compositor() { return m_compositor; }
QWasmEventTranslator *eventTranslator() { return m_eventTranslator; }
QWasmClipboard *getWasmClipboard() { return m_clipboard; } QWasmClipboard *getWasmClipboard() { return m_clipboard; }
static QWasmIntegration *get() { return s_instance; } static QWasmIntegration *get() { return s_instance; }
static void QWasmBrowserExit(); static void QWasmBrowserExit();
QStringList canvasIds;
private: private:
void addScreen(const QString &canvasId);
mutable QWasmFontDatabase *m_fontDb; mutable QWasmFontDatabase *m_fontDb;
QWasmCompositor *m_compositor;
mutable QWasmScreen *m_screen;
mutable QWasmEventTranslator *m_eventTranslator;
mutable QWasmEventDispatcher *m_eventDispatcher; mutable QWasmEventDispatcher *m_eventDispatcher;
mutable QHash<QWindow *, QWasmBackingStore *> m_backingStores; mutable QHash<QWindow *, QWasmBackingStore *> m_backingStores;
QVector<QWasmScreen *> m_screens;
mutable QWasmClipboard *m_clipboard; mutable QWasmClipboard *m_clipboard;
static QWasmIntegration *s_instance; static QWasmIntegration *s_instance;
}; };

View File

@ -57,9 +57,7 @@ void QWasmOpenGLContext::maybeRecreateEmscriptenContext(QPlatformSurface *surfac
emscripten_webgl_destroy_context(m_context); emscripten_webgl_destroy_context(m_context);
// Create new context // Create new context
QWasmScreen *wasmScreen = QWasmIntegration::get()->screen(); const QString canvasId = QWasmScreen::get(surface->screen())->canvasId();
const QString canvasId = wasmScreen->m_canvasId;
// FIXME: get the actual canvas from the surface.
m_context = createEmscriptenContext(canvasId, m_requestedFormat); m_context = createEmscriptenContext(canvasId, m_requestedFormat);
// Register context-lost callback. // Register context-lost callback.

View File

@ -29,8 +29,10 @@
#include "qwasmscreen.h" #include "qwasmscreen.h"
#include "qwasmwindow.h" #include "qwasmwindow.h"
#include "qwasmeventtranslator.h"
#include "qwasmcompositor.h" #include "qwasmcompositor.h"
#include <emscripten/bind.h> #include <emscripten/bind.h>
#include <emscripten/val.h>
#include <QtEglSupport/private/qeglconvenience_p.h> #include <QtEglSupport/private/qeglconvenience_p.h>
#ifndef QT_NO_OPENGL #ifndef QT_NO_OPENGL
@ -44,12 +46,13 @@
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
QWasmScreen::QWasmScreen(QWasmCompositor *compositor) QWasmScreen::QWasmScreen(const QString &canvasId)
: m_compositor(compositor) : m_canvasId(canvasId)
, m_depth(32)
, m_format(QImage::Format_RGB32)
{ {
m_compositor->setScreen(this); m_compositor = new QWasmCompositor(this);
m_eventTranslator = new QWasmEventTranslator(this);
updateQScreenAndCanvasRenderSize();
} }
QWasmScreen::~QWasmScreen() QWasmScreen::~QWasmScreen()
@ -57,6 +60,31 @@ QWasmScreen::~QWasmScreen()
} }
QWasmScreen *QWasmScreen::get(QPlatformScreen *screen)
{
return static_cast<QWasmScreen *>(screen);
}
QWasmScreen *QWasmScreen::get(QScreen *screen)
{
return get(screen->handle());
}
QWasmCompositor *QWasmScreen::compositor()
{
return m_compositor;
}
QWasmEventTranslator *QWasmScreen::eventTranslator()
{
return m_eventTranslator;
}
QString QWasmScreen::canvasId() const
{
return m_canvasId;
}
QRect QWasmScreen::geometry() const QRect QWasmScreen::geometry() const
{ {
return m_geometry; return m_geometry;
@ -82,6 +110,11 @@ qreal QWasmScreen::devicePixelRatio() const
return qreal(htmlWindowDpr); return qreal(htmlWindowDpr);
} }
QString QWasmScreen::name() const
{
return m_canvasId;
}
QPlatformCursor *QWasmScreen::cursor() const QPlatformCursor *QWasmScreen::cursor() const
{ {
return const_cast<QWasmCursor *>(&m_cursor); return const_cast<QWasmCursor *>(&m_cursor);
@ -114,14 +147,7 @@ void QWasmScreen::setGeometry(const QRect &rect)
resizeMaximizedWindows(); resizeMaximizedWindows();
} }
static void set_canvas_size(double width, double height, const char *canvasId) void QWasmScreen::updateQScreenAndCanvasRenderSize()
{
emscripten::val canvas = emscripten::val::global(canvasId);
canvas.set("width", width);
canvas.set("height", height);
}
void QWasmScreen::updateQScreenAndCanvasRenderSize(const QString &canvasId)
{ {
// The HTML canvas has two sizes: the CSS size and the canvas render size. // The HTML canvas has two sizes: the CSS size and the canvas render size.
// The CSS size is determined according to standard CSS rules, while the // The CSS size is determined according to standard CSS rules, while the
@ -130,17 +156,22 @@ void QWasmScreen::updateQScreenAndCanvasRenderSize(const QString &canvasId)
// Setting the render size to a value larger than the CSS size enables high-dpi // Setting the render size to a value larger than the CSS size enables high-dpi
// rendering. // rendering.
QByteArray canvasId = m_canvasId.toUtf8();
double css_width; double css_width;
double css_height; double css_height;
emscripten_get_element_css_size(canvasId.toLocal8Bit().constData(), &css_width, &css_height); emscripten_get_element_css_size(canvasId.constData(), &css_width, &css_height);
QSizeF cssSize(css_width, css_height); QSizeF cssSize(css_width, css_height);
QWasmScreen *screen = QWasmIntegration::get()->screen(); QSizeF canvasSize = cssSize * devicePixelRatio();
QSizeF canvasSize = cssSize * screen->devicePixelRatio(); emscripten::val canvas = emscripten::val::global(canvasId.constData());
canvas.set("width", canvasSize.width());
canvas.set("height", canvasSize.height());
set_canvas_size(canvasSize.width(), canvasSize.height(), canvasId.toLocal8Bit().constData()); emscripten::val rect = canvas.call<emscripten::val>("getBoundingClientRect");
screen->setGeometry(QRect(QPoint(0, 0), cssSize.toSize())); QPoint position(rect["left"].as<int>(), rect["top"].as<int>());
QWasmIntegration::get()->compositor()->redrawWindowContent();
setGeometry(QRect(position, cssSize.toSize()));
m_compositor->redrawWindowContent();
} }
QT_END_NAMESPACE QT_END_NAMESPACE

View File

@ -43,20 +43,28 @@ class QPlatformOpenGLContext;
class QWasmWindow; class QWasmWindow;
class QWasmBackingStore; class QWasmBackingStore;
class QWasmCompositor; class QWasmCompositor;
class QWasmEventTranslator;
class QOpenGLContext; class QOpenGLContext;
class QWasmScreen : public QObject, public QPlatformScreen class QWasmScreen : public QObject, public QPlatformScreen
{ {
Q_OBJECT Q_OBJECT
public: public:
QWasmScreen(const QString &canvasId);
QWasmScreen(QWasmCompositor *compositor);
~QWasmScreen(); ~QWasmScreen();
static QWasmScreen *get(QPlatformScreen *screen);
static QWasmScreen *get(QScreen *screen);
QString canvasId() const;
QWasmCompositor *compositor();
QWasmEventTranslator *eventTranslator();
QRect geometry() const override; QRect geometry() const override;
int depth() const override; int depth() const override;
QImage::Format format() const override; QImage::Format format() const override;
qreal devicePixelRatio() const override; qreal devicePixelRatio() const override;
QString name() const override;
QPlatformCursor *cursor() const override; QPlatformCursor *cursor() const override;
void resizeMaximizedWindows(); void resizeMaximizedWindows();
@ -64,19 +72,18 @@ public:
QWindow *topLevelAt(const QPoint &p) const override; QWindow *topLevelAt(const QPoint &p) const override;
void invalidateSize(); void invalidateSize();
static void updateQScreenAndCanvasRenderSize(const QString &); void updateQScreenAndCanvasRenderSize();
QString m_canvasId;
void setCanvas(const QString &canvasId) { m_canvasId = canvasId; }
public slots: public slots:
void setGeometry(const QRect &rect); void setGeometry(const QRect &rect);
private: private:
QWasmCompositor *m_compositor; QString m_canvasId;
QWasmCompositor *m_compositor = nullptr;
QWasmEventTranslator *m_eventTranslator = nullptr;
QRect m_geometry = QRect(0, 0, 100, 100); QRect m_geometry = QRect(0, 0, 100, 100);
int m_depth; int m_depth = 32;
QImage::Format m_format; QImage::Format m_format = QImage::Format_RGB32;
QWasmCursor m_cursor; QWasmCursor m_cursor;
}; };

View File

@ -15,23 +15,24 @@
</style> </style>
</head> </head>
<body onload="init()"> <body onload="init()">
<figure style="overflow:visible;" id="spinner"> <figure style="overflow:visible;" id="qtspinner">
<center style="margin-top:1.5em; line-height:150%"> <center style="margin-top:1.5em; line-height:150%">
<img src="qtlogo.svg"; width=320; height=200; style="display:block"> </img> <img src="qtlogo.svg"; width=320; height=200; style="display:block"> </img>
<strong>Qt for WebAssembly: APPNAME</strong> <strong>Qt for WebAssembly: APPNAME</strong>
<div id="status"></div> <div id="qtstatus"></div>
<noscript>JavaScript is disabled. Please enable JavaScript to use this application.</noscript> <noscript>JavaScript is disabled. Please enable JavaScript to use this application.</noscript>
</center> </center>
</figure> </figure>
<canvas id="canvas" oncontextmenu="event.preventDefault()" contenteditable="true"></canvas> <canvas id="qtcanvas" oncontextmenu="event.preventDefault()" contenteditable="true"></canvas>
<script type='text/javascript'> <script type='text/javascript'>
function init() { function init() {
var spinner = document.getElementById('spinner'); var spinner = document.getElementById('qtspinner');
var canvas = document.getElementById('canvas'); var canvas = document.getElementById('qtcanvas');
var status = document.getElementById('status') var status = document.getElementById('qtstatus')
var qtLoader = QtLoader({ var qtLoader = QtLoader({
canvasElements : [canvas],
showLoader: function(loaderStatus) { showLoader: function(loaderStatus) {
spinner.style.display = 'block'; spinner.style.display = 'block';
canvas.style.display = 'none'; canvas.style.display = 'none';
@ -54,7 +55,6 @@
showCanvas: function() { showCanvas: function() {
spinner.style.display = 'none'; spinner.style.display = 'none';
canvas.style.display = 'block'; canvas.style.display = 'block';
return canvas;
}, },
}); });
qtLoader.loadEmscriptenModule("APPNAME"); qtLoader.loadEmscriptenModule("APPNAME");