From 29c0377f07f4942f9957ea87d59c252148dc9e5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Morten=20Johan=20S=C3=B8rvig?= Date: Fri, 1 Jun 2018 15:13:30 +0200 Subject: WebAssembly for QtBase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is the squashed diff from wip/webassembly to dev. Done-with: Peng Wu Done-with: Sami Enne Done-with: Morten Johan Sørvig Started-by: Andrew Knight Change-Id: I6562433c0a38d6ec49ab675e0f104f2665f3392d Reviewed-by: Lorn Potter Reviewed-by: Eskil Abrahamsen Blomfeldt --- src/plugins/platforms/platforms.pro | 2 + src/plugins/platforms/wasm/main.cpp | 54 ++ src/plugins/platforms/wasm/qtloader.js | 516 +++++++++++++++ src/plugins/platforms/wasm/qtlogo.svg | 67 ++ src/plugins/platforms/wasm/qwasmbackingstore.cpp | 165 +++++ src/plugins/platforms/wasm/qwasmbackingstore.h | 70 ++ src/plugins/platforms/wasm/qwasmcompositor.cpp | 721 +++++++++++++++++++++ src/plugins/platforms/wasm/qwasmcompositor.h | 169 +++++ src/plugins/platforms/wasm/qwasmcursor.cpp | 131 ++++ src/plugins/platforms/wasm/qwasmcursor.h | 43 ++ .../platforms/wasm/qwasmeventdispatcher.cpp | 181 ++++++ src/plugins/platforms/wasm/qwasmeventdispatcher.h | 64 ++ .../platforms/wasm/qwasmeventtranslator.cpp | 522 +++++++++++++++ src/plugins/platforms/wasm/qwasmeventtranslator.h | 210 ++++++ src/plugins/platforms/wasm/qwasmfontdatabase.cpp | 86 +++ src/plugins/platforms/wasm/qwasmfontdatabase.h | 49 ++ src/plugins/platforms/wasm/qwasmintegration.cpp | 219 +++++++ src/plugins/platforms/wasm/qwasmintegration.h | 92 +++ src/plugins/platforms/wasm/qwasmopenglcontext.cpp | 147 +++++ src/plugins/platforms/wasm/qwasmopenglcontext.h | 63 ++ src/plugins/platforms/wasm/qwasmscreen.cpp | 118 ++++ src/plugins/platforms/wasm/qwasmscreen.h | 82 +++ src/plugins/platforms/wasm/qwasmstylepixmaps_p.h | 183 ++++++ src/plugins/platforms/wasm/qwasmtheme.cpp | 50 ++ src/plugins/platforms/wasm/qwasmtheme.h | 56 ++ src/plugins/platforms/wasm/qwasmwindow.cpp | 398 ++++++++++++ src/plugins/platforms/wasm/qwasmwindow.h | 124 ++++ src/plugins/platforms/wasm/wasm.json | 3 + src/plugins/platforms/wasm/wasm.pro | 65 ++ src/plugins/platforms/wasm/wasm_shell.html | 61 ++ 30 files changed, 4711 insertions(+) create mode 100644 src/plugins/platforms/wasm/main.cpp create mode 100644 src/plugins/platforms/wasm/qtloader.js create mode 100644 src/plugins/platforms/wasm/qtlogo.svg create mode 100644 src/plugins/platforms/wasm/qwasmbackingstore.cpp create mode 100644 src/plugins/platforms/wasm/qwasmbackingstore.h create mode 100644 src/plugins/platforms/wasm/qwasmcompositor.cpp create mode 100644 src/plugins/platforms/wasm/qwasmcompositor.h create mode 100644 src/plugins/platforms/wasm/qwasmcursor.cpp create mode 100644 src/plugins/platforms/wasm/qwasmcursor.h create mode 100644 src/plugins/platforms/wasm/qwasmeventdispatcher.cpp create mode 100644 src/plugins/platforms/wasm/qwasmeventdispatcher.h create mode 100644 src/plugins/platforms/wasm/qwasmeventtranslator.cpp create mode 100644 src/plugins/platforms/wasm/qwasmeventtranslator.h create mode 100644 src/plugins/platforms/wasm/qwasmfontdatabase.cpp create mode 100644 src/plugins/platforms/wasm/qwasmfontdatabase.h create mode 100644 src/plugins/platforms/wasm/qwasmintegration.cpp create mode 100644 src/plugins/platforms/wasm/qwasmintegration.h create mode 100644 src/plugins/platforms/wasm/qwasmopenglcontext.cpp create mode 100644 src/plugins/platforms/wasm/qwasmopenglcontext.h create mode 100644 src/plugins/platforms/wasm/qwasmscreen.cpp create mode 100644 src/plugins/platforms/wasm/qwasmscreen.h create mode 100644 src/plugins/platforms/wasm/qwasmstylepixmaps_p.h create mode 100644 src/plugins/platforms/wasm/qwasmtheme.cpp create mode 100644 src/plugins/platforms/wasm/qwasmtheme.h create mode 100644 src/plugins/platforms/wasm/qwasmwindow.cpp create mode 100644 src/plugins/platforms/wasm/qwasmwindow.h create mode 100644 src/plugins/platforms/wasm/wasm.json create mode 100644 src/plugins/platforms/wasm/wasm.pro create mode 100644 src/plugins/platforms/wasm/wasm_shell.html (limited to 'src/plugins/platforms') diff --git a/src/plugins/platforms/platforms.pro b/src/plugins/platforms/platforms.pro index e61887618f..5bf2b77421 100644 --- a/src/plugins/platforms/platforms.pro +++ b/src/plugins/platforms/platforms.pro @@ -46,6 +46,8 @@ haiku { SUBDIRS += haiku } +wasm: SUBDIRS = wasm + qtConfig(mirclient): SUBDIRS += mirclient qtConfig(integrityfb): SUBDIRS += integrity diff --git a/src/plugins/platforms/wasm/main.cpp b/src/plugins/platforms/wasm/main.cpp new file mode 100644 index 0000000000..a4d23b4e0d --- /dev/null +++ b/src/plugins/platforms/wasm/main.cpp @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include "qwasmintegration.h" + +QT_BEGIN_NAMESPACE + +class QWasmIntegrationPlugin : public QPlatformIntegrationPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QPlatformIntegrationFactoryInterface_iid FILE "wasm.json") +public: + QPlatformIntegration *create(const QString &, const QStringList &) override; +}; + +QPlatformIntegration *QWasmIntegrationPlugin::create(const QString& system, const QStringList& paramList) +{ + Q_UNUSED(paramList); + if (!system.compare(QStringLiteral("wasm"), Qt::CaseInsensitive)) + return new QWasmIntegration; + + return nullptr; +} + +QT_END_NAMESPACE + +#include "main.moc" diff --git a/src/plugins/platforms/wasm/qtloader.js b/src/plugins/platforms/wasm/qtloader.js new file mode 100644 index 0000000000..37a5308034 --- /dev/null +++ b/src/plugins/platforms/wasm/qtloader.js @@ -0,0 +1,516 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// QtLoader provides javascript API for managing Qt application modules. +// +// QtLoader provides API on top of Emscripten which supports common lifecycle +// tasks such as displaying placeholder content while the module downloads, +// handing application exits, and checking for browser wasm support. +// +// There are two usage modes: +// * Managed: QtLoader owns and manages the HTML display elements like +// the loader and canvas. +// * External: The embedding HTML page owns the display elements. QtLoader +// provides event callbacks which the page reacts to. +// +// Managed mode usage: +// +// var config = { +// containerElements : [$("container-id")]; +// } +// var qtLoader = QtLoader(config); +// qtLoader.loadEmscriptenModule("applicationName"); +// +// External mode.usage: +// +// var config = { +// showLoader: function() { +// loader.style.display = 'block' +// canvas.style.display = 'hidden' +// }, +// showCanvas: function() { +// loader.style.display = 'hidden' +// canvas.style.display = 'block' +// return canvas; +// } +// } +// var qtLoader = QtLoader(config); +// qtLoader.loadEmscriptenModule("applicationName"); +// +// Config keys +// +// containerElements : [container-element, ...] +// One or more HTML elements. QtLoader will display loader elements +// on these while loading the applicaton, and replace the loader with a +// canvas on load complete. +// showLoader : function(status, containerElement) +// Optional loading element constructor function. Implement to create +// a custom loading screen. This function may be called multiple times, +// while preparing the application binary. "status" is a string +// containing the loading sub-status, and may be either "Downloading", +// or "Compiling". The browser may be using streaming compilation, in +// which case the wasm module is compiled during downloading and the +// there is no separate compile step. +// showCanvas : function(containerElement) +// Optional canvas constructor function. Implement to create custom +// canvas elements. +// showExit : function(crashed, exitCode, containerElement) +// Optional exited element constructor function. +// showError : function(crashed, exitCode, containerElement) +// Optional error element constructor function. +// +// path : +// Prefix path for wasm file, realative to the loading HMTL file. +// restartMode : "DoNotRestart", "RestartOnExit", "RestartOnCrash" +// Controls whether the application should be reloaded on exits. The default is "DoNotRestart" +// restartType : "RestartModule", "ReloadPage" +// restartLimit : +// Restart attempts limit. The default is 10. +// stdoutEnabled : +// stderrEnabled : +// environment : +// key-value environment variable pairs. +// +// QtLoader object API +// +// webAssemblySupported : bool +// webGLSupported : bool +// canLoadQt : bool +// Reports if WebAssembly and WebGL are supported. These are requirements for +// running Qt applications. +// loadEmscriptenModule(applicationName) +// Loads the application from the given emscripten javascript module file and wasm file +// status +// One of "Created", "Loading", "Running", "Exited". +// crashed +// Set to true if there was an unclean exit. +// exitCode +// main()/emscripten_force_exit() return code. Valid on status change to +// "Exited", iff crashed is false. +// exitText +// Abort/exit message. + + +var Module = {} + +function QtLoader(config) +{ + function webAssemblySupported() { + return typeof WebAssembly !== "undefined" + } + + function webGLSupported() { + // We expect that WebGL is supported if WebAssembly is; however + // the GPU may be blacklisted. + try { + var canvas = document.createElement("canvas"); + return !!(window.WebGLRenderingContext && (canvas.getContext("webgl") || canvas.getContext("experimental-webgl"))); + } catch (e) { + return false; + } + } + + function canLoadQt() { + // The current Qt implementation requires WebAssembly (asm.js is not in use), + // and also WebGL (there is no raster fallback). + return webAssemblySupported() && webGLSupported(); + } + + function removeChildren(element) { + while (element.firstChild) element.removeChild(element.firstChild); + } + + // Set default state handler functions if needed + if (config.containerElements !== undefined) { + config.showError = config.showError || function(errorText, container) { + removeChildren(container); + var errorTextElement = document.createElement("text"); + errorTextElement.className = "QtError" + errorTextElement.innerHTML = errorText; + return errorTextElement; + } + + config.showLoader = config.showLoader || function(loadingState, container) { + removeChildren(container); + var loadingText = document.createElement("text"); + loadingText.className = "QtLoading" + loadingText.innerHTML = '

${loadingState}...

'; + return loadingText; + }; + + config.showCanvas = config.showCanvas || function(container) { + removeChildren(container); + var canvas = document.createElement("canvas"); + canvas.className = "QtCanvas" + canvas.style = "height: 100%; width: 100%;" + return canvas; + } + + config.showExit = config.showExit || function(crashed, exitCode, container) { + if (!crashed) + return undefined; + + removeChildren(container); + var fontSize = 54; + var crashSymbols = ["\u{1F615}", "\u{1F614}", "\u{1F644}", "\u{1F928}", "\u{1F62C}", + "\u{1F915}", "\u{2639}", "\u{1F62E}", "\u{1F61E}", "\u{1F633}"]; + var symbolIndex = Math.floor(Math.random() * crashSymbols.length); + var errorHtml = ` ${crashSymbols[symbolIndex]} ` + var errorElement = document.createElement("text"); + errorElement.className = "QtExit" + errorElement.innerHTML = errorHtml; + return errorElement; + } + } + + config.restartMode = config.restartMode || "DoNotRestart"; + config.restartLimit = config.restartLimit || 10; + + if (config.stdoutEnabled === undefined) config.stdoutEnabled = true; + if (config.stderrEnabled === undefined) config.stderrEnabled = true; + + // Make sure config.path is defined and ends with "/" if needed + if (config.path === undefined) + config.path = ""; + if (config.path.length > 0 && !config.path.endsWith("/")) + config.path = config.path.concat("/"); + + if (config.environment === undefined) + config.environment = {}; + + var publicAPI = {}; + publicAPI.webAssemblySupported = webAssemblySupported(); + publicAPI.webGLSupported = webGLSupported(); + publicAPI.canLoadQt = canLoadQt(); + publicAPI.canLoadApplication = canLoadQt(); + publicAPI.status = undefined; + publicAPI.loadEmscriptenModule = loadEmscriptenModule; + + restartCount = 0; + + function fetchResource(filePath) { + var fullPath = config.path + filePath; + return fetch(fullPath).then(function(response) { + if (!response.ok) { + self.error = response.status + " " + response.statusText + " " + response.url; + setStatus("Error"); + return Promise.reject(self.error) + } else { + return response; + } + }); + } + + function fetchText(filePath) { + return fetchResource(filePath).then(function(response) { + return response.text(); + }); + } + + function fetchThenCompileWasm(response) { + return response.arrayBuffer().then(function(data) { + self.loaderSubState = "Compiling"; + setStatus("Loading") // trigger loaderSubState udpate + return WebAssembly.compile(data); + }); + } + + function fetchCompileWasm(filePath) { + return fetchResource(filePath).then(function(response) { + if (typeof WebAssembly.compileStreaming !== "undefined") { + self.loaderSubState = "Downloading/Compiling"; + setStatus("Loading"); + return WebAssembly.compileStreaming(response).catch(function(error) { + // compileStreaming may/will fail if the server does not set the correct + // mime type (application/wasm) for the wasm file. Fall back to fetch, + // then compile in this case. + return fetchThenCompileWasm(response); + }); + } else { + // Fall back to fetch, then compile if compileStreaming is not supported + return fetchThenCompileWasm(response); + } + }); + } + + function loadEmscriptenModule(applicationName) { + + // Loading in qtloader.js goes through four steps: + // 1) Check prerequisites + // 2) Download resources + // 3) Configure the emscripten Module object + // 4) Start the emcripten runtime, after which emscripten takes over + + // Check for Wasm & WebGL support; set error and return before downloading resources if missing + if (!webAssemblySupported()) { + self.error = "Error: WebAssembly is not supported" + setStatus("Error"); + return; + } + if (!webGLSupported()) { + self.error = "Error: WebGL is not supported" + setStatus("Error"); + return; + } + + // Continue waiting if loadEmscriptenModule() is called again + if (publicAPI.status == "Loading") + return; + self.loaderSubState = "Downloading"; + setStatus("Loading"); + + // Fetch emscripten generated javascript runtime + var emscriptenModuleSource = undefined + var emscriptenModuleSourcePromise = fetchText(applicationName + ".js").then(function(source) { + emscriptenModuleSource = source + }); + + // Fetch and compile wasm module + var wasmModule = undefined; + var wasmModulePromise = fetchCompileWasm(applicationName + ".wasm").then(function (module) { + wasmModule = module; + }); + + // Wait for all resources ready + Promise.all([emscriptenModuleSourcePromise, wasmModulePromise]).then(function(){ + completeLoadEmscriptenModule(applicationName, emscriptenModuleSource, wasmModule); + }).catch(function(error) { + self.error = error; + setStatus("Error"); + }); + } + + function completeLoadEmscriptenModule(applicationName, emscriptenModuleSource, wasmModule) { + + // The wasm binary has been compiled into a module during resource download, + // and is ready to be instantiated. Define the instantiateWasm callback which + // emscripten will call to create the instance. + Module.instantiateWasm = function(imports, successCallback) { + return WebAssembly.instantiate(wasmModule, imports).then(function(instance) { + successCallback(instance); + return instance; + }, function(error) { + self.error = error; + setStatus("Error"); + }); + }; + + Module.locateFile = Module.locateFile || function(filename) { + return config.path + filename; + }; + + // Attach status callbacks + Module.setStatus = Module.setStatus || function(text) { + // Currently the only usable status update from this function + // is "Running..." + if (text.startsWith("Running")) + setStatus("Running"); + }; + Module.monitorRunDependencies = Module.monitorRunDependencies || function(left) { + // console.log("monitorRunDependencies " + left) + }; + + // Attach standard out/err callbacks. + Module.print = Module.print || function(text) { + if (config.stdoutEnabled) + console.log(text) + }; + Module.printErr = Module.printErr || function(text) { + // Filter out OpenGL getProcAddress warnings. Qt tries to resolve + // all possible function/extension names at startup which causes + // emscripten to spam the console log with warnings. + if (text.startsWith !== undefined && text.startsWith("bad name in getProcAddress:")) + return; + + if (config.stderrEnabled) + console.log(text) + }; + + // Error handling: set status to "Exited", update crashed and + // exitCode according to exit type. + // Emscripten will typically call printErr with the error text + // as well. Note that emscripten may also throw exceptions from + // async callbacks. These should be handled in window.onerror by user code. + Module.onAbort = Module.onAbort || function(text) { + publicAPI.crashed = true; + publicAPI.exitText = text; + setStatus("Exited"); + }; + Module.quit = Module.quit || function(code, exception) { + if (exception.name == "ExitStatus") { + // Clean exit with code + publicAPI.exitText = undefined + publicAPI.exitCode = code; + } else { + publicAPI.exitText = exception.toString(); + publicAPI.crashed = true; + } + setStatus("Exited"); + }; + + // Set environment variables + Module.preRun = Module.preRun || [] + Module.preRun.push(function() { + for (var [key, value] of Object.entries(config.environment)) { + Module.ENV[key.toUpperCase()] = value; + } + }); + + config.restart = function() { + + // Restart by reloading the page. This will wipe all state which means + // reload loops can't be prevented. + if (config.restartType == "ReloadPage") { + location.reload(); + } + + // Restart by readling the emscripten app module. + ++self.restartCount; + if (self.restartCount > config.restartLimit) { + self.error = "Error: This application has crashed too many times and has been disabled. Reload the page to try again." + setStatus("Error"); + return; + } + loadEmscriptenModule(applicationName); + }; + + publicAPI.exitCode = undefined; + publicAPI.exitText = undefined; + publicAPI.crashed = false; + + // Finally evaluate the emscripten application script, which will + // reference the global Module object created above. + self.eval(emscriptenModuleSource); // ES5 indirect global scope eval + } + + function setErrorContent() { + if (config.containerElements === undefined) { + if (config.showError !== undefined) + config.showError(self.error); + return; + } + + for (container of config.containerElements) { + var errorElement = config.showError(self.error, container); + container.appendChild(errorElement); + } + } + + function setLoaderContent() { + if (config.containerElements === undefined) { + if (config.showLoader !== undefined) + config.showLoader(self.loaderSubState); + return; + } + + for (container of config.containerElements) { + var loaderElement = config.showLoader(self.loaderSubState, container); + container.appendChild(loaderElement); + } + } + + function setCanvasContent() { + var firstCanvas; + if (config.containerElements === undefined) { + firstCanvas = config.showCanvas(); + } else { + for (container of config.containerElements) { + var canvasElement = config.showCanvas(container); + container.appendChild(canvasElement); + } + firstCanvas = config.containerElements[0].firstChild; + } + + if (Module.canvas === undefined) { + Module.canvas = firstCanvas; + } + } + + function setExitContent() { + + // publicAPI.crashed = true; + + if (publicAPI.status != "Exited") + return; + + if (config.containerElements === undefined) { + if (config.showExit !== undefined) + config.showExit(publicAPI.crashed, publicAPI.exitCode); + return; + } + + if (!publicAPI.crashed) + return; + + for (container of config.containerElements) { + var loaderElement = config.showExit(publicAPI.crashed, publicAPI.exitCode, container); + if (loaderElement !== undefined) + container.appendChild(loaderElement); + } + } + + var committedStatus = undefined; + function handleStatusChange() { + if (publicAPI.status != "Loading" && committedStatus == publicAPI.status) + return; + committedStatus = publicAPI.status; + + if (publicAPI.status == "Error") { + setErrorContent(); + } else if (publicAPI.status == "Loading") { + setLoaderContent(); + } else if (publicAPI.status == "Running") { + setCanvasContent(); + } else if (publicAPI.status == "Exited") { + if (config.restartMode == "RestartOnExit" || + config.restartMode == "RestartOnCrash" && publicAPI.crashed) { + committedStatus = undefined; + config.restart(); + } else { + setExitContent(); + } + } + + // Send status change notification + if (config.statusChanged) + config.statusChanged(publicAPI.status); + } + + function setStatus(status) { + if (status != "Loading" && publicAPI.status == status) + return; + publicAPI.status = status; + + window.setTimeout(function() { handleStatusChange(); }, 0); + } + + setStatus("Created"); + + return publicAPI; +} diff --git a/src/plugins/platforms/wasm/qtlogo.svg b/src/plugins/platforms/wasm/qtlogo.svg new file mode 100644 index 0000000000..cb8989bb79 --- /dev/null +++ b/src/plugins/platforms/wasm/qtlogo.svg @@ -0,0 +1,67 @@ + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/src/plugins/platforms/wasm/qwasmbackingstore.cpp b/src/plugins/platforms/wasm/qwasmbackingstore.cpp new file mode 100644 index 0000000000..5b40c44807 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmbackingstore.cpp @@ -0,0 +1,165 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwasmbackingstore.h" +#include "qwasmwindow.h" +#include "qwasmcompositor.h" + +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +QWasmBackingStore::QWasmBackingStore(QWasmCompositor *compositor, QWindow *window) + : QPlatformBackingStore(window) + , m_compositor(compositor) + , m_texture(new QOpenGLTexture(QOpenGLTexture::Target2D)) +{ + QWasmWindow *wasmWindow = static_cast(window->handle()); + if (wasmWindow) + wasmWindow->setBackingStore(this); +} + +QWasmBackingStore::~QWasmBackingStore() +{ +} + +QPaintDevice *QWasmBackingStore::paintDevice() +{ + return &m_image; +} + +void QWasmBackingStore::flush(QWindow *window, const QRegion ®ion, const QPoint &offset) +{ + Q_UNUSED(window); + Q_UNUSED(region); + Q_UNUSED(offset); + + m_dirty |= region; + m_compositor->requestRedraw(); +} + +void QWasmBackingStore::updateTexture() +{ + if (m_dirty.isNull()) + return; + + 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; + 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); + + // intersect with image rect to be sure + QRect r = imageRect & deviceRect; + // if the rect is wide enough it is cheaper to just extend it instead of doing an image copy + if (r.width() >= imageRect.width() / 2) { + r.setX(0); + r.setWidth(imageRect.width()); + } + + fixed |= 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())); + } else { + glTexSubImage2D(GL_TEXTURE_2D, 0, rect.x(), rect.y(), rect.width(), rect.height(), GL_RGBA, GL_UNSIGNED_BYTE, + m_image.copy(rect).constBits()); + } + } + /* End of code taken from QEGLPlatformBackingStore */ + + m_dirty = QRegion(); +} + +void QWasmBackingStore::beginPaint(const QRegion ®ion) +{ + m_dirty |= region; + // Keep backing store device pixel ratio in sync with window + if (m_image.devicePixelRatio() != window()->devicePixelRatio()) + resize(backingStore()->size(), backingStore()->staticContents()); + + QPainter painter(&m_image); + painter.setCompositionMode(QPainter::CompositionMode_Source); + const QColor blank = Qt::transparent; + for (const QRect &rect : region) + painter.fillRect(rect, blank); +} + +void QWasmBackingStore::resize(const QSize &size, const QRegion &staticContents) +{ + Q_UNUSED(staticContents) + + m_image = QImage(size * window()->devicePixelRatio(), QImage::Format_RGB32); + m_image.setDevicePixelRatio(window()->devicePixelRatio()); + + if (m_texture->isCreated()) + m_texture->destroy(); +} + +QImage QWasmBackingStore::toImage() const +{ + // used by QPlatformBackingStore::composeAndFlush + return m_image; +} + +const QImage &QWasmBackingStore::getImageRef() const +{ + return m_image; +} + +const QOpenGLTexture *QWasmBackingStore::getUpdatedTexture() +{ + updateTexture(); + return m_texture.data(); +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmbackingstore.h b/src/plugins/platforms/wasm/qwasmbackingstore.h new file mode 100644 index 0000000000..6ffa262e3d --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmbackingstore.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWASMBACKINGSTORE_H +#define QWASMBACKINGSTORE_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QOpenGLTexture; +class QRegion; +class QWasmCompositor; + +class QWasmBackingStore : public QPlatformBackingStore +{ +public: + QWasmBackingStore(QWasmCompositor *compositor, QWindow *window); + ~QWasmBackingStore(); + + QPaintDevice *paintDevice() override; + + void beginPaint(const QRegion &) override; + void flush(QWindow *window, const QRegion ®ion, const QPoint &offset) override; + void resize(const QSize &size, const QRegion &staticContents) override; + QImage toImage() const override; + const QImage &getImageRef() const; + + const QOpenGLTexture *getUpdatedTexture(); + +protected: + void updateTexture(); + +private: + QWasmCompositor *m_compositor; + QImage m_image; + QScopedPointer m_texture; + QRegion m_dirty; +}; + +QT_END_NAMESPACE + +#endif // QWASMBACKINGSTORE_H diff --git a/src/plugins/platforms/wasm/qwasmcompositor.cpp b/src/plugins/platforms/wasm/qwasmcompositor.cpp new file mode 100644 index 0000000000..f3ea013325 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmcompositor.cpp @@ -0,0 +1,721 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwasmcompositor.h" +#include "qwasmwindow.h" +#include "qwasmstylepixmaps_p.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +Q_GUI_EXPORT int qt_defaultDpiX(); + +QWasmCompositedWindow::QWasmCompositedWindow() + : window(nullptr) + , parentWindow(nullptr) + , flushPending(false) + , visible(false) +{ +} + +QWasmCompositor::QWasmCompositor() + : m_frameBuffer(nullptr) + , m_blitter(new QOpenGLTextureBlitter) + , m_needComposit(false) + , m_inFlush(false) + , m_inResize(false) + , m_isEnabled(true) + , m_targetDevicePixelRatio(1) +{ +} + +QWasmCompositor::~QWasmCompositor() +{ + delete m_frameBuffer; +} + +void QWasmCompositor::setEnabled(bool enabled) +{ + m_isEnabled = enabled; +} + +void QWasmCompositor::addWindow(QWasmWindow *window, QWasmWindow *parentWindow) +{ + QWasmCompositedWindow compositedWindow; + compositedWindow.window = window; + compositedWindow.parentWindow = parentWindow; + m_compositedWindows.insert(window, compositedWindow); + + if (parentWindow == 0) + m_windowStack.append(window); + else + m_compositedWindows[parentWindow].childWindows.append(window); + + notifyTopWindowChanged(window); +} + +void QWasmCompositor::removeWindow(QWasmWindow *window) +{ + QWasmWindow *platformWindow = m_compositedWindows[window].parentWindow; + + if (platformWindow) { + QWasmWindow *parentWindow = window; + m_compositedWindows[parentWindow].childWindows.removeAll(window); + } + + m_windowStack.removeAll(window); + m_compositedWindows.remove(window); + + notifyTopWindowChanged(window); +} + +void QWasmCompositor::setScreen(QWasmScreen *screen) +{ + m_screen = screen; +} + +void QWasmCompositor::setVisible(QWasmWindow *window, bool visible) +{ + QWasmCompositedWindow &compositedWindow = m_compositedWindows[window]; + if (compositedWindow.visible == visible) + return; + + compositedWindow.visible = visible; + compositedWindow.flushPending = true; + if (visible) + compositedWindow.damage = compositedWindow.window->geometry(); + else + m_globalDamage = compositedWindow.window->geometry(); // repaint previosly covered area. + + requestRedraw(); +} + +void QWasmCompositor::raise(QWasmWindow *window) +{ + if (m_compositedWindows.size() <= 1) + return; + + QWasmCompositedWindow &compositedWindow = m_compositedWindows[window]; + compositedWindow.damage = compositedWindow.window->geometry(); + m_windowStack.removeAll(window); + m_windowStack.append(window); + + notifyTopWindowChanged(window); +} + +void QWasmCompositor::lower(QWasmWindow *window) +{ + if (m_compositedWindows.size() <= 1) + return; + + m_windowStack.removeAll(window); + m_windowStack.prepend(window); + QWasmCompositedWindow &compositedWindow = m_compositedWindows[window]; + m_globalDamage = compositedWindow.window->geometry(); // repaint previosly covered area. + + notifyTopWindowChanged(window); +} + +void QWasmCompositor::setParent(QWasmWindow *window, QWasmWindow *parent) +{ + m_compositedWindows[window].parentWindow = parent; + + requestRedraw(); +} + +void QWasmCompositor::flush(QWasmWindow *window, const QRegion ®ion) +{ + QWasmCompositedWindow &compositedWindow = m_compositedWindows[window]; + compositedWindow.flushPending = true; + compositedWindow.damage = region; + + requestRedraw(); +} + +int QWasmCompositor::windowCount() const +{ + return m_windowStack.count(); +} + + +void QWasmCompositor::redrawWindowContent() +{ + // Redraw window content by sending expose events. This redraw + // will cause a backing store flush, which will call requestRedraw() + // to composit. + for (QWasmWindow *platformWindow : m_windowStack) { + QWindow *window = platformWindow->window(); + QWindowSystemInterface::handleExposeEvent( + window, QRect(QPoint(0, 0), window->geometry().size())); + } +} + +void QWasmCompositor::requestRedraw() +{ + if (m_needComposit) + return; + + m_needComposit = true; + QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest)); +} + +QWindow *QWasmCompositor::windowAt(QPoint p, int padding) const +{ + int index = m_windowStack.count() - 1; + // qDebug() << "window at" << "point" << p << "window count" << index; + + while (index >= 0) { + const QWasmCompositedWindow &compositedWindow = m_compositedWindows[m_windowStack.at(index)]; + //qDebug() << "windwAt testing" << compositedWindow.window << + + QRect geometry = compositedWindow.window->windowFrameGeometry() + .adjusted(-padding, -padding, padding, padding); + + if (compositedWindow.visible && geometry.contains(p)) + return m_windowStack.at(index)->window(); + --index; + } + + return 0; +} + +QWindow *QWasmCompositor::keyWindow() const +{ + return m_windowStack.at(m_windowStack.count() - 1)->window(); +} + +bool QWasmCompositor::event(QEvent *ev) +{ + if (ev->type() == QEvent::UpdateRequest) { + if (m_isEnabled) + frame(); + return true; + } + + return QObject::event(ev); +} + +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, QWasmWindow *window) +{ + QWasmBackingStore *backingStore = window->backingStore(); + + QOpenGLTexture const *texture = backingStore->getUpdatedTexture(); + + blit(blitter, screen, texture, window->geometry()); +} + +QPalette QWasmCompositor::makeWindowPalette() +{ + 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)); + + return palette; +} + +QRect QWasmCompositor::titlebarRect(QWasmTitleBarOptions tb, QWasmCompositor::SubControls subcontrol) +{ + QRect ret; + const int controlMargin = 2; + const int controlHeight = tb.rect.height() - controlMargin *2; + const int delta = controlHeight + controlMargin; + int offset = 0; + + bool isMinimized = tb.state & Qt::WindowMinimized; + bool isMaximized = tb.state & Qt::WindowMaximized; + + ret = tb.rect; + switch (subcontrol) { + case SC_TitleBarLabel: + if (tb.flags & Qt::WindowSystemMenuHint) + ret.adjust(delta, 0, -delta, 0); + break; + case SC_TitleBarCloseButton: + if (tb.flags & Qt::WindowSystemMenuHint) { + ret.adjust(0, 0, -delta, 0); + offset += delta; + } + break; + case SC_TitleBarMaxButton: + if (!isMaximized && tb.flags & Qt::WindowMaximizeButtonHint) { + ret.adjust(0, 0, -delta*2, 0); + offset += (delta +delta); + } + break; + case SC_TitleBarNormalButton: + if (isMinimized && (tb.flags & Qt::WindowMinimizeButtonHint)) + offset += delta; + else if (isMaximized && (tb.flags & Qt::WindowMaximizeButtonHint)) + ret.adjust(0, 0, -delta*2, 0); + offset += (delta +delta); + break; + case SC_TitleBarSysMenu: + if (tb.flags & Qt::WindowSystemMenuHint) { + ret.setRect(tb.rect.left() + controlMargin, tb.rect.top() + controlMargin, + controlHeight, controlHeight); + } + break; + default: + break; + }; + + if (subcontrol != SC_TitleBarLabel && subcontrol != SC_TitleBarSysMenu) { + ret.setRect(tb.rect.right() - offset, tb.rect.top() + controlMargin, + controlHeight, controlHeight); + } + + if (qApp->layoutDirection() == Qt::LeftToRight) + return ret; + + QRect rect = ret; + rect.translate(2 * (tb.rect.right() - ret.right()) + + ret.width() - tb.rect.width(), 0); + + return rect; +} + +int dpiScaled(qreal value) +{ + return value * (qreal(qt_defaultDpiX()) / 96.0); +} + +QWasmCompositor::QWasmTitleBarOptions QWasmCompositor::makeTitleBarOptions(const QWasmWindow *window) +{ + int width = window->windowFrameGeometry().width(); + int border = window->borderWidth(); + + QWasmTitleBarOptions titleBarOptions; + + titleBarOptions.rect = QRect(border, border, width - 2 * border, window->titleHeight()); + titleBarOptions.flags = window->window()->flags(); + titleBarOptions.state = window->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 = QWasmCompositor::makeWindowPalette(); + + if (window->window()->isActive()) + titleBarOptions.palette.setCurrentColorGroup(QPalette::Active); + else + titleBarOptions.palette.setCurrentColorGroup(QPalette::Inactive); + + if (window->activeSubControl() != QWasmCompositor::SC_None) + titleBarOptions.subControls = window->activeSubControl(); + + if (!window->window()->title().isEmpty()) + titleBarOptions.titleBarOptionsString = window->window()->title(); + + return titleBarOptions; +} + +void QWasmCompositor::drawWindowDecorations(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, 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_RGB32); + image.setDevicePixelRatio(dpr); + QPainter painter(&image); + painter.fillRect(QRect(0, 0, width, height), painter.background()); + + QWasmTitleBarOptions titleBarOptions = makeTitleBarOptions(window); + + drawTitlebarWindow(titleBarOptions, &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.setData(image, QOpenGLTexture::DontGenerateMipMaps); + texture.create(); + texture.bind(); + + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, image.width(), image.height(), GL_RGBA, GL_UNSIGNED_BYTE, + image.constScanLine(0)); + + blit(blitter, screen, &texture, QRect(window->windowFrameGeometry().topLeft(), QSize(width, height))); +} + +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 = 0; + + const qreal devicePixelRatio = painter->device()->devicePixelRatioF(); + 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); +} + +//from commonstyle.cpp +static QPixmap cachedPixmapFromXPM(const char * const *xpm) +{ + QPixmap result; + const QString tag = QString::asprintf("xpm:0x%p", static_cast(xpm)); + if (!QPixmapCache::find(tag, &result)) { + result = QPixmap(xpm); + QPixmapCache::insert(tag, result); + } + return result; +} + +void QWasmCompositor::drawItemPixmap(QPainter *painter, const QRect &rect, int alignment, + const QPixmap &pixmap) const +{ + 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; + + QRect aligned = QRect(x, y, w, h); + QRect inter = aligned.intersected(rect); + + painter->drawPixmap(inter.x(), inter.y(), pixmap, inter.x() - aligned.x(), inter.y() - aligned.y(), inter.width() * scale, inter.height() *scale); +} + + +void QWasmCompositor::drawTitlebarWindow(QWasmTitleBarOptions tb, QPainter *painter) +{ + QRect ir; + if (tb.subControls.testFlag(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); + ir = titlebarRect(tb, SC_TitleBarLabel); + 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); + } // SC_TitleBarLabel + + bool down = false; + QPixmap pixmap; + + if (tb.subControls.testFlag(SC_TitleBarCloseButton) + && tb.flags & Qt::WindowSystemMenuHint) { + ir = titlebarRect(tb, SC_TitleBarCloseButton); + down = tb.subControls & SC_TitleBarCloseButton && (tb.state & State_Sunken); + pixmap = cachedPixmapFromXPM(qt_close_xpm).scaled(QSize(10, 10)); + drawItemPixmap(painter, ir, Qt::AlignCenter, pixmap); + } //SC_TitleBarCloseButton + + if (tb.subControls.testFlag(SC_TitleBarMaxButton) + && tb.flags & Qt::WindowMaximizeButtonHint + && !(tb.state & Qt::WindowMaximized)) { + ir = titlebarRect(tb, SC_TitleBarMaxButton); + down = tb.subControls & SC_TitleBarMaxButton && (tb.state & State_Sunken); + pixmap = cachedPixmapFromXPM(qt_maximize_xpm).scaled(QSize(10, 10)); + drawItemPixmap(painter, ir, Qt::AlignCenter, pixmap); + } //SC_TitleBarMaxButton + + bool drawNormalButton = (tb.subControls & SC_TitleBarNormalButton) + && (((tb.flags & Qt::WindowMinimizeButtonHint) + && (tb.flags & Qt::WindowMinimized)) + || ((tb.flags & Qt::WindowMaximizeButtonHint) + && (tb.flags & Qt::WindowMaximized))); + + if (drawNormalButton) { + ir = titlebarRect(tb, SC_TitleBarNormalButton); + down = tb.subControls & SC_TitleBarNormalButton && (tb.state & State_Sunken); + pixmap = cachedPixmapFromXPM(qt_normalizeup_xpm).scaled( QSize(10, 10)); + + drawItemPixmap(painter, ir, Qt::AlignCenter, pixmap); + } // SC_TitleBarNormalButton + + if (tb.subControls & SC_TitleBarSysMenu && tb.flags & Qt::WindowSystemMenuHint) { + ir = titlebarRect(tb, SC_TitleBarSysMenu); + pixmap = cachedPixmapFromXPM(qt_menu_xpm).scaled(QSize(10, 10)); + drawItemPixmap(painter, ir, Qt::AlignCenter, pixmap); + } +} + +void QWasmCompositor::drawShadePanel(QWasmTitleBarOptions options, QPainter *painter) +{ + int lineWidth = 1; + QPalette palette = options.palette; + const QBrush *fill = &options.palette.brush(QPalette::Button); + + int x = options.rect.x(); + int y = options.rect.y(); + int w = options.rect.width(); + int h = options.rect.height(); + + const qreal devicePixelRatio = painter->device()->devicePixelRatioF(); + 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); + lineWidth = qRound(devicePixelRatio * lineWidth); + } + + QColor shade = palette.dark().color(); + QColor light = palette.light().color(); + + if (fill) { + if (fill->color() == shade) + shade = palette.shadow().color(); + if (fill->color() == light) + light = palette.midlight().color(); + } + QPen oldPen = painter->pen(); + QVector lines; + lines.reserve(2*lineWidth); + + painter->setPen(light); + int x1, y1, x2, y2; + int i; + x1 = x; + y1 = y2 = y; + x2 = x + w - 2; + for (i = 0; i < lineWidth; i++) // top shadow + lines << QLineF(x1, y1++, x2--, y2++); + + x2 = x1; + y1 = y + h - 2; + for (i = 0; i < lineWidth; i++) // left shado + lines << QLineF(x1++, y1, x2++, y2--); + + painter->drawLines(lines); + lines.clear(); + painter->setPen(shade); + x1 = x; + y1 = y2 = y+h-1; + x2 = x+w-1; + for (i=0; idrawLines(lines); + if (fill) // fill with fill color + painter->fillRect(x+lineWidth, y+lineWidth, w-lineWidth*2, h-lineWidth*2, *fill); + painter->setPen(oldPen); // restore pen + +} + +void QWasmCompositor::drawWindow(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, QWasmWindow *window) +{ + if (window->window()->type() != Qt::Popup) + drawWindowDecorations(blitter, screen, window); + drawWindowContent(blitter, screen, window); +} + +void QWasmCompositor::frame() +{ + if (!m_needComposit) + return; + + m_needComposit = false; + + if (m_windowStack.empty() || !m_screen) + return; + + QWasmWindow *someWindow = nullptr; + + foreach (QWasmWindow *window, m_windowStack) { + if (window->window()->surfaceClass() == QSurface::Window + && qt_window_private(static_cast(window->window()))->receivedExpose) { + someWindow = window; + break; + } + } + + if (!someWindow) + return; + + if (m_context.isNull()) { + m_context.reset(new QOpenGLContext()); + //mContext->setFormat(mScreen->format()); + m_context->setScreen(m_screen->screen()); + m_context->create(); + } + + m_context->makeCurrent(someWindow->window()); + + if (!m_blitter->isCreated()) + m_blitter->create(); + + qreal dpr = m_screen->devicePixelRatio(); + glViewport(0, 0, m_screen->geometry().width() * dpr, m_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); + + foreach (QWasmWindow *window, m_windowStack) { + QWasmCompositedWindow &compositedWindow = m_compositedWindows[window]; + + if (!compositedWindow.visible) + continue; + + drawWindow(m_blitter.data(), m_screen, window); + } + + m_blitter->release(); + + if (someWindow && someWindow->window()->surfaceType() == QSurface::OpenGLSurface) + m_context->swapBuffers(someWindow->window()); +} + +void QWasmCompositor::notifyTopWindowChanged(QWasmWindow *window) +{ + QWindow *modalWindow; + bool blocked = QGuiApplicationPrivate::instance()->isWindowBlocked(window->window(), &modalWindow); + + if (blocked) { + raise(static_cast(modalWindow->handle())); + return; + } + + requestRedraw(); + QWindowSystemInterface::handleWindowActivated(window->window()); +} diff --git a/src/plugins/platforms/wasm/qwasmcompositor.h b/src/plugins/platforms/wasm/qwasmcompositor.h new file mode 100644 index 0000000000..4e5ed46cec --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmcompositor.h @@ -0,0 +1,169 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWASMCOMPOSITOR_H +#define QWASMCOMPOSITOR_H + +#include +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QWasmWindow; +class QWasmScreen; +class QOpenGLContext; +class QOpenGLTextureBlitter; + +class QWasmCompositedWindow +{ +public: + QWasmCompositedWindow(); + + QWasmWindow *window; + QWasmWindow *parentWindow; + QRegion damage; + bool flushPending; + bool visible; + QList childWindows; +}; + +class QWasmCompositor : public QObject +{ + Q_OBJECT +public: + QWasmCompositor(); + ~QWasmCompositor(); + + enum QWasmSubControl { + SC_None = 0x00000000, + SC_TitleBarSysMenu = 0x00000001, + SC_TitleBarMinButton = 0x00000002, + SC_TitleBarMaxButton = 0x00000004, + SC_TitleBarCloseButton = 0x00000008, + SC_TitleBarNormalButton = 0x00000010, + SC_TitleBarLabel = 0x00000100 + }; + Q_DECLARE_FLAGS(SubControls, QWasmSubControl) + + enum QWasmStateFlag { + State_None = 0x00000000, + State_Enabled = 0x00000001, + State_Raised = 0x00000002, + State_Sunken = 0x00000004 + }; + Q_DECLARE_FLAGS(StateFlags, QWasmStateFlag) + + struct QWasmTitleBarOptions { + QRect rect; + Qt::WindowFlags flags; + int state; + QPalette palette; + QString titleBarOptionsString; + QWasmCompositor::SubControls subControls; + }; + + struct QWasmFrameOptions { + QRect rect; + int lineWidth; + QPalette palette; + }; + + void setEnabled(bool enabled); + + void addWindow(QWasmWindow *window, QWasmWindow *parentWindow = nullptr); + void removeWindow(QWasmWindow *window); + void setScreen(QWasmScreen *screen); + + void setVisible(QWasmWindow *window, bool visible); + void raise(QWasmWindow *window); + void lower(QWasmWindow *window); + void setParent(QWasmWindow *window, QWasmWindow *parent); + + void flush(QWasmWindow *surface, const QRegion ®ion); + + int windowCount() const; + + void redrawWindowContent(); + void requestRedraw(); + + QWindow *windowAt(QPoint p, int padding = 0) const; + QWindow *keyWindow() const; + + bool event(QEvent *event); + + static QWasmTitleBarOptions makeTitleBarOptions(const QWasmWindow *window); + static QRect titlebarRect(QWasmTitleBarOptions tb, QWasmCompositor::SubControls subcontrol); + +private slots: + void frame(); + +private: + void createFrameBuffer(); + void flushCompletedCallback(int32_t); + void notifyTopWindowChanged(QWasmWindow *window); + void drawWindow(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, QWasmWindow *window); + void drawWindowContent(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, QWasmWindow *window); + void blit(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, const QOpenGLTexture *texture, QRect targetGeometry); + + void drawWindowDecorations(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, QWasmWindow *window); + void drwPanelButton(); + + QImage *m_frameBuffer; + QScopedPointer m_context; + QScopedPointer m_blitter; + QWasmScreen *m_screen; + + QHash m_compositedWindows; + QList m_windowStack; + QRegion m_globalDamage; // damage caused by expose, window close, etc. + bool m_needComposit; + bool m_inFlush; + bool m_inResize; + bool m_isEnabled; + QSize m_targetSize; + qreal m_targetDevicePixelRatio; + + static QPalette makeWindowPalette(); + + void drawFrameWindow(QWasmFrameOptions options, QPainter *painter); + void drawTitlebarWindow(QWasmTitleBarOptions options, QPainter *painter); + void drawShadePanel(QWasmTitleBarOptions options, QPainter *painter); + void drawItemPixmap(QPainter *painter, const QRect &rect, + int alignment, const QPixmap &pixmap) const; +}; +Q_DECLARE_OPERATORS_FOR_FLAGS(QWasmCompositor::SubControls) + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/platforms/wasm/qwasmcursor.cpp b/src/plugins/platforms/wasm/qwasmcursor.cpp new file mode 100644 index 0000000000..54804a55b3 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmcursor.cpp @@ -0,0 +1,131 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwasmcursor.h" + +#include + +#include + +void QWasmCursor::changeCursor(QCursor *windowCursor, QWindow *window) +{ + if (windowCursor == nullptr) + 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") + if (windowCursor->shape() == Qt::BitmapCursor || windowCursor->shape() >= Qt::CustomCursor) + qWarning() << "QWasmCursor: bitmap and custom cursors are not supported"; + + QByteArray htmlCursorName = cursorShapeToHtml(windowCursor->shape()); + + if (htmlCursorName.isEmpty()) + htmlCursorName = "auto"; + + // Set cursor on the main canvas + EM_ASM_ARGS({ + if (Module['canvas']) { + Module['canvas'].style['cursor'] = Pointer_stringify($0); + } + }, htmlCursorName.constData()); +} + +QByteArray QWasmCursor::cursorShapeToHtml(Qt::CursorShape shape) +{ + QByteArray cursorName; + + switch (shape) { + case Qt::ArrowCursor: + cursorName = "default"; + break; + case Qt::UpArrowCursor: + break; + case Qt::CrossCursor: + cursorName = "crosshair"; + break; + case Qt::WaitCursor: + cursorName = "wait"; + break; + case Qt::IBeamCursor: + cursorName = "text"; + break; + case Qt::SizeVerCursor: + cursorName = "ns-resize"; + break; + case Qt::SizeHorCursor: + cursorName = "ew-resize"; + break; + case Qt::SizeBDiagCursor: + cursorName = "nesw-resize"; + break; + case Qt::SizeFDiagCursor: + cursorName = "nwse-resize"; + break; + case Qt::SizeAllCursor: + break; // no equivalent? + case Qt::BlankCursor: + cursorName = "none"; + break; + case Qt::SplitVCursor: + cursorName = "row-resize"; + break; + case Qt::SplitHCursor: + cursorName = "col-resize"; + break; + case Qt::PointingHandCursor: + cursorName = "pointer"; + break; + case Qt::ForbiddenCursor: + cursorName = "not-allowed"; + break; + case Qt::WhatsThisCursor: + cursorName = "help"; + break; + case Qt::BusyCursor: + cursorName = "wait"; + break; + case Qt::OpenHandCursor: + break; // no equivalent? + case Qt::ClosedHandCursor: + break; // no equivalent? + case Qt::DragCopyCursor: + break; // no equivalent? + case Qt::DragMoveCursor: + break; // no equivalent? + case Qt::DragLinkCursor: + break; // no equivalent? + default: + break; + } + + return cursorName; +} diff --git a/src/plugins/platforms/wasm/qwasmcursor.h b/src/plugins/platforms/wasm/qwasmcursor.h new file mode 100644 index 0000000000..516e07aa31 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmcursor.h @@ -0,0 +1,43 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWASMCURSOR_H +#define QWASMCURSOR_H + +#include + +class QWasmCursor : public QPlatformCursor +{ +public: + void changeCursor(QCursor *windowCursor, QWindow *window) override; + + QByteArray cursorShapeToHtml(Qt::CursorShape shape); +}; + +#endif diff --git a/src/plugins/platforms/wasm/qwasmeventdispatcher.cpp b/src/plugins/platforms/wasm/qwasmeventdispatcher.cpp new file mode 100644 index 0000000000..41355d72ae --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmeventdispatcher.cpp @@ -0,0 +1,181 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwasmeventdispatcher.h" + +#include + +#include + +class QWasmEventDispatcherPrivate : public QEventDispatcherUNIXPrivate +{ + +}; + +QWasmEventDispatcher *g_htmlEventDispatcher; + +QWasmEventDispatcher::QWasmEventDispatcher(QObject *parent) + : QUnixEventDispatcherQPA(parent) +{ + + g_htmlEventDispatcher = this; +} + +QWasmEventDispatcher::~QWasmEventDispatcher() +{ + g_htmlEventDispatcher = nullptr; +} + +bool QWasmEventDispatcher::registerRequestUpdateCallback(std::function callback) +{ + if (!g_htmlEventDispatcher || !g_htmlEventDispatcher->m_hasMainLoop) + return false; + + g_htmlEventDispatcher->m_requestUpdateCallbacks.append(callback); + emscripten_resume_main_loop(); + return true; +} + +void QWasmEventDispatcher::maintainTimers() +{ + if (!g_htmlEventDispatcher || !g_htmlEventDispatcher->m_hasMainLoop) + return; + + g_htmlEventDispatcher->doMaintainTimers(); +} + +bool QWasmEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags) +{ + // WaitForMoreEvents is not supported (except for in combination with EventLoopExec below), + // and we don't want the unix event dispatcher base class to attempt to wait either. + flags &= ~QEventLoop::WaitForMoreEvents; + + // Handle normal processEvents. + if (!(flags & QEventLoop::EventLoopExec)) + return QUnixEventDispatcherQPA::processEvents(flags); + + // Handle processEvents from QEventLoop::exec(): + // + // At this point the application has created its root objects on + // the stack and has called app.exec() which has called into this + // function via QEventLoop. + // + // The application now expects that exec() will not return until + // app exit time. However, the browser expects that we return + // control to it periodically, also after initial setup in main(). + + // EventLoopExec for nested event loops is not supported. + Q_ASSERT(!m_hasMainLoop); + m_hasMainLoop = true; + + // Call emscripten_set_main_loop_arg() with a callback which processes + // events. Also set simulateInfiniteLoop to true which makes emscripten + // return control to the browser without unwinding the C++ stack. + auto callback = [](void *eventDispatcher) { + QWasmEventDispatcher *that = static_cast(eventDispatcher); + + // Save and clear updateRequest callbacks so we can register new ones + auto requestUpdateCallbacksCopy = that->m_requestUpdateCallbacks; + that->m_requestUpdateCallbacks.clear(); + + // Repaint all windows + for (auto callback : qAsConst(requestUpdateCallbacksCopy)) + callback(); + + // Pause main loop if no updates were requested. Updates will be + // restarted again by registerRequestUpdateCallback(). + if (that->m_requestUpdateCallbacks.isEmpty()) + emscripten_pause_main_loop(); + + that->doMaintainTimers(); + }; + int fps = 0; // update using requestAnimationFrame + int simulateInfiniteLoop = 1; + emscripten_set_main_loop_arg(callback, this, fps, simulateInfiniteLoop); + + // Note: the above call never returns, not even at app exit + return false; +} + +void QWasmEventDispatcher::doMaintainTimers() +{ + Q_D(QWasmEventDispatcher); + + // This functon schedules native timers in order to wake up to + // process events and activate Qt timers. This is done using the + // emscripten_async_call() API which schedules a new timer. + // There is unfortunately no way to cancel or update a current + // native timer. + + // Schedule a zero-timer to continue processing any pending events. + if (!m_hasZeroTimer && hasPendingEvents()) { + auto callback = [](void *eventDispatcher) { + QWasmEventDispatcher *that = static_cast(eventDispatcher); + that->m_hasZeroTimer = false; + that->QUnixEventDispatcherQPA::processEvents(QEventLoop::AllEvents); + + // Processing events may have posted new events or created new timers + that->doMaintainTimers(); + }; + + emscripten_async_call(callback, this, 0); + m_hasZeroTimer = true; + return; + } + + auto timespecToNanosec = [](timespec ts) -> uint64_t { return ts.tv_sec * 1000 + ts.tv_nsec / (1000 * 1000); }; + + // Get current time and time-to-first-Qt-timer. This polls for system + // time, and we use this time as the current time for the duration of this call. + timespec toWait; + bool hasTimers = d->timerList.timerWait(toWait); + if (!hasTimers) + return; // no timer needed + + uint64_t currentTime = timespecToNanosec(d->timerList.currentTime); + uint64_t toWaitDuration = timespecToNanosec(toWait); + + // The currently scheduled timer target is stored in m_currentTargetTime. + // We can re-use it if the new target is equivalent or later. + uint64_t newTargetTime = currentTime + toWaitDuration; + if (newTargetTime >= m_currentTargetTime) + return; // existing timer is good + + // Schedule a native timer with a callback which processes events (and timers) + auto callback = [](void *eventDispatcher) { + QWasmEventDispatcher *that = static_cast(eventDispatcher); + that->m_currentTargetTime = std::numeric_limits::max(); + that->QUnixEventDispatcherQPA::processEvents(QEventLoop::AllEvents); + + // Processing events may have posted new events or created new timers + that->doMaintainTimers(); + }; + emscripten_async_call(callback, this, toWaitDuration); + m_currentTargetTime = newTargetTime; +} diff --git a/src/plugins/platforms/wasm/qwasmeventdispatcher.h b/src/plugins/platforms/wasm/qwasmeventdispatcher.h new file mode 100644 index 0000000000..5300b3de73 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmeventdispatcher.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWASMEVENTDISPATCHER_H +#define QWASMEVENTDISPATCHER_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QWasmEventDispatcherPrivate; + +class QWasmEventDispatcher : public QUnixEventDispatcherQPA +{ + Q_DECLARE_PRIVATE(QWasmEventDispatcher) +public: + explicit QWasmEventDispatcher(QObject *parent = nullptr); + ~QWasmEventDispatcher(); + + static bool registerRequestUpdateCallback(std::function callback); + static void maintainTimers(); + +protected: + bool processEvents(QEventLoop::ProcessEventsFlags flags) override; + void doMaintainTimers(); + +private: + bool m_hasMainLoop = false; + bool m_hasZeroTimer = false; + uint64_t m_currentTargetTime = std::numeric_limits::max(); + QVector> m_requestUpdateCallbacks; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/platforms/wasm/qwasmeventtranslator.cpp b/src/plugins/platforms/wasm/qwasmeventtranslator.cpp new file mode 100644 index 0000000000..6545eda4e3 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmeventtranslator.cpp @@ -0,0 +1,522 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwasmeventtranslator.h" +#include "qwasmeventdispatcher.h" +#include "qwasmcompositor.h" +#include "qwasmintegration.h" + +#include +#include +#include +#include +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +// macOS CTRL <-> META switching. We most likely want to enable +// the existing switching code in QtGui, but for now do it here. +static bool g_usePlatformMacCtrlMetaSwitching = false; + +QWasmEventTranslator::QWasmEventTranslator(QObject *parent) + : QObject(parent) + , draggedWindow(nullptr) + , pressedButtons(Qt::NoButton) + , resizeMode(QWasmWindow::ResizeNone) +{ + emscripten_set_keydown_callback(0, (void *)this, 1, &keyboard_cb); + emscripten_set_keyup_callback(0, (void *)this, 1, &keyboard_cb); + + emscripten_set_mousedown_callback(0, (void *)this, 1, &mouse_cb); + emscripten_set_mouseup_callback(0, (void *)this, 1, &mouse_cb); + emscripten_set_mousemove_callback(0, (void *)this, 1, &mouse_cb); + + emscripten_set_focus_callback(0, (void *)this, 1, &focus_cb); + + emscripten_set_wheel_callback(0, (void *)this, 1, &wheel_cb); + + touchDevice = new QTouchDevice; + touchDevice->setType(QTouchDevice::TouchScreen); + touchDevice->setCapabilities(QTouchDevice::Position | QTouchDevice::Area | QTouchDevice::NormalizedPosition); + QWindowSystemInterface::registerTouchDevice(touchDevice); + + emscripten_set_touchstart_callback("#canvas", (void *)this, 1, &touchCallback); + emscripten_set_touchend_callback("#canvas", (void *)this, 1, &touchCallback); + emscripten_set_touchmove_callback("#canvas", (void *)this, 1, &touchCallback); + emscripten_set_touchcancel_callback("#canvas", (void *)this, 1, &touchCallback); + + // The Platform Detect: expand coverage and move as needed + enum Platform { + GenericPlatform, + MacOSPlatform + }; + Platform platform = + Platform(EM_ASM_INT("if (navigator.platform.includes(\"Mac\")) return 1; return 0;")); + + g_usePlatformMacCtrlMetaSwitching = (platform == MacOSPlatform); +} + +template +QFlags QWasmEventTranslator::translatKeyModifier(const Event *event) +{ + QFlags keyModifier = Qt::NoModifier; + if (event->shiftKey) + keyModifier |= Qt::ShiftModifier; + if (event->ctrlKey) { + if (g_usePlatformMacCtrlMetaSwitching) + keyModifier |= Qt::MetaModifier; + else + keyModifier |= Qt::ControlModifier; + } + if (event->altKey) + keyModifier |= Qt::AltModifier; + if (event->metaKey) { + if (g_usePlatformMacCtrlMetaSwitching) + keyModifier |= Qt::ControlModifier; + else + keyModifier |= Qt::MetaModifier; + } + return keyModifier; +} + +QFlags QWasmEventTranslator::translateKeyboardEventModifier(const EmscriptenKeyboardEvent *keyEvent) +{ + QFlags keyModifier = translatKeyModifier(keyEvent); + if (keyEvent->location == DOM_KEY_LOCATION_NUMPAD) { + keyModifier |= Qt::KeypadModifier; + } + + return keyModifier; +} + +QFlags QWasmEventTranslator::translateMouseEventModifier(const EmscriptenMouseEvent *mouseEvent) +{ + return translatKeyModifier(mouseEvent); +} + +int QWasmEventTranslator::keyboard_cb(int eventType, const EmscriptenKeyboardEvent *keyEvent, void *userData) +{ + Q_UNUSED(userData) + + bool alphanumeric; + Qt::Key qtKey = translateEmscriptKey(keyEvent, &alphanumeric); + + QEvent::Type keyType = QEvent::None; + switch (eventType) { + case EMSCRIPTEN_EVENT_KEYPRESS: + case EMSCRIPTEN_EVENT_KEYDOWN: //down + keyType = QEvent::KeyPress; + break; + case EMSCRIPTEN_EVENT_KEYUP: //up + keyType = QEvent::KeyRelease; + break; + default: + break; + }; + + if (keyType == QEvent::None) + return 0; + + QString keyText = alphanumeric ? QString(keyEvent->key) : QString(); + bool accepted = QWindowSystemInterface::handleKeyEvent( + 0, keyType, qtKey, translateKeyboardEventModifier(keyEvent), keyText); + QWasmEventDispatcher::maintainTimers(); + return accepted ? 1 : 0; +} + +Qt::Key QWasmEventTranslator::translateEmscriptKey(const EmscriptenKeyboardEvent *emscriptKey, bool *outAlphanumeric) +{ + Qt::Key qtKey; + if (outAlphanumeric) + *outAlphanumeric = false; + + switch (emscriptKey->keyCode) { + case KeyMultiply: qtKey = Qt::Key_Asterisk; *outAlphanumeric = true; break; + case KeyAdd: qtKey = Qt::Key_Plus; *outAlphanumeric = true; break; + case KeyMinus: qtKey = Qt::Key_Minus; *outAlphanumeric = true; break; + case KeySubtract: qtKey = Qt::Key_Minus; *outAlphanumeric = true; break; + case KeyDecimal: qtKey = Qt::Key_Period; *outAlphanumeric = true; break; + case KeyDivide: qtKey = Qt::Key_Slash; *outAlphanumeric = true; break; + case KeyNumPad0: qtKey = Qt::Key_0; *outAlphanumeric = true; break; + case KeyNumPad1: qtKey = Qt::Key_1; *outAlphanumeric = true; break; + case KeyNumPad2: qtKey = Qt::Key_2; *outAlphanumeric = true; break; + case KeyNumPad3: qtKey = Qt::Key_3; *outAlphanumeric = true; break; + case KeyNumPad4: qtKey = Qt::Key_4; *outAlphanumeric = true; break; + case KeyNumPad5: qtKey = Qt::Key_5; *outAlphanumeric = true; break; + case KeyNumPad6: qtKey = Qt::Key_6; *outAlphanumeric = true; break; + case KeyNumPad7: qtKey = Qt::Key_7; *outAlphanumeric = true; break; + case KeyNumPad8: qtKey = Qt::Key_8; *outAlphanumeric = true; break; + case KeyNumPad9: qtKey = Qt::Key_9; *outAlphanumeric = true; break; + case KeyComma: qtKey = Qt::Key_Comma; *outAlphanumeric = true; break; + case KeyPeriod: qtKey = Qt::Key_Period; *outAlphanumeric = true; break; + case KeySlash: qtKey = Qt::Key_Slash; *outAlphanumeric = true; break; + case KeySemiColon: qtKey = Qt::Key_Semicolon; *outAlphanumeric = true; break; + case KeyEquals: qtKey = Qt::Key_Equal; *outAlphanumeric = true; break; + case KeyOpenBracket: qtKey = Qt::Key_BracketLeft; *outAlphanumeric = true; break; + case KeyCloseBracket: qtKey = Qt::Key_BracketRight; *outAlphanumeric = true; break; + case KeyBackSlash: qtKey = Qt::Key_Backslash; *outAlphanumeric = true; break; + case KeyMeta: + Q_FALLTHROUGH(); + case KeyMetaRight: + qtKey = Qt::Key_Meta; + break; + case KeyTab: qtKey = Qt::Key_Tab; break; + case KeyClear: qtKey = Qt::Key_Clear; break; + case KeyBackSpace: qtKey = Qt::Key_Backspace; break; + case KeyEnter: qtKey = Qt::Key_Return; break; + case KeyShift: qtKey = Qt::Key_Shift; break; + case KeyControl: qtKey = Qt::Key_Control; break; + case KeyAlt: qtKey = Qt::Key_Alt; break; + case KeyCapsLock: qtKey = Qt::Key_CapsLock; break; + case KeyEscape: qtKey = Qt::Key_Escape; break; + case KeyPageUp: qtKey = Qt::Key_PageUp; break; + case KeyPageDown: qtKey = Qt::Key_PageDown; break; + case KeyEnd: qtKey = Qt::Key_End; break; + case KeyHome: qtKey = Qt::Key_Home; break; + case KeyLeft: qtKey = Qt::Key_Left; break; + case KeyUp: qtKey = Qt::Key_Up; break; + case KeyRight: qtKey = Qt::Key_Right; break; + case KeyDown: qtKey = Qt::Key_Down; break; + case KeyBrightnessDown: qtKey = Qt::Key_MonBrightnessDown; break; + case KeyBrightnessUp: qtKey = Qt::Key_MonBrightnessUp; break; + case KeyMediaTrackPrevious: qtKey = Qt::Key_MediaPrevious; break; + case KeyMediaPlayPause: qtKey = Qt::Key_MediaTogglePlayPause; break; + case KeyMediaTrackNext: qtKey = Qt::Key_MediaNext; break; + case KeyAudioVolumeMute: qtKey = Qt::Key_VolumeMute; break; + case KeyAudioVolumeDown: qtKey = Qt::Key_VolumeDown; break; + case KeyAudioVolumeUp: qtKey = Qt::Key_VolumeUp; break; + case KeyDelete: qtKey = Qt::Key_Delete; break; + + case KeyF1: qtKey = Qt::Key_F1; break; + case KeyF2: qtKey = Qt::Key_F2; break; + case KeyF3: qtKey = Qt::Key_F3; break; + case KeyF4: qtKey = Qt::Key_F4; break; + case KeyF5: qtKey = Qt::Key_F5; break; + case KeyF6: qtKey = Qt::Key_F6; break; + case KeyF7: qtKey = Qt::Key_F7; break; + case KeyF8: qtKey = Qt::Key_F8; break; + case KeyF9: qtKey = Qt::Key_F9; break; + case KeyF10: qtKey = Qt::Key_F10; break; + case KeyF11: qtKey = Qt::Key_F11; break; + case KeyF12: qtKey = Qt::Key_F12; break; + case 124: qtKey = Qt::Key_F13; break; + case 125: qtKey = Qt::Key_F14; break; + + case KeySpace: + default: + if (outAlphanumeric) + *outAlphanumeric = true; + qtKey = static_cast(emscriptKey->keyCode); + break; + } + + // Handle Mac command key. Using event->keyCode as above is + // no reliable since the codes differ between browsers. + if (qstrncmp(emscriptKey->key, "Meta", 4) == 0) { + qtKey = Qt::Key_Meta; + *outAlphanumeric = false; + } + + if (g_usePlatformMacCtrlMetaSwitching) { + if (qtKey == Qt::Key_Meta) + qtKey = Qt::Key_Control; + else if (qtKey == Qt::Key_Control) + qtKey = Qt::Key_Meta; + } + + return qtKey; +} + +Qt::MouseButton QWasmEventTranslator::translateMouseButton(unsigned short button) +{ + if (button == 0) + return Qt::LeftButton; + else if (button == 1) + return Qt::MiddleButton; + else if (button == 2) + return Qt::RightButton; + + return Qt::NoButton; +} + +int QWasmEventTranslator::mouse_cb(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) +{ + QWasmEventTranslator *translator = (QWasmEventTranslator*)userData; + translator->processMouse(eventType,mouseEvent); + QWasmEventDispatcher::maintainTimers(); + return 0; +} + +void resizeWindow(QWindow *window, QWasmWindow::ResizeMode mode, + QRect startRect, QPoint amount) +{ + if (mode == QWasmWindow::ResizeNone) + return; + + bool top = mode == QWasmWindow::ResizeTopLeft || + mode == QWasmWindow::ResizeTop || + mode == QWasmWindow::ResizeTopRight; + + bool bottom = mode == QWasmWindow::ResizeBottomLeft || + mode == QWasmWindow::ResizeBottom || + mode == QWasmWindow::ResizeBottomRight; + + bool left = mode == QWasmWindow::ResizeLeft || + mode == QWasmWindow::ResizeTopLeft || + mode == QWasmWindow::ResizeBottomLeft; + + bool right = mode == QWasmWindow::ResizeRight || + mode == QWasmWindow::ResizeTopRight || + mode == QWasmWindow::ResizeBottomRight; + + int x1 = startRect.left(); + int y1 = startRect.top(); + int x2 = startRect.right(); + int y2 = startRect.bottom(); + + if (left) + x1 += amount.x(); + if (top) + y1 += amount.y(); + if (right) + x2 += amount.x(); + if (bottom) + y2 += amount.y(); + + int w = x2-x1; + int h = y2-y1; + + if (w < window->minimumWidth()) { + if (left) + x1 -= window->minimumWidth() - w; + + w = window->minimumWidth(); + } + + if (h < window->minimumHeight()) { + if (top) + y1 -= window->minimumHeight() - h; + + h = window->minimumHeight(); + } + + window->setGeometry(x1, y1, w, h); +} + +void QWasmEventTranslator::processMouse(int eventType, const EmscriptenMouseEvent *mouseEvent) +{ + auto timestamp = mouseEvent->timestamp; + QPoint point(mouseEvent->canvasX, mouseEvent->canvasY); + + QEvent::Type buttonEventType = QEvent::None; + + Qt::MouseButton button = translateMouseButton(mouseEvent->button); + Qt::KeyboardModifiers modifiers = translateMouseEventModifier(mouseEvent); + + QWindow *window2 = QWasmIntegration::get()->compositor()->windowAt(point, 5); + QWasmWindow *htmlWindow = static_cast(window2->handle()); + bool onFrame = false; + if (window2 && !window2->geometry().contains(point)) + onFrame = true; + + QPoint localPoint(point.x() - window2->geometry().x(), point.y() - window2->geometry().y()); + + switch (eventType) { + case 5: //down + { + if (window2) + window2->raise(); + + pressedButtons.setFlag(button); + + if (mouseEvent->button == 0) { + pressedWindow = window2; + buttonEventType = QEvent::MouseButtonPress; + if (htmlWindow && window2->flags().testFlag(Qt::WindowTitleHint) && htmlWindow->isPointOnTitle(point)) + draggedWindow = window2; + else if (htmlWindow && htmlWindow->isPointOnResizeRegion(point)) { + draggedWindow = window2; + resizeMode = htmlWindow->resizeModeAtPoint(point); + resizePoint = point; + resizeStartRect = window2->geometry(); + } + } + + htmlWindow->injectMousePressed(localPoint, point, button, modifiers); + break; + } + case 6: //up + { + pressedButtons.setFlag(translateMouseButton(mouseEvent->button), false); + buttonEventType = QEvent::MouseButtonRelease; + QWasmWindow *oldWindow = nullptr; + + if (mouseEvent->button == 0 && pressedWindow) { + oldWindow = static_cast(pressedWindow->handle()); + pressedWindow = nullptr; + } + + + if (mouseEvent->button == 0) { + draggedWindow = nullptr; + resizeMode = QWasmWindow::ResizeNone; + } + + if (oldWindow) + oldWindow->injectMouseReleased(localPoint, point, button, modifiers); + break; + } + case 8://move //drag event + { + buttonEventType = QEvent::MouseMove; + if (resizeMode == QWasmWindow::ResizeNone && draggedWindow) { + draggedWindow->setX(draggedWindow->x() + mouseEvent->movementX); + draggedWindow->setY(draggedWindow->y() + mouseEvent->movementY); + } + + if (resizeMode != QWasmWindow::ResizeNone) { + QPoint delta = QPoint(mouseEvent->canvasX, mouseEvent->canvasY) - resizePoint; + resizeWindow(draggedWindow, resizeMode, resizeStartRect, delta); + } + break; + } + default: + break; + }; + + if (window2 && !onFrame) { + QWindowSystemInterface::handleMouseEvent( + window2, timestamp, localPoint, point, pressedButtons, button, buttonEventType, modifiers); + } +} + +int QWasmEventTranslator::focus_cb(int /*eventType*/, const EmscriptenFocusEvent */*focusEvent*/, void */*userData*/) +{ + return 0; +} + +int QWasmEventTranslator::wheel_cb(int eventType, const EmscriptenWheelEvent *wheelEvent, void *userData) +{ + Q_UNUSED(eventType) + Q_UNUSED(userData) + + EmscriptenMouseEvent mouseEvent = wheelEvent->mouse; + + int scrollFactor = 0; + switch (wheelEvent->deltaMode) { + case DOM_DELTA_PIXEL://chrome safari + scrollFactor = 1; + break; + case DOM_DELTA_LINE: //firefox + scrollFactor = 12; + break; + case DOM_DELTA_PAGE: + scrollFactor = 20; + break; + }; + + Qt::KeyboardModifiers modifiers = translateMouseEventModifier(&mouseEvent); + auto timestamp = mouseEvent.timestamp; + QPoint globalPoint(mouseEvent.canvasX, mouseEvent.canvasY); + + QWindow *window2 = QWasmIntegration::get()->compositor()->windowAt(globalPoint, 5); + + QPoint localPoint(globalPoint.x() - window2->geometry().x(), globalPoint.y() - window2->geometry().y()); + + QPoint pixelDelta; + + if (wheelEvent->deltaY != 0) pixelDelta.setY(wheelEvent->deltaY * scrollFactor); + if (wheelEvent->deltaX != 0) pixelDelta.setX(wheelEvent->deltaX * scrollFactor); + + QWindowSystemInterface::handleWheelEvent(window2, timestamp, localPoint, globalPoint, QPoint(), pixelDelta, modifiers); + return 1; +} + +int QWasmEventTranslator::touchCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData) +{ + QList touchPointList; + touchPointList.reserve(touchEvent->numTouches); + QWindow *window2; + + for (int i = 0; i < touchEvent->numTouches; i++) { + + const EmscriptenTouchPoint *touches = &touchEvent->touches[i]; + + QPoint point(touches->canvasX, touches->canvasY); + window2 = QWasmIntegration::get()->compositor()->windowAt(point, 5); + + QWindowSystemInterface::TouchPoint touchPoint; + + auto cX = point.x(); + auto cY = point.y(); + touchPoint.area = QRect(0, 0, 8, 8); + touchPoint.area.moveCenter(QPointF(cX,cY)); // simulate area + + touchPoint.id = touches->identifier; + touchPoint.normalPosition = QPointF(cX / window2->width(), cY / window2->height()); + + switch (eventType) { + case EMSCRIPTEN_EVENT_TOUCHSTART: + touchPoint.state = Qt::TouchPointPressed; + break; + case EMSCRIPTEN_EVENT_TOUCHEND: + touchPoint.state = Qt::TouchPointReleased; + break; + case EMSCRIPTEN_EVENT_TOUCHMOVE: + touchPoint.state = Qt::TouchPointMoved; + break; + default: + Q_UNREACHABLE(); + } + + touchPointList.append(touchPoint); + } + + QWasmEventTranslator *wasmEventTranslator = (QWasmEventTranslator*)userData; + QFlags keyModifier = translatKeyModifier(touchEvent); + + if (eventType != EMSCRIPTEN_EVENT_TOUCHCANCEL) + QWindowSystemInterface::handleTouchEvent(window2, wasmEventTranslator->getTimestamp(), wasmEventTranslator->touchDevice, touchPointList, keyModifier); + else + QWindowSystemInterface::handleTouchCancelEvent(window2, wasmEventTranslator->getTimestamp(), wasmEventTranslator->touchDevice, keyModifier); + + QCoreApplication::processEvents(); + return 1; +} + +quint64 QWasmEventTranslator::getTimestamp() +{ + return QDeadlineTimer::current().deadlineNSecs() / 1000; +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmeventtranslator.h b/src/plugins/platforms/wasm/qwasmeventtranslator.h new file mode 100644 index 0000000000..11430a57a2 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmeventtranslator.h @@ -0,0 +1,210 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWASMEVENTTRANSLATOR_H +#define QWASMEVENTTRANSLATOR_H + +#include +#include +#include +#include +#include "qwasmwindow.h" +#include + +QT_BEGIN_NAMESPACE + +class QWindow; + +class QWasmEventTranslator : public QObject +{ + Q_OBJECT + + enum KeyCode { + // numpad + KeyNumPad0 = 0x60, + KeyNumPad1 = 0x61, + KeyNumPad2 = 0x62, + KeyNumPad3 = 0x63, + KeyNumPad4 = 0x64, + KeyNumPad5 = 0x65, + KeyNumPad6 = 0x66, + KeyNumPad7 = 0x67, + KeyNumPad8 = 0x68, + KeyNumPad9 = 0x69, + KeyMultiply = 0x6A, + KeyAdd = 0x6B, + KeySeparator = 0x6C, + KeySubtract = 0x6D, + KeyDecimal = 0x6E, + KeyDivide = 0x6F, + KeyMeta = 0x5B, + KeyMetaRight = 0x5C, + //////// + KeyClear = 0x90, + KeyEnter = 0xD, + KeyBackSpace = 0x08, + KeyCancel = 0x03, + KeyTab = 0x09, + KeyShift = 0x10, + KeyControl = 0x11, + KeyAlt = 0x12, + KeyPause = 0x13, + KeyCapsLock = 0x14, + KeyEscape = 0x1B, + KeySpace = 0x20, + KeyPageUp = 0x21, + KeyPageDown = 0x22, + KeyEnd = 0x23, + KeyHome = 0x24, + KeyLeft = 0x25, + KeyUp = 0x26, + KeyRight = 0x27, + KeyDown = 0x28, + KeyComma = 0xBC, + KeyPeriod = 0xBE, + KeySlash = 0xBF, + KeyZero = 0x30, + KeyOne = 0x31, + KeyTwo = 0x32, + KeyThree = 0x33, + KeyFour = 0x34, + KeyFive = 0x35, + KeySix = 0x36, + KeySeven = 0x37, + KeyEight = 0x38, + KeyNine = 0x39, + KeyBrightnessDown = 0xD8, + KeyBrightnessUp = 0xD9, + KeyMediaTrackPrevious = 0xB1, + KeyMediaPlayPause = 0xB3, + KeyMediaTrackNext = 0xB0, + KeyAudioVolumeMute = 0xAD, + KeyAudioVolumeDown = 0xAE, + KeyAudioVolumeUp = 0xAF, + KeySemiColon = 0xBA, + KeyEquals = 0xBB, + KeyMinus = 0xBD, + KeyA = 0x41, + KeyB = 0x42, + KeyC = 0x43, + KeyD = 0x44, + KeyE = 0x45, + KeyF = 0x46, + KeyG = 0x47, + KeyH = 0x48, + KeyI = 0x49, + KeyJ = 0x4A, + KeyK = 0x4B, + KeyL = 0x4C, + KeyM = 0x4D, + KeyN = 0x4E, + KeyO = 0x4F, + KeyP = 0x50, + KeyQ = 0x51, + KeyR = 0x52, + KeyS = 0x53, + KeyT = 0x54, + KeyU = 0x55, + KeyV = 0x56, + KeyW = 0x57, + KeyX = 0x58, + KeyY = 0x59, + KeyZ = 0x5A, + KeyOpenBracket = 0xDB, + KeyBackSlash = 0xDC, + KeyCloseBracket = 0xDD, + KeyF1 = 0x70, + KeyF2 = 0x71, + KeyF3 = 0x72, + KeyF4 = 0x73, + KeyF5 = 0x74, + KeyF6 = 0x75, + KeyF7 = 0x76, + KeyF8 = 0x77, + KeyF9 = 0x78, + KeyF10 = 0x79, + KeyF11 = 0x7A, + KeyF12 = 0x7B, + KeyDelete = 0x2E, + KeyNumLock = 0x90, + KeyScrollLock = 0x91, + KeyPrintScreen = 0x9A, + KeyInsert = 0x9B, + KeyHelp = 0x9C, + KeyBackQuote = 0xC0, + KeyQuote = 0xDE, + KeyFinal = 0x18, + KeyConvert = 0x1C, + KeyNonConvert = 0x1D, + KeyAccept = 0x1E, + KeyModeChange = 0x1F, + KeyKana = 0x15, + KeyKanji = 0x19, + KeyUndefined = 0x0 + }; + +public: + + explicit QWasmEventTranslator(QObject *parent = 0); + + static int keyboard_cb(int eventType, const EmscriptenKeyboardEvent *keyEvent, void *userData); + static int mouse_cb(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); + static int focus_cb(int eventType, const EmscriptenFocusEvent *focusEvent, void *userData); + static int wheel_cb(int eventType, const EmscriptenWheelEvent *wheelEvent, void *userData); + + static int touchCallback(int eventType, const EmscriptenTouchEvent *ev, void *userData); + + void processEvents(); + +Q_SIGNALS: + void getWindowAt(const QPoint &point, QWindow **window); +private: + static Qt::Key translateEmscriptKey(const EmscriptenKeyboardEvent *emscriptKey, bool *outAlphanumretic); + template + static QFlags translatKeyModifier(const Event *event); + static QFlags translateKeyboardEventModifier(const EmscriptenKeyboardEvent *keyEvent); + static QFlags translateMouseEventModifier(const EmscriptenMouseEvent *mouseEvent); + static Qt::MouseButton translateMouseButton(unsigned short button); + + void processMouse(int eventType, const EmscriptenMouseEvent *mouseEvent); + +private: + QWindow *draggedWindow; + QWindow *pressedWindow; + Qt::MouseButtons pressedButtons; + + QWasmWindow::ResizeMode resizeMode; + QPoint resizePoint; + QRect resizeStartRect; + QTouchDevice *touchDevice; + quint64 getTimestamp(); +}; + +QT_END_NAMESPACE +#endif // QWASMEVENTTRANSLATOR_H diff --git a/src/plugins/platforms/wasm/qwasmfontdatabase.cpp b/src/plugins/platforms/wasm/qwasmfontdatabase.cpp new file mode 100644 index 0000000000..0c72dfddc4 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmfontdatabase.cpp @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwasmfontdatabase.h" + +#include + +QT_BEGIN_NAMESPACE + +void QWasmFontDatabase::populateFontDatabase() +{ + // Load font file from resources. Currently + // all fonts needs to be bundled with the nexe + // as Qt resources. + QStringList fontFileNames = QStringList() << QStringLiteral(":/fonts/Vera.ttf") + << QStringLiteral(":/fonts/DejaVuSans.ttf"); + + foreach (const QString &fontFileName, fontFileNames) { + QFile theFont(fontFileName); + if (!theFont.open(QIODevice::ReadOnly)) + break; + + QFreeTypeFontDatabase::addTTFile(theFont.readAll(), fontFileName.toLatin1()); + } +} + +QFontEngine *QWasmFontDatabase::fontEngine(const QFontDef &fontDef, void *handle) +{ + return QFreeTypeFontDatabase::fontEngine(fontDef, handle); +} + +QStringList QWasmFontDatabase::fallbacksForFamily(const QString &family, QFont::Style style, + QFont::StyleHint styleHint, + QChar::Script script) const +{ + QStringList fallbacks + = QFreeTypeFontDatabase::fallbacksForFamily(family, style, styleHint, script); + + // Add the vera.ttf font (loaded in populateFontDatabase above) as a falback font + // to all other fonts (except itself). + const QString veraFontFamily = QStringLiteral("Bitstream Vera Sans"); + if (family != veraFontFamily) + fallbacks.append(veraFontFamily); + + return fallbacks; +} + +QStringList QWasmFontDatabase::addApplicationFont(const QByteArray &fontData, + const QString &fileName) +{ + return QFreeTypeFontDatabase::addApplicationFont(fontData, fileName); +} + +void QWasmFontDatabase::releaseHandle(void *handle) +{ + QFreeTypeFontDatabase::releaseHandle(handle); +} + + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmfontdatabase.h b/src/plugins/platforms/wasm/qwasmfontdatabase.h new file mode 100644 index 0000000000..891f12859e --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmfontdatabase.h @@ -0,0 +1,49 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWASMFONTDATABASE_H +#define QWASMFONTDATABASE_H + +#include + +QT_BEGIN_NAMESPACE + +class QWasmFontDatabase : public QFreeTypeFontDatabase +{ +public: + void populateFontDatabase() override; + QFontEngine *fontEngine(const QFontDef &fontDef, void *handle) override; + QStringList fallbacksForFamily(const QString &family, QFont::Style style, + QFont::StyleHint styleHint, + QChar::Script script) const override; + QStringList addApplicationFont(const QByteArray &fontData, const QString &fileName) override; + void releaseHandle(void *handle) override; +}; +QT_END_NAMESPACE +#endif diff --git a/src/plugins/platforms/wasm/qwasmintegration.cpp b/src/plugins/platforms/wasm/qwasmintegration.cpp new file mode 100644 index 0000000000..accc1fd2fe --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmintegration.cpp @@ -0,0 +1,219 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwasmintegration.h" +#include "qwasmeventtranslator.h" +#include "qwasmeventdispatcher.h" +#include "qwasmcompositor.h" +#include "qwasmopenglcontext.h" +#include "qwasmtheme.h" + +#include "qwasmwindow.h" +#ifndef QT_NO_OPENGL +# include "qwasmbackingstore.h" +#endif +#include "qwasmfontdatabase.h" +#if defined(Q_OS_UNIX) +#include +#endif +#include +#include +#include +#include + +#include + +// this is where EGL headers are pulled in, make sure it is last +#include "qwasmscreen.h" + +using namespace emscripten; +QT_BEGIN_NAMESPACE + +void browserBeforeUnload() +{ + QWasmIntegration::QWasmBrowserExit(); +} + +EMSCRIPTEN_BINDINGS(my_module) +{ + function("browserBeforeUnload", &browserBeforeUnload); +} + +static QWasmIntegration *globalHtml5Integration; +QWasmIntegration *QWasmIntegration::get() { return globalHtml5Integration; } + +QWasmIntegration::QWasmIntegration() + : m_fontDb(nullptr), + m_compositor(new QWasmCompositor), + m_screen(new QWasmScreen(m_compositor)), + m_eventDispatcher(nullptr) +{ + + globalHtml5Integration = this; + + updateQScreenAndCanvasRenderSize(); + screenAdded(m_screen); + emscripten_set_resize_callback(0, (void *)this, 1, uiEvent_cb); + + m_eventTranslator = new QWasmEventTranslator; + + EM_ASM(// exit app if browser closes + window.onbeforeunload = function () { + Module.browserBeforeUnload(); + }; + ); +} + +QWasmIntegration::~QWasmIntegration() +{ + delete m_compositor; + destroyScreen(m_screen); + delete m_fontDb; + delete m_eventTranslator; +} + +void QWasmIntegration::QWasmBrowserExit() +{ + QCoreApplication *app = QCoreApplication::instance(); + app->quit(); +} + +bool QWasmIntegration::hasCapability(QPlatformIntegration::Capability cap) const +{ + switch (cap) { + case ThreadedPixmaps: return true; + case OpenGL: return true; + case ThreadedOpenGL: return true; + case RasterGLSurface: return true; + case MultipleWindows: return true; + case WindowManagement: return true; + default: return QPlatformIntegration::hasCapability(cap); + } +} + +QPlatformWindow *QWasmIntegration::createPlatformWindow(QWindow *window) const +{ + return new QWasmWindow(window, m_compositor, m_backingStores.value(window)); +} + +QPlatformBackingStore *QWasmIntegration::createPlatformBackingStore(QWindow *window) const +{ +#ifndef QT_NO_OPENGL + QWasmBackingStore *backingStore = new QWasmBackingStore(m_compositor, window); + m_backingStores.insert(window, backingStore); + return backingStore; +#else + return nullptr; +#endif +} + +#ifndef QT_NO_OPENGL +QPlatformOpenGLContext *QWasmIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const +{ + return new QWasmOpenGLContext(context->format()); +} +#endif + +QPlatformFontDatabase *QWasmIntegration::fontDatabase() const +{ + if (m_fontDb == nullptr) + m_fontDb = new QWasmFontDatabase; + + return m_fontDb; +} + +QAbstractEventDispatcher *QWasmIntegration::createEventDispatcher() const +{ + return new QWasmEventDispatcher; +} + +QVariant QWasmIntegration::styleHint(QPlatformIntegration::StyleHint hint) const +{ + return QPlatformIntegration::styleHint(hint); +} + +QStringList QWasmIntegration::themeNames() const +{ + return QStringList() << QLatin1String("webassembly"); +} + +QPlatformTheme *QWasmIntegration::createPlatformTheme(const QString &name) const +{ + if (name == QLatin1String("webassembly")) + return new QWasmTheme; + return QPlatformIntegration::createPlatformTheme(name); +} + +int QWasmIntegration::uiEvent_cb(int eventType, const EmscriptenUiEvent *e, void *userData) +{ + Q_UNUSED(e) + Q_UNUSED(userData) + + if (eventType == EMSCRIPTEN_EVENT_RESIZE) { + // 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 + // update the Qt screen size (and canvas render size). + updateQScreenAndCanvasRenderSize(); + } + + return 0; +} + +static void set_canvas_size(double width, double height) +{ + EM_ASM_({ + var canvas = Module.canvas; + canvas.width = $0; + canvas.height = $1; + }, width, height); +} + +void QWasmIntegration::updateQScreenAndCanvasRenderSize() +{ + // 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 + // render size is set using the "width" and "height" attributes. The render + // 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. + + double css_width; + double css_height; + emscripten_get_element_css_size(0, &css_width, &css_height); + QSizeF cssSize(css_width, css_height); + + QWasmScreen *screen = QWasmIntegration::get()->m_screen; + QSizeF canvasSize = cssSize * screen->devicePixelRatio(); + + set_canvas_size(canvasSize.width(), canvasSize.height()); + screen->setGeometry(QRect(QPoint(0, 0), cssSize.toSize())); + QWasmIntegration::get()->m_compositor->redrawWindowContent(); +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmintegration.h b/src/plugins/platforms/wasm/qwasmintegration.h new file mode 100644 index 0000000000..ebc3d9d431 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmintegration.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWASMINTEGRATION_H +#define QWASMINTEGRATION_H + +#include "qwasmwindow.h" + +#include +#include + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QWasmEventTranslator; +class QWasmFontDatabase; +class QWasmWindow; +class QWasmEventDispatcher; +class QWasmScreen; +class QWasmCompositor; +class QWasmBackingStore; + +class QWasmIntegration : public QObject, public QPlatformIntegration +{ + Q_OBJECT +public: + QWasmIntegration(); + ~QWasmIntegration(); + + bool hasCapability(QPlatformIntegration::Capability cap) const override; + QPlatformWindow *createPlatformWindow(QWindow *window) const override; + QPlatformBackingStore *createPlatformBackingStore(QWindow *window) const override; +#ifndef QT_NO_OPENGL + QPlatformOpenGLContext *createPlatformOpenGLContext(QOpenGLContext *context) const override; +#endif + QPlatformFontDatabase *fontDatabase() const override; + QAbstractEventDispatcher *createEventDispatcher() const override; + QVariant styleHint(QPlatformIntegration::StyleHint hint) const override; + QStringList themeNames() const override; + QPlatformTheme *createPlatformTheme(const QString &name) const override; + + static QWasmIntegration *get(); + QWasmScreen *screen() { return m_screen; } + QWasmCompositor *compositor() { return m_compositor; } + QWasmEventTranslator *eventTranslator() { return m_eventTranslator; } + + static void QWasmBrowserExit(); + static void updateQScreenAndCanvasRenderSize(); + +private: + mutable QWasmFontDatabase *m_fontDb; + QWasmCompositor *m_compositor; + mutable QWasmScreen *m_screen; + mutable QWasmEventTranslator *m_eventTranslator; + mutable QWasmEventDispatcher *m_eventDispatcher; + static int uiEvent_cb(int eventType, const EmscriptenUiEvent *e, void *userData); + mutable QHash m_backingStores; +}; + +QT_END_NAMESPACE + +#endif // QWASMINTEGRATION_H diff --git a/src/plugins/platforms/wasm/qwasmopenglcontext.cpp b/src/plugins/platforms/wasm/qwasmopenglcontext.cpp new file mode 100644 index 0000000000..73af3d1878 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmopenglcontext.cpp @@ -0,0 +1,147 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwasmopenglcontext.h" + +#include + +QT_BEGIN_NAMESPACE + +QWasmOpenGLContext::QWasmOpenGLContext(const QSurfaceFormat &format) + : m_requestedFormat(format) +{ + m_requestedFormat.setRenderableType(QSurfaceFormat::OpenGLES); +} + +QWasmOpenGLContext::~QWasmOpenGLContext() +{ + if (m_context) + emscripten_webgl_destroy_context(m_context); +} + +void QWasmOpenGLContext::maybeRecreateEmscriptenContext(QPlatformSurface *surface) +{ + // Native emscripten contexts are tied to a single surface. Recreate + // the context if the surface is changed. + if (surface != m_surface) { + m_surface = surface; + + // Destroy existing context + if (m_context) + emscripten_webgl_destroy_context(m_context); + + // Create new context + const char *canvasId = 0; // (use default canvas) FIXME: get the actual canvas from the surface. + m_context = createEmscriptenContext(canvasId, m_requestedFormat); + + // Register context-lost callback. + auto callback = [](int eventType, const void *reserved, void *userData) -> EM_BOOL + { + Q_UNUSED(eventType); + Q_UNUSED(reserved); + // The application may get contex-lost if e.g. moved to the background. Set + // m_contextLost which will make isValid() return false. Application code will + // then detect this and recrate the the context, resulting in a new QWasmOpenGLContext + // instance. + reinterpret_cast(userData)->m_contextLost = true; + return true; + }; + bool capture = true; + emscripten_set_webglcontextlost_callback(canvasId, this, capture, callback); + } +} + +EMSCRIPTEN_WEBGL_CONTEXT_HANDLE QWasmOpenGLContext::createEmscriptenContext(const char *canvasId, QSurfaceFormat format) +{ + EmscriptenWebGLContextAttributes attributes; + emscripten_webgl_init_context_attributes(&attributes); // Populate with default attributes + + attributes.preferLowPowerToHighPerformance = false; + attributes.failIfMajorPerformanceCaveat = false; + attributes.antialias = true; + attributes.enableExtensionsByDefault = true; + + if (format.majorVersion() == 3) { + attributes.majorVersion = 2; + } + + // WebGL offers enable/disable control but not size control for these + attributes.alpha = format.alphaBufferSize() > 0; + attributes.depth = format.depthBufferSize() > 0; + attributes.stencil = format.stencilBufferSize() > 0; + + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context = emscripten_webgl_create_context(canvasId, &attributes); + + return context; +} + +QSurfaceFormat QWasmOpenGLContext::format() const +{ + return m_requestedFormat; +} + +GLuint QWasmOpenGLContext::defaultFramebufferObject(QPlatformSurface *surface) const +{ + return QPlatformOpenGLContext::defaultFramebufferObject(surface); +} + +bool QWasmOpenGLContext::makeCurrent(QPlatformSurface *surface) +{ + maybeRecreateEmscriptenContext(surface); + + return emscripten_webgl_make_context_current(m_context) == EMSCRIPTEN_RESULT_SUCCESS; +} + +void QWasmOpenGLContext::swapBuffers(QPlatformSurface *surface) +{ + Q_UNUSED(surface); + // No swapbuffers on WebGl +} + +void QWasmOpenGLContext::doneCurrent() +{ + // No doneCurrent on WebGl +} + +bool QWasmOpenGLContext::isSharing() const +{ + return false; +} + +bool QWasmOpenGLContext::isValid() const +{ + return (m_contextLost == false); +} + +QFunctionPointer QWasmOpenGLContext::getProcAddress(const char *procName) +{ + return reinterpret_cast(eglGetProcAddress(procName)); +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmopenglcontext.h b/src/plugins/platforms/wasm/qwasmopenglcontext.h new file mode 100644 index 0000000000..9123100479 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmopenglcontext.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QWasmOpenGLContext : public QPlatformOpenGLContext +{ +public: + QWasmOpenGLContext(const QSurfaceFormat &format); + ~QWasmOpenGLContext(); + + QSurfaceFormat format() const override; + void swapBuffers(QPlatformSurface *surface) override; + GLuint defaultFramebufferObject(QPlatformSurface *surface) const override; + bool makeCurrent(QPlatformSurface *surface) override; + void doneCurrent() override; + bool isSharing() const override; + bool isValid() const override; + QFunctionPointer getProcAddress(const char *procName) override; + +private: + void maybeRecreateEmscriptenContext(QPlatformSurface *surface); + static EMSCRIPTEN_WEBGL_CONTEXT_HANDLE createEmscriptenContext(const char *canvasId, QSurfaceFormat format); + + bool m_contextLost = false; + QSurfaceFormat m_requestedFormat; + QPlatformSurface *m_surface = nullptr; + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE m_context = 0; +}; + +QT_END_NAMESPACE + diff --git a/src/plugins/platforms/wasm/qwasmscreen.cpp b/src/plugins/platforms/wasm/qwasmscreen.cpp new file mode 100644 index 0000000000..93e9906ffc --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmscreen.cpp @@ -0,0 +1,118 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwasmscreen.h" +#include "qwasmwindow.h" +#include "qwasmcompositor.h" + +#include +#ifndef QT_NO_OPENGL +# include +#endif +#include +#include +#include +#include + + +QT_BEGIN_NAMESPACE + +QWasmScreen::QWasmScreen(QWasmCompositor *compositor) + : m_compositor(compositor) + , m_depth(32) + , m_format(QImage::Format_RGB32) +{ + m_compositor->setScreen(this); +} + +QWasmScreen::~QWasmScreen() +{ + +} + +QRect QWasmScreen::geometry() const +{ + return m_geometry; +} + +int QWasmScreen::depth() const +{ + return m_depth; +} + +QImage::Format QWasmScreen::format() const +{ + return m_format; +} + +qreal QWasmScreen::devicePixelRatio() const +{ + // FIXME: The effective device pixel ratio may be different from the + // HTML window dpr if the OpenGL driver/GPU allocates a less than + // full resolution surface. Use emscripten_webgl_get_drawing_buffer_size() + // and compute the dpr instead. + double htmlWindowDpr = EM_ASM_DOUBLE({ + return window.devicePixelRatio; + }); + return qreal(htmlWindowDpr); +} + +QPlatformCursor *QWasmScreen::cursor() const +{ + return const_cast(&m_cursor); +} + +void QWasmScreen::resizeMaximizedWindows() +{ + QPlatformScreen::resizeMaximizedWindows(); +} + +QWindow *QWasmScreen::topWindow() const +{ + return m_compositor->keyWindow(); +} + +QWindow *QWasmScreen::topLevelAt(const QPoint &p) const +{ + return m_compositor->windowAt(p); +} + +void QWasmScreen::invalidateSize() +{ + m_geometry = QRect(); +} + +void QWasmScreen::setGeometry(const QRect &rect) +{ + m_geometry = rect; + QWindowSystemInterface::handleScreenGeometryChange(QPlatformScreen::screen(), geometry(), availableGeometry()); + resizeMaximizedWindows(); +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmscreen.h b/src/plugins/platforms/wasm/qwasmscreen.h new file mode 100644 index 0000000000..3891db77bb --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmscreen.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWASMSCREEN_H +#define QWASMSCREEN_H + +#include "qwasmcursor.h" + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QPlatformOpenGLContext; +class QWasmWindow; +class QWasmBackingStore; +class QWasmCompositor; +class QOpenGLContext; + +class QWasmScreen : public QObject, public QPlatformScreen +{ + Q_OBJECT +public: + + QWasmScreen(QWasmCompositor *compositor); + ~QWasmScreen(); + + QRect geometry() const override; + int depth() const override; + QImage::Format format() const override; + qreal devicePixelRatio() const override; + QPlatformCursor *cursor() const override; + + void resizeMaximizedWindows(); + QWindow *topWindow() const; + QWindow *topLevelAt(const QPoint &p) const override; + + void invalidateSize(); + +public slots: + void setGeometry(const QRect &rect); +protected: + +private: + QWasmCompositor *m_compositor; + + QRect m_geometry = QRect(0, 0, 100, 100); + int m_depth; + QImage::Format m_format; + QWasmCursor m_cursor; +}; + +QT_END_NAMESPACE +#endif // QWASMSCREEN_H diff --git a/src/plugins/platforms/wasm/qwasmstylepixmaps_p.h b/src/plugins/platforms/wasm/qwasmstylepixmaps_p.h new file mode 100644 index 0000000000..2b5860f42f --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmstylepixmaps_p.h @@ -0,0 +1,183 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWASMSTYLEPIXMAPS_P_H +#define QWASMSTYLEPIXMAPS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +/* XPM */ +static const char * const qt_menu_xpm[] = { +"16 16 72 1", +" c None", +". c #65AF36", +"+ c #66B036", +"@ c #77B94C", +"# c #A7D28C", +"$ c #BADBA4", +"% c #A4D088", +"& c #72B646", +"* c #9ACB7A", +"= c #7FBD56", +"- c #85C05F", +"; c #F4F9F0", +"> c #FFFFFF", +", c #E5F1DC", +"' c #ECF5E7", +") c #7ABA50", +"! c #83BF5C", +"~ c #AED595", +"{ c #D7EACA", +"] c #A9D28D", +"^ c #BCDDA8", +"/ c #C4E0B1", +"( c #81BE59", +"_ c #D0E7C2", +": c #D4E9C6", +"< c #6FB542", +"[ c #6EB440", +"} c #88C162", +"| c #98CA78", +"1 c #F4F9F1", +"2 c #8FC56C", +"3 c #F1F8EC", +"4 c #E8F3E1", +"5 c #D4E9C7", +"6 c #74B748", +"7 c #80BE59", +"8 c #73B747", +"9 c #6DB43F", +"0 c #CBE4BA", +"a c #80BD58", +"b c #6DB33F", +"c c #FEFFFE", +"d c #68B138", +"e c #F9FCF7", +"f c #91C66F", +"g c #E8F3E0", +"h c #DCEDD0", +"i c #91C66E", +"j c #A3CF86", +"k c #C9E3B8", +"l c #B0D697", +"m c #E3F0DA", +"n c #95C873", +"o c #E6F2DE", +"p c #9ECD80", +"q c #BEDEAA", +"r c #C7E2B6", +"s c #79BA4F", +"t c #6EB441", +"u c #BCDCA7", +"v c #FAFCF8", +"w c #F6FAF3", +"x c #84BF5D", +"y c #EDF6E7", +"z c #FAFDF9", +"A c #88C263", +"B c #98CA77", +"C c #CDE5BE", +"D c #67B037", +"E c #D9EBCD", +"F c #6AB23C", +"G c #77B94D", +" .++++++++++++++", +".+++++++++++++++", +"+++@#$%&+++*=+++", +"++-;>,>')+!>~+++", +"++{>]+^>/(_>:~<+", +"+[>>}+|>123>456+", +"+7>>8+->>90>~+++", +"+a>>b+a>c[0>~+++", +"+de>=+f>g+0>~+++", +"++h>i+j>k+0>~+++", +"++l>mno>p+q>rst+", +"++duv>wl++xy>zA+", +"++++B>Cb++++&D++", +"+++++0zE++++++++", +"++++++FG+++++++.", +"++++++++++++++. "}; + +static const char * const qt_close_xpm[] = { +"10 10 2 1", +"# c #000000", +". c None", +"..........", +".##....##.", +"..##..##..", +"...####...", +"....##....", +"...####...", +"..##..##..", +".##....##.", +"..........", +".........."}; + +static const char * const qt_maximize_xpm[]={ +"10 10 2 1", +"# c #000000", +". c None", +"#########.", +"#########.", +"#.......#.", +"#.......#.", +"#.......#.", +"#.......#.", +"#.......#.", +"#.......#.", +"#########.", +".........."}; + + +static const char * const qt_normalizeup_xpm[] = { +"10 10 2 1", +"# c #000000", +". c None", +"...######.", +"...######.", +"...#....#.", +".######.#.", +".######.#.", +".#....###.", +".#....#...", +".#....#...", +".######...", +".........."}; + + +#endif // QWASMSTYLEPIXMAPS_P_H diff --git a/src/plugins/platforms/wasm/qwasmtheme.cpp b/src/plugins/platforms/wasm/qwasmtheme.cpp new file mode 100644 index 0000000000..a7f2db3bd3 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmtheme.cpp @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwasmtheme.h" +#include + +QT_BEGIN_NAMESPACE + +QWasmTheme::QWasmTheme() +{ +} + +QWasmTheme::~QWasmTheme() +{ +} + +QVariant QWasmTheme::themeHint(ThemeHint hint) const +{ + if (hint == QPlatformTheme::StyleNames) + return QVariant(QStringList() << QLatin1String("fusion")); + return QPlatformTheme::themeHint(hint); +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmtheme.h b/src/plugins/platforms/wasm/qwasmtheme.h new file mode 100644 index 0000000000..e4cc06e049 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmtheme.h @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWASMTHEME_H +#define QWASMTHEME_H + +#include + +QT_BEGIN_NAMESPACE + +class QWasmEventTranslator; +class QWasmFontDatabase; +class QWasmWindow; +class QWasmEventDispatcher; +class QWasmScreen; +class QWasmCompositor; +class QWasmBackingStore; + +class QWasmTheme : public QPlatformTheme +{ +public: + QWasmTheme(); + ~QWasmTheme(); + + QVariant themeHint(ThemeHint hint) const override; +}; + +QT_END_NAMESPACE + +#endif // QWASMTHEME_H diff --git a/src/plugins/platforms/wasm/qwasmwindow.cpp b/src/plugins/platforms/wasm/qwasmwindow.cpp new file mode 100644 index 0000000000..0489813929 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmwindow.cpp @@ -0,0 +1,398 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include + +#include "qwasmwindow.h" +#include "qwasmscreen.h" +#include "qwasmcompositor.h" +#include "qwasmeventdispatcher.h" + +#include + +Q_GUI_EXPORT int qt_defaultDpiX(); + +QT_BEGIN_NAMESPACE + +QWasmWindow::QWasmWindow(QWindow *w, QWasmCompositor *compositor, QWasmBackingStore *backingStore) + : QPlatformWindow(w), + m_window(w), + m_compositor(compositor), + m_backingStore(backingStore) +{ + m_needsCompositor = w->surfaceType() != QSurface::OpenGLSurface; + static int serialNo = 0; + m_winid = ++serialNo; + qWarning("QWasmWindow %p: %p 0x%x\n", this, w, uint(m_winid)); + + m_compositor->addWindow(this); + + // Pure OpenGL windows draw directly using egl, disable the compositor. + m_compositor->setEnabled(w->surfaceType() != QSurface::OpenGLSurface); +} + +QWasmWindow::~QWasmWindow() +{ + m_compositor->removeWindow(this); +} + +void QWasmWindow::initialize() +{ + QRect rect = windowGeometry(); + + QPlatformWindow::setGeometry(rect); + + const QSize minimumSize = windowMinimumSize(); + if (rect.width() > 0 || rect.height() > 0) { + rect.setWidth(qBound(1, rect.width(), 2000)); + rect.setHeight(qBound(1, rect.height(), 2000)); + } else if (minimumSize.width() > 0 || minimumSize.height() > 0) { + rect.setSize(minimumSize); + } + + setWindowState(window()->windowStates()); + setWindowFlags(window()->flags()); + setWindowTitle(window()->title()); + m_hasTitle = window()->flags().testFlag(Qt::WindowTitleHint) && m_needsCompositor; + + if (window()->isTopLevel()) + setWindowIcon(window()->icon()); + m_normalGeometry = rect; +} + +QWasmScreen *QWasmWindow::platformScreen() const +{ + return static_cast(window()->screen()->handle()); +} + +void QWasmWindow::setGeometry(const QRect &rect) +{ + QRect r = rect; + if (m_needsCompositor) { + int yMin = window()->geometry().top() - window()->frameGeometry().top(); + + if (r.y() < yMin) + r.moveTop(yMin); + } + QWindowSystemInterface::handleGeometryChange(window(), r); + QPlatformWindow::setGeometry(r); + + QWindowSystemInterface::flushWindowSystemEvents(); + invalidate(); +} + +void QWasmWindow::setVisible(bool visible) +{ + QRect newGeom; + + if (visible) { + const bool forceFullScreen = !m_needsCompositor;//make gl apps fullscreen for now + + if (forceFullScreen || (m_windowState & Qt::WindowFullScreen)) + newGeom = platformScreen()->geometry(); + else if (m_windowState & Qt::WindowMaximized) + newGeom = platformScreen()->availableGeometry(); + } + QPlatformWindow::setVisible(visible); + + m_compositor->setVisible(this, visible); + + if (!newGeom.isEmpty()) + setGeometry(newGeom); // may or may not generate an expose + + invalidate(); +} + +QMargins QWasmWindow::frameMargins() const +{ + int border = m_hasTitle ? 4. * (qreal(qt_defaultDpiX()) / 96.0) : 0; + int titleBarHeight = m_hasTitle ? titleHeight() : 0; + + QMargins margins; + margins.setLeft(border); + margins.setRight(border); + margins.setTop(2*border + titleBarHeight); + margins.setBottom(border); + + return margins; +} + +void QWasmWindow::raise() +{ + m_compositor->raise(this); + QWindowSystemInterface::handleExposeEvent(window(), QRect(QPoint(0, 0), geometry().size())); + invalidate(); +} + +void QWasmWindow::lower() +{ + m_compositor->lower(this); + QWindowSystemInterface::handleExposeEvent(window(), QRect(QPoint(0, 0), geometry().size())); + invalidate(); +} + +WId QWasmWindow::winId() const +{ + return m_winid; +} + +void QWasmWindow::propagateSizeHints() +{ +// get rid of base class warning +} + +void QWasmWindow::injectMousePressed(const QPoint &local, const QPoint &global, + Qt::MouseButton button, Qt::KeyboardModifiers mods) +{ + Q_UNUSED(local); + Q_UNUSED(mods); + + if (!m_hasTitle || button != Qt::LeftButton) + return; + + if (maxButtonRect().contains(global)) + m_activeControl = QWasmCompositor::SC_TitleBarMaxButton; + else if (minButtonRect().contains(global)) + m_activeControl = QWasmCompositor::SC_TitleBarMinButton; + else if (closeButtonRect().contains(global)) + m_activeControl = QWasmCompositor::SC_TitleBarCloseButton; + else if (normButtonRect().contains(global)) + m_activeControl = QWasmCompositor::SC_TitleBarNormalButton; + + invalidate(); +} + +void QWasmWindow::injectMouseReleased(const QPoint &local, const QPoint &global, + Qt::MouseButton button, Qt::KeyboardModifiers mods) +{ + Q_UNUSED(local); + Q_UNUSED(mods); + + if (!m_hasTitle || button != Qt::LeftButton) + return; + + if (closeButtonRect().contains(global) && m_activeControl == QWasmCompositor::SC_TitleBarCloseButton) + window()->close(); + + if (maxButtonRect().contains(global) && m_activeControl == QWasmCompositor::SC_TitleBarMaxButton) { + window()->setWindowState(Qt::WindowMaximized); + platformScreen()->resizeMaximizedWindows(); + } + + if (normButtonRect().contains(global) && m_activeControl == QWasmCompositor::SC_TitleBarNormalButton) { + window()->setWindowState(Qt::WindowNoState); + setGeometry(normalGeometry()); + } + + m_activeControl = QWasmCompositor::SC_None; + + invalidate(); +} + +int QWasmWindow::titleHeight() const +{ + return 18. * (qreal(qt_defaultDpiX()) / 96.0);//dpiScaled(18.); +} + +int QWasmWindow::borderWidth() const +{ + return 4. * (qreal(qt_defaultDpiX()) / 96.0);// dpiScaled(4.); +} + +QRegion QWasmWindow::titleGeometry() const +{ + int border = borderWidth(); + + QRegion result(window()->frameGeometry().x() + border, + window()->frameGeometry().y() + border, + window()->frameGeometry().width() - 2*border, + titleHeight()); + + result -= titleControlRegion(); + + return result; +} + +QRegion QWasmWindow::resizeRegion() const +{ + int border = borderWidth(); + QRegion result(window()->frameGeometry().adjusted(-border, -border, border, border)); + result -= window()->frameGeometry().adjusted(border, border, -border, -border); + + return result; +} + +bool QWasmWindow::isPointOnTitle(QPoint point) const +{ + bool ok = titleGeometry().contains(point); + return ok; +} + +bool QWasmWindow::isPointOnResizeRegion(QPoint point) const +{ + return resizeRegion().contains(point); +} + +QWasmWindow::ResizeMode QWasmWindow::resizeModeAtPoint(QPoint point) const +{ + QPoint p1 = window()->frameGeometry().topLeft() - QPoint(5, 5); + QPoint p2 = window()->frameGeometry().bottomRight() + QPoint(5, 5); + int corner = 20; + + QRect top(p1, QPoint(p2.x(), p1.y() + corner)); + QRect middle(QPoint(p1.x(), p1.y() + corner), QPoint(p2.x(), p2.y() - corner)); + QRect bottom(QPoint(p1.x(), p2.y() - corner), p2); + + QRect left(p1, QPoint(p1.x() + corner, p2.y())); + QRect center(QPoint(p1.x() + corner, p1.y()), QPoint(p2.x() - corner, p2.y())); + QRect right(QPoint(p2.x() - corner, p1.y()), p2); + + if (top.contains(point)) { + // Top + if (left.contains(point)) + return ResizeTopLeft; + if (center.contains(point)) + return ResizeTop; + if (right.contains(point)) + return ResizeTopRight; + } else if (middle.contains(point)) { + // Middle + if (left.contains(point)) + return ResizeLeft; + if (right.contains(point)) + return ResizeRight; + } else if (bottom.contains(point)) { + // Bottom + if (left.contains(point)) + return ResizeBottomLeft; + if (center.contains(point)) + return ResizeBottom; + if (right.contains(point)) + return ResizeBottomRight; + } + + return ResizeNone; +} + +QRect getSubControlRect(const QWasmWindow *window, QWasmCompositor::SubControls subControl) +{ + QWasmCompositor::QWasmTitleBarOptions options = QWasmCompositor::makeTitleBarOptions(window); + + QRect r = QWasmCompositor::titlebarRect(options, subControl); + r.translate(window->window()->frameGeometry().x(), window->window()->frameGeometry().y()); + + return r; +} + +QRect QWasmWindow::maxButtonRect() const +{ + return getSubControlRect(this, QWasmCompositor::SC_TitleBarMaxButton); +} + +QRect QWasmWindow::minButtonRect() const +{ + return getSubControlRect(this, QWasmCompositor::SC_TitleBarMinButton); +} + +QRect QWasmWindow::closeButtonRect() const +{ + return getSubControlRect(this, QWasmCompositor::SC_TitleBarCloseButton); +} + +QRect QWasmWindow::normButtonRect() const +{ + return getSubControlRect(this, QWasmCompositor::SC_TitleBarNormalButton); +} + +QRect QWasmWindow::sysMenuRect() const +{ + return getSubControlRect(this, QWasmCompositor::SC_TitleBarSysMenu); +} + +QRegion QWasmWindow::titleControlRegion() const +{ + QRegion result; + result += closeButtonRect(); + result += minButtonRect(); + result += maxButtonRect(); + result += sysMenuRect(); + + return result; +} + +void QWasmWindow::invalidate() +{ + m_compositor->requestRedraw(); +} + +QWasmCompositor::SubControls QWasmWindow::activeSubControl() const +{ + return m_activeControl; +} + +void QWasmWindow::setWindowState(Qt::WindowStates states) +{ + m_windowState = Qt::WindowNoState; + if (states & Qt::WindowMinimized) + m_windowState = Qt::WindowMinimized; + else if (states & Qt::WindowFullScreen) + m_windowState = Qt::WindowFullScreen; + else if (states & Qt::WindowMaximized) + m_windowState = Qt::WindowMaximized; +} + +QRect QWasmWindow::normalGeometry() const +{ + return m_normalGeometry; +} + +qreal QWasmWindow::devicePixelRatio() const +{ + return screen()->devicePixelRatio(); +} + +void QWasmWindow::requestUpdate() +{ + QPointer windowPointer(window()); + bool registered = QWasmEventDispatcher::registerRequestUpdateCallback([=](){ + if (windowPointer.isNull()) + return; + + deliverUpdateRequest(); + }); + + if (!registered) + QPlatformWindow::requestUpdate(); +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmwindow.h b/src/plugins/platforms/wasm/qwasmwindow.h new file mode 100644 index 0000000000..a0c463e796 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmwindow.h @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWASMWINDOW_H +#define QWASMWINDOW_H + +#include "qwasmintegration.h" +#include +#include +#include "qwasmbackingstore.h" +#include "qwasmscreen.h" +#include "qwasmcompositor.h" + +QT_BEGIN_NAMESPACE + +class QWasmCompositor; + +class QWasmWindow : public QPlatformWindow +{ +public: + enum ResizeMode { + ResizeNone, + ResizeTopLeft, + ResizeTop, + ResizeTopRight, + ResizeRight, + ResizeBottomRight, + ResizeBottom, + ResizeBottomLeft, + ResizeLeft + }; + + QWasmWindow(QWindow *w, QWasmCompositor *compositor, QWasmBackingStore *backingStore); + ~QWasmWindow(); + + void initialize() override; + + void setGeometry(const QRect &) override; + void setVisible(bool visible) override; + QMargins frameMargins() const override; + + WId winId() const override; + + void propagateSizeHints() override; + void raise() override; + void lower() override; + QRect normalGeometry() const override; + qreal devicePixelRatio() const override; + void requestUpdate() override; + + QWasmScreen *platformScreen() const; + void setBackingStore(QWasmBackingStore *store) { m_backingStore = store; } + 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); + + int titleHeight() const; + int borderWidth() const; + QRegion titleGeometry() const; + QRegion resizeRegion() const; + bool isPointOnTitle(QPoint point) const; + bool isPointOnResizeRegion(QPoint point) const; + ResizeMode resizeModeAtPoint(QPoint point) const; + QRect maxButtonRect() const; + QRect minButtonRect() const; + QRect closeButtonRect() const; + QRect sysMenuRect() const; + QRect normButtonRect() const; + QRegion titleControlRegion() const; + QWasmCompositor::SubControls activeSubControl() const; + + void setWindowState(Qt::WindowStates state) override; + bool setKeyboardGrabEnabled(bool) override { return false; } + bool setMouseGrabEnabled(bool) override { return false; } + +protected: + void invalidate(); + +protected: + friend class QWasmScreen; + + QWindow* m_window = nullptr; + QWasmCompositor *m_compositor = nullptr; + QWasmBackingStore *m_backingStore = nullptr; + QRect m_normalGeometry {0, 0, 0 ,0}; + + Qt::WindowState m_windowState = Qt::WindowNoState; + QWasmCompositor::SubControls m_activeControl = QWasmCompositor::SC_None; + WId m_winid = 0; + bool m_hasTitle = false; + bool m_needsCompositor = false; +}; +QT_END_NAMESPACE +#endif // QWASMWINDOW_H diff --git a/src/plugins/platforms/wasm/wasm.json b/src/plugins/platforms/wasm/wasm.json new file mode 100644 index 0000000000..6e700e06b9 --- /dev/null +++ b/src/plugins/platforms/wasm/wasm.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "wasm" ] +} diff --git a/src/plugins/platforms/wasm/wasm.pro b/src/plugins/platforms/wasm/wasm.pro new file mode 100644 index 0000000000..f1205702ef --- /dev/null +++ b/src/plugins/platforms/wasm/wasm.pro @@ -0,0 +1,65 @@ +TARGET = wasm +CONFIG += static plugin +QT += \ + core-private gui-private \ + eventdispatcher_support-private fontdatabase_support-private egl_support-private + +# Avoid X11 header collision, use generic EGL native types +DEFINES += QT_EGL_NO_X11 + +SOURCES = \ + main.cpp \ + qwasmintegration.cpp \ + qwasmwindow.cpp \ + qwasmscreen.cpp \ + qwasmfontdatabase.cpp \ + qwasmeventtranslator.cpp \ + qwasmeventdispatcher.cpp \ + qwasmcompositor.cpp \ + qwasmcursor.cpp \ + qwasmopenglcontext.cpp \ + qwasmtheme.cpp + +HEADERS = \ + qwasmintegration.h \ + qwasmwindow.h \ + qwasmscreen.h \ + qwasmfontdatabase.h \ + qwasmeventtranslator.h \ + qwasmeventdispatcher.h \ + qwasmcompositor.h \ + qwasmstylepixmaps_p.h \ + qwasmcursor.h \ + qwasmopenglcontext.h \ + qwasmtheme.h + +wasmfonts.files = \ + ../../../3rdparty/wasm/Vera.ttf \ + ../../../3rdparty/wasm/DejaVuSans.ttf +wasmfonts.prefix = /fonts +wasmfonts.base = ../../../3rdparty/wasm +RESOURCES += wasmfonts + +qtConfig(opengl) { + SOURCES += qwasmbackingstore.cpp + HEADERS += qwasmbackingstore.h +} +CONFIG += egl + +OTHER_FILES += \ + wasm.json \ + wasm_shell.html \ + qtloader.js + +shell_files.path = $$[QT_INSTALL_PLUGINS]/platforms +shell_files.files = \ + wasm_shell.html \ + qtloader.js \ + qtlogo.svg + +INSTALLS += shell_files + +PLUGIN_TYPE = platforms +PLUGIN_CLASS_NAME = QWasmIntegrationPlugin +!equals(TARGET, $$QT_DEFAULT_QPA_PLUGIN): PLUGIN_EXTENDS = - +load(qt_plugin) diff --git a/src/plugins/platforms/wasm/wasm_shell.html b/src/plugins/platforms/wasm/wasm_shell.html new file mode 100644 index 0000000000..67bfcdfbdc --- /dev/null +++ b/src/plugins/platforms/wasm/wasm_shell.html @@ -0,0 +1,61 @@ + + + + + + APPNAME + + + +

+
+ + Qt for WebAssembly: APPNAME +
+ +
+
+ + + + + + -- cgit v1.2.3