diff options
Diffstat (limited to 'src/plugins/platforms/wasm/qwasmscreen.cpp')
-rw-r--r-- | src/plugins/platforms/wasm/qwasmscreen.cpp | 289 |
1 files changed, 174 insertions, 115 deletions
diff --git a/src/plugins/platforms/wasm/qwasmscreen.cpp b/src/plugins/platforms/wasm/qwasmscreen.cpp index 51593b9d29..ddf8140c48 100644 --- a/src/plugins/platforms/wasm/qwasmscreen.cpp +++ b/src/plugins/platforms/wasm/qwasmscreen.cpp @@ -2,111 +2,124 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "qwasmscreen.h" -#include "qwasmwindow.h" -#include "qwasmeventtranslator.h" + #include "qwasmcompositor.h" +#include "qwasmcssstyle.h" #include "qwasmintegration.h" -#include "qwasmstring.h" +#include "qwasmkeytranslator.h" +#include "qwasmwindow.h" #include <emscripten/bind.h> #include <emscripten/val.h> -#include <QtGui/private/qeglconvenience_p.h> -#ifndef QT_NO_OPENGL -# include <QtGui/private/qeglplatformcontext_p.h> -#endif #include <qpa/qwindowsysteminterface.h> #include <QtCore/qcoreapplication.h> #include <QtGui/qguiapplication.h> #include <private/qhighdpiscaling_p.h> -using namespace emscripten; +#include <tuple> QT_BEGIN_NAMESPACE -const char * QWasmScreen::m_canvasResizeObserverCallbackContextPropertyName = "data-qtCanvasResizeObserverCallbackContext"; +using namespace emscripten; + +const char *QWasmScreen::m_canvasResizeObserverCallbackContextPropertyName = + "data-qtCanvasResizeObserverCallbackContext"; QWasmScreen::QWasmScreen(const emscripten::val &containerOrCanvas) - : m_container(containerOrCanvas) - , m_canvas(emscripten::val::undefined()) - , m_compositor(new QWasmCompositor(this)) - , m_eventTranslator(new QWasmEventTranslator()) + : m_container(containerOrCanvas), + m_intermediateContainer(emscripten::val::undefined()), + m_shadowContainer(emscripten::val::undefined()), + m_compositor(new QWasmCompositor(this)), + m_deadKeySupport(std::make_unique<QWasmDeadKeySupport>()) { - // Each screen is backed by a html canvas element. Use either - // a user-supplied canvas or create one as a child of the user- - // supplied root element. - std::string tagName = containerOrCanvas["tagName"].as<std::string>(); - if (tagName == "CANVAS" || tagName == "canvas") { - m_canvas = containerOrCanvas; - } else { - // Create the canvas (for the correct document) as a child of the container - m_canvas = containerOrCanvas["ownerDocument"].call<emscripten::val>("createElement", std::string("canvas")); - containerOrCanvas.call<void>("appendChild", m_canvas); - std::string screenId = std::string("qtcanvas_") + std::to_string(uint32_t(this)); - m_canvas.set("id", screenId); - - // Make the canvas occupy 100% of parent - emscripten::val style = m_canvas["style"]; - style.set("width", std::string("100%")); - style.set("height", std::string("100%")); + auto document = m_container["ownerDocument"]; + // Each screen is represented by a div container. All of the windows exist therein as + // its children. Qt versions < 6.5 used to represent screens as canvas. Support that by + // transforming the canvas into a div. + if (m_container["tagName"].call<std::string>("toLowerCase") == "canvas") { + qWarning() << "Support for canvas elements as an element backing screen is deprecated. The " + "canvas provided for the screen will be transformed into a div."; + auto container = document.call<emscripten::val>("createElement", emscripten::val("div")); + m_container["parentNode"].call<void>("replaceChild", container, m_container); + m_container = container; } - // Configure canvas - emscripten::val style = m_canvas["style"]; - style.set("border", std::string("0px none")); - style.set("background-color", std::string("white")); - - // Set contenteditable so that the canvas gets clipboard events, - // then hide the resulting focus frame, and reset the cursor. - m_canvas.set("contentEditable", std::string("true")); - // set inputmode to none to stop mobile keyboard opening - // when user clicks anywhere on the canvas. - m_canvas.set("inputmode", std::string("none")); - style.set("outline", std::string("0px solid transparent")); - style.set("caret-color", std::string("transparent")); - style.set("cursor", std::string("default")); + // Create an intermediate container which we can remove during cleanup in ~QWasmScreen(). + // This is required due to the attachShadow() call below; there is no corresponding + // "detachShadow()" API to return the container to its previous state. + m_intermediateContainer = document.call<emscripten::val>("createElement", emscripten::val("div")); + m_intermediateContainer.set("id", std::string("qt-shadow-container")); + emscripten::val intermediateContainerStyle = m_intermediateContainer["style"]; + intermediateContainerStyle.set("width", std::string("100%")); + intermediateContainerStyle.set("height", std::string("100%")); + m_container.call<void>("appendChild", m_intermediateContainer); + + auto shadowOptions = emscripten::val::object(); + shadowOptions.set("mode", "open"); + auto shadow = m_intermediateContainer.call<emscripten::val>("attachShadow", shadowOptions); + + m_shadowContainer = document.call<emscripten::val>("createElement", emscripten::val("div")); + + shadow.call<void>("appendChild", QWasmCSSStyle::createStyleElement(m_shadowContainer)); + + shadow.call<void>("appendChild", m_shadowContainer); + + m_shadowContainer.set("id", std::string("qt-screen-") + std::to_string(uintptr_t(this))); + + m_shadowContainer["classList"].call<void>("add", std::string("qt-screen")); // Disable the default context menu; Qt applications typically // provide custom right-click behavior. - m_onContextMenu = std::make_unique<qstdweb::EventCallback>(m_canvas, "contextmenu", [](emscripten::val event){ - event.call<void>("preventDefault"); - }); - - // Create "specialHTMLTargets" mapping for the canvas. Normally, Emscripten - // uses the html element id when targeting elements, for example when registering - // event callbacks. However, this approach is limited to supporting single-document - // apps/ages only, since Emscripten uses the main document to look up the element. - // As a workaround for this, Emscripten supports registering custom mappings in the - // "specialHTMLTargets" object. Add a mapping for the canvas for this screen. - emscripten::val specialHtmlTargets = emscripten::val::module_property("specialHTMLTargets"); - std::string id = std::string("!qtcanvas_") + std::to_string(uint32_t(this)); - specialHtmlTargets.set(id, m_canvas); - - // Install event handlers on the container/canvas. This must be - // done after the canvas has been created above. - m_compositor->initEventHandlers(); + m_onContextMenu = std::make_unique<qstdweb::EventCallback>( + m_shadowContainer, "contextmenu", + [](emscripten::val event) { event.call<void>("preventDefault"); }); + // Create "specialHTMLTargets" mapping for the canvas - the element might be unreachable based + // on its id only under some conditions, like the target being embedded in a shadow DOM or a + // subframe. + emscripten::val::module_property("specialHTMLTargets") + .set(eventTargetId().toStdString(), m_shadowContainer); + + emscripten::val::module_property("specialHTMLTargets") + .set(outerScreenId().toStdString(), m_container); updateQScreenAndCanvasRenderSize(); - m_canvas.call<void>("focus"); + m_shadowContainer.call<void>("focus"); + + m_touchDevice = std::make_unique<QPointingDevice>( + "touchscreen", 1, QInputDevice::DeviceType::TouchScreen, + QPointingDevice::PointerType::Finger, + QPointingDevice::Capability::Position | QPointingDevice::Capability::Area + | QPointingDevice::Capability::NormalizedPosition, + 10, 0); + m_tabletDevice = std::make_unique<QPointingDevice>( + "stylus", 2, QInputDevice::DeviceType::Stylus, + QPointingDevice::PointerType::Pen, + QPointingDevice::Capability::Position | QPointingDevice::Capability::Pressure + | QPointingDevice::Capability::NormalizedPosition + | QInputDevice::Capability::MouseEmulation + | QInputDevice::Capability::Hover | QInputDevice::Capability::Rotation + | QInputDevice::Capability::XTilt | QInputDevice::Capability::YTilt + | QInputDevice::Capability::TangentialPressure, + 0, 0); + + QWindowSystemInterface::registerInputDevice(m_touchDevice.get()); } QWasmScreen::~QWasmScreen() { - // Delete the compositor before removing the screen from specialHTMLTargets, - // since its destructor needs to look up the target when deregistering - // event handlers. - m_compositor = nullptr; + m_intermediateContainer.call<void>("remove"); - emscripten::val specialHtmlTargets = emscripten::val::module_property("specialHTMLTargets"); - std::string id = std::string("!qtcanvas_") + std::to_string(uint32_t(this)); - specialHtmlTargets.set(id, emscripten::val::undefined()); + emscripten::val::module_property("specialHTMLTargets") + .set(eventTargetId().toStdString(), emscripten::val::undefined()); - m_canvas.set(m_canvasResizeObserverCallbackContextPropertyName, emscripten::val(intptr_t(0))); + m_shadowContainer.set(m_canvasResizeObserverCallbackContextPropertyName, + emscripten::val(intptr_t(0))); } void QWasmScreen::deleteScreen() { - m_compositor->destroy(); + // Deletes |this|! QWindowSystemInterface::handleScreenRemoved(this); } @@ -117,6 +130,8 @@ QWasmScreen *QWasmScreen::get(QPlatformScreen *screen) QWasmScreen *QWasmScreen::get(QScreen *screen) { + if (!screen) + return nullptr; return get(screen->handle()); } @@ -125,34 +140,21 @@ QWasmCompositor *QWasmScreen::compositor() return m_compositor.get(); } -QWasmEventTranslator *QWasmScreen::eventTranslator() -{ - return m_eventTranslator.get(); -} - -emscripten::val QWasmScreen::container() const -{ - return m_container; -} - -emscripten::val QWasmScreen::canvas() const +emscripten::val QWasmScreen::element() const { - return m_canvas; + return m_shadowContainer; } -// Returns the html element id for the screen's canvas. -QString QWasmScreen::canvasId() const +QString QWasmScreen::eventTargetId() const { - return QWasmString::toQString(m_canvas["id"]); + // Return a globally unique id for the canvas. We can choose any string, + // as long as it starts with a "!". + return QString("!qtcanvas_%1").arg(uintptr_t(this)); } -// Returns the canvas _target_ id, for use with Emscripten's -// event registration functions. The target id is a globally -// unique id, unlike the html element id which is only unique -// within one html document. See specialHtmlTargets. -QString QWasmScreen::canvasTargetId() const +QString QWasmScreen::outerScreenId() const { - return QStringLiteral("!qtcanvas_") + QString::number(int32_t(this)); + return QString("!outerscreen_%1").arg(uintptr_t(this)); } QRect QWasmScreen::geometry() const @@ -204,7 +206,7 @@ qreal QWasmScreen::devicePixelRatio() const QString QWasmScreen::name() const { - return canvasId(); + return QString::fromEcmaString(m_shadowContainer["id"]); } QPlatformCursor *QWasmScreen::cursor() const @@ -221,12 +223,30 @@ void QWasmScreen::resizeMaximizedWindows() QWindow *QWasmScreen::topWindow() const { - return m_compositor->keyWindow(); + return activeChild() ? activeChild()->window() : nullptr; } QWindow *QWasmScreen::topLevelAt(const QPoint &p) const { - return m_compositor->windowAt(p); + const auto found = + std::find_if(childStack().begin(), childStack().end(), [&p](const QWasmWindow *window) { + const QRect geometry = window->windowFrameGeometry(); + + return window->isVisible() && geometry.contains(p); + }); + return found != childStack().end() ? (*found)->window() : nullptr; +} + +QPointF QWasmScreen::mapFromLocal(const QPointF &p) const +{ + return geometry().topLeft() + p; +} + +QPointF QWasmScreen::clipPoint(const QPointF &p) const +{ + const auto geometryF = screen()->geometry().toRectF(); + return QPointF(qBound(geometryF.left(), p.x(), geometryF.right()), + qBound(geometryF.top(), p.y(), geometryF.bottom())); } void QWasmScreen::invalidateSize() @@ -237,10 +257,23 @@ void QWasmScreen::invalidateSize() void QWasmScreen::setGeometry(const QRect &rect) { m_geometry = rect; - QWindowSystemInterface::handleScreenGeometryChange(QPlatformScreen::screen(), geometry(), availableGeometry()); + QWindowSystemInterface::handleScreenGeometryChange(QPlatformScreen::screen(), geometry(), + availableGeometry()); resizeMaximizedWindows(); } +void QWasmScreen::onSubtreeChanged(QWasmWindowTreeNodeChangeType changeType, + QWasmWindowTreeNode *parent, QWasmWindow *child) +{ + Q_UNUSED(parent); + if (changeType == QWasmWindowTreeNodeChangeType::NodeInsertion && parent == this + && childStack().size() == 1) { + child->window()->setFlag(Qt::WindowStaysOnBottomHint); + } + QWasmWindowTreeNode::onSubtreeChanged(changeType, parent, child); + m_compositor->onWindowTreeChanged(changeType, child); +} + void QWasmScreen::updateQScreenAndCanvasRenderSize() { // The HTML canvas has two sizes: the CSS size and the canvas render size. @@ -249,28 +282,26 @@ void QWasmScreen::updateQScreenAndCanvasRenderSize() // size must be set manually and is not auto-updated on CSS size change. // Setting the render size to a value larger than the CSS size enables high-dpi // rendering. - - QByteArray canvasSelector = canvasTargetId().toUtf8(); double css_width; double css_height; - emscripten_get_element_css_size(canvasSelector.constData(), &css_width, &css_height); + emscripten_get_element_css_size(outerScreenId().toUtf8().constData(), &css_width, &css_height); QSizeF cssSize(css_width, css_height); QSizeF canvasSize = cssSize * devicePixelRatio(); - m_canvas.set("width", canvasSize.width()); - m_canvas.set("height", canvasSize.height()); + m_shadowContainer.set("width", canvasSize.width()); + m_shadowContainer.set("height", canvasSize.height()); - // Returns the html elments document/body position + // Returns the html elements document/body position auto getElementBodyPosition = [](const emscripten::val &element) -> QPoint { - emscripten::val bodyRect = element["ownerDocument"]["body"].call<emscripten::val>("getBoundingClientRect"); + emscripten::val bodyRect = + element["ownerDocument"]["body"].call<emscripten::val>("getBoundingClientRect"); emscripten::val canvasRect = element.call<emscripten::val>("getBoundingClientRect"); - return QPoint (canvasRect["left"].as<int>() - bodyRect["left"].as<int>(), - canvasRect["top"].as<int>() - bodyRect["top"].as<int>()); + return QPoint(canvasRect["left"].as<int>() - bodyRect["left"].as<int>(), + canvasRect["top"].as<int>() - bodyRect["top"].as<int>()); }; - setGeometry(QRect(getElementBodyPosition(m_canvas), cssSize.toSize())); - m_compositor->requestUpdateAllWindows(); + setGeometry(QRect(getElementBodyPosition(m_shadowContainer), cssSize.toSize())); } void QWasmScreen::canvasResizeObserverCallback(emscripten::val entries, emscripten::val) @@ -279,20 +310,23 @@ void QWasmScreen::canvasResizeObserverCallback(emscripten::val entries, emscript if (count == 0) return; emscripten::val entry = entries[0]; - QWasmScreen *screen = - reinterpret_cast<QWasmScreen *>(entry["target"][m_canvasResizeObserverCallbackContextPropertyName].as<intptr_t>()); + QWasmScreen *screen = reinterpret_cast<QWasmScreen *>( + entry["target"][m_canvasResizeObserverCallbackContextPropertyName].as<intptr_t>()); if (!screen) { qWarning() << "QWasmScreen::canvasResizeObserverCallback: missing screen pointer"; return; } // We could access contentBoxSize|contentRect|devicePixelContentBoxSize on the entry here, but - // these are not universally supported across all browsers. Get the sizes from the canvas instead. + // these are not universally supported across all browsers. Get the sizes from the canvas + // instead. screen->updateQScreenAndCanvasRenderSize(); } -EMSCRIPTEN_BINDINGS(qtCanvasResizeObserverCallback) { - emscripten::function("qtCanvasResizeObserverCallback", &QWasmScreen::canvasResizeObserverCallback); +EMSCRIPTEN_BINDINGS(qtCanvasResizeObserverCallback) +{ + emscripten::function("qtCanvasResizeObserverCallback", + &QWasmScreen::canvasResizeObserverCallback); } void QWasmScreen::installCanvasResizeObserver() @@ -300,15 +334,40 @@ void QWasmScreen::installCanvasResizeObserver() emscripten::val ResizeObserver = emscripten::val::global("ResizeObserver"); if (ResizeObserver == emscripten::val::undefined()) return; // ResizeObserver API is not available - emscripten::val resizeObserver = ResizeObserver.new_(emscripten::val::module_property("qtCanvasResizeObserverCallback")); + emscripten::val resizeObserver = + ResizeObserver.new_(emscripten::val::module_property("qtCanvasResizeObserverCallback")); if (resizeObserver == emscripten::val::undefined()) return; // Something went horribly wrong // We need to get back to this instance from the (static) resize callback; // set a "data-" property on the canvas element. - m_canvas.set(m_canvasResizeObserverCallbackContextPropertyName, emscripten::val(intptr_t(this))); + m_shadowContainer.set(m_canvasResizeObserverCallbackContextPropertyName, + emscripten::val(intptr_t(this))); - resizeObserver.call<void>("observe", m_canvas); + resizeObserver.call<void>("observe", m_shadowContainer); +} + +emscripten::val QWasmScreen::containerElement() +{ + return m_shadowContainer; +} + +QWasmWindowTreeNode *QWasmScreen::parentNode() +{ + return nullptr; +} + +QList<QWasmWindow *> QWasmScreen::allWindows() +{ + QList<QWasmWindow *> windows; + for (auto *child : childStack()) { + QWindowList list = child->window()->findChildren<QWindow *>(Qt::FindChildrenRecursively); + std::transform( + list.begin(), list.end(), std::back_inserter(windows), + [](const QWindow *window) { return static_cast<QWasmWindow *>(window->handle()); }); + windows.push_back(child); + } + return windows; } QT_END_NAMESPACE |