diff options
Diffstat (limited to 'src/plugins/platforms/wasm/qwasmwindow.cpp')
-rw-r--r-- | src/plugins/platforms/wasm/qwasmwindow.cpp | 773 |
1 files changed, 531 insertions, 242 deletions
diff --git a/src/plugins/platforms/wasm/qwasmwindow.cpp b/src/plugins/platforms/wasm/qwasmwindow.cpp index f95335f891..6fa56f1d06 100644 --- a/src/plugins/platforms/wasm/qwasmwindow.cpp +++ b/src/plugins/platforms/wasm/qwasmwindow.cpp @@ -1,96 +1,231 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <qpa/qwindowsysteminterface.h> #include <private/qguiapplication_p.h> -#include <QtGui/private/qopenglcontext_p.h> +#include <QtCore/qfile.h> #include <QtGui/private/qwindow_p.h> -#include <QtGui/qopenglcontext.h> - +#include <QtGui/private/qhighdpiscaling_p.h> +#include <private/qpixmapcache_p.h> +#include <QtGui/qopenglfunctions.h> +#include <QBuffer> + +#include "qwasmbase64iconstore.h" +#include "qwasmdom.h" +#include "qwasmclipboard.h" +#include "qwasmintegration.h" +#include "qwasmkeytranslator.h" #include "qwasmwindow.h" +#include "qwasmwindowclientarea.h" #include "qwasmscreen.h" #include "qwasmcompositor.h" +#include "qwasmevent.h" #include "qwasmeventdispatcher.h" +#include "qwasmaccessibility.h" +#include "qwasmclipboard.h" #include <iostream> +#include <sstream> -Q_GUI_EXPORT int qt_defaultDpiX(); +#include <emscripten/val.h> + +#include <QtCore/private/qstdweb_p.h> QT_BEGIN_NAMESPACE -QWasmWindow::QWasmWindow(QWindow *w, QWasmCompositor *compositor, QWasmBackingStore *backingStore) +namespace { +QWasmWindowStack::PositionPreference positionPreferenceFromWindowFlags(Qt::WindowFlags flags) +{ + if (flags.testFlag(Qt::WindowStaysOnTopHint)) + return QWasmWindowStack::PositionPreference::StayOnTop; + if (flags.testFlag(Qt::WindowStaysOnBottomHint)) + return QWasmWindowStack::PositionPreference::StayOnBottom; + return QWasmWindowStack::PositionPreference::Regular; +} +} // namespace + +Q_GUI_EXPORT int qt_defaultDpiX(); + +QWasmWindow::QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport, + QWasmCompositor *compositor, QWasmBackingStore *backingStore) : QPlatformWindow(w), m_window(w), m_compositor(compositor), - m_backingStore(backingStore) + m_backingStore(backingStore), + m_deadKeySupport(deadKeySupport), + m_document(dom::document()), + m_qtWindow(m_document.call<emscripten::val>("createElement", emscripten::val("div"))), + m_windowContents(m_document.call<emscripten::val>("createElement", emscripten::val("div"))), + m_canvasContainer(m_document.call<emscripten::val>("createElement", emscripten::val("div"))), + m_a11yContainer(m_document.call<emscripten::val>("createElement", emscripten::val("div"))), + m_canvas(m_document.call<emscripten::val>("createElement", emscripten::val("canvas"))) { - m_needsCompositor = w->surfaceType() != QSurface::OpenGLSurface; + m_qtWindow.set("className", "qt-window"); + m_qtWindow["style"].set("display", std::string("none")); + + m_nonClientArea = std::make_unique<NonClientArea>(this, m_qtWindow); + m_nonClientArea->titleBar()->setTitle(window()->title()); + + m_clientArea = std::make_unique<ClientArea>(this, compositor->screen(), m_windowContents); + + m_windowContents.set("className", "qt-window-contents"); + m_qtWindow.call<void>("appendChild", m_windowContents); + + m_canvas["classList"].call<void>("add", emscripten::val("qt-window-content")); + + // Set contenteditable so that the canvas gets clipboard events, + // then hide the resulting focus frame. + m_canvas.set("contentEditable", std::string("true")); + m_canvas["style"].set("outline", std::string("none")); + + QWasmClipboard::installEventHandlers(m_canvas); + + // set inputMode to none to stop mobile keyboard opening + // when user clicks anywhere on the canvas. + m_canvas.set("inputMode", std::string("none")); + + // Hide the canvas from screen readers. + m_canvas.call<void>("setAttribute", std::string("aria-hidden"), std::string("true")); + + m_windowContents.call<void>("appendChild", m_canvasContainer); + + m_canvasContainer["classList"].call<void>("add", emscripten::val("qt-window-canvas-container")); + m_canvasContainer.call<void>("appendChild", m_canvas); + + m_canvasContainer.call<void>("appendChild", m_a11yContainer); + m_a11yContainer["classList"].call<void>("add", emscripten::val("qt-window-a11y-container")); + + const bool rendersTo2dContext = w->surfaceType() != QSurface::OpenGLSurface; + if (rendersTo2dContext) + m_context2d = m_canvas.call<emscripten::val>("getContext", emscripten::val("2d")); static int serialNo = 0; - m_winid = ++serialNo; + m_winId = ++serialNo; + m_qtWindow.set("id", "qt-window-" + std::to_string(m_winId)); + emscripten::val::module_property("specialHTMLTargets").set(canvasSelector(), m_canvas); + + m_flags = window()->flags(); + + const auto pointerCallback = std::function([this](emscripten::val event) { + if (processPointer(*PointerEvent::fromWeb(event))) + event.call<void>("preventDefault"); + }); + + m_pointerEnterCallback = + std::make_unique<qstdweb::EventCallback>(m_qtWindow, "pointerenter", pointerCallback); + m_pointerLeaveCallback = + std::make_unique<qstdweb::EventCallback>(m_qtWindow, "pointerleave", pointerCallback); + + m_wheelEventCallback = std::make_unique<qstdweb::EventCallback>( + m_qtWindow, "wheel", [this](emscripten::val event) { + if (processWheel(*WheelEvent::fromWeb(event))) + event.call<void>("preventDefault"); + }); + + const auto keyCallback = std::function([this](emscripten::val event) { + if (processKey(*KeyEvent::fromWebWithDeadKeyTranslation(event, m_deadKeySupport))) + event.call<void>("preventDefault"); + event.call<void>("stopPropagation"); + }); + + emscripten::val keyFocusWindow; + if (QWasmInputContext *wasmContext = + qobject_cast<QWasmInputContext *>(QWasmIntegration::get()->inputContext())) { + // if there is an touchscreen input context, + // use that window for key input + keyFocusWindow = wasmContext->m_inputElement; + } else { + keyFocusWindow = m_qtWindow; + } - m_compositor->addWindow(this); + m_keyDownCallback = + std::make_unique<qstdweb::EventCallback>(keyFocusWindow, "keydown", keyCallback); + m_keyUpCallback = std::make_unique<qstdweb::EventCallback>(keyFocusWindow, "keyup", keyCallback); - // Pure OpenGL windows draw directly using egl, disable the compositor. - m_compositor->setEnabled(w->surfaceType() != QSurface::OpenGLSurface); + setParent(parent()); } QWasmWindow::~QWasmWindow() { - m_compositor->removeWindow(this); + emscripten::val::module_property("specialHTMLTargets").delete_(canvasSelector()); + m_canvasContainer.call<void>("removeChild", m_canvas); + m_context2d = emscripten::val::undefined(); + commitParent(nullptr); + if (m_requestAnimationFrameId > -1) + emscripten_cancel_animation_frame(m_requestAnimationFrameId); +#if QT_CONFIG(accessibility) + QWasmAccessibility::removeAccessibilityEnableButton(window()); +#endif } -void QWasmWindow::destroy() +QSurfaceFormat QWasmWindow::format() const { - if (m_backingStore) - m_backingStore->destroy(); + return window()->requestedFormat(); } -void QWasmWindow::initialize() +QWasmWindow *QWasmWindow::fromWindow(QWindow *window) { - QRect rect = windowGeometry(); + return static_cast<QWasmWindow *>(window->handle()); +} - QPlatformWindow::setGeometry(rect); +void QWasmWindow::onRestoreClicked() +{ + window()->setWindowState(Qt::WindowNoState); +} - 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); - } +void QWasmWindow::onMaximizeClicked() +{ + window()->setWindowState(Qt::WindowMaximized); +} + +void QWasmWindow::onToggleMaximized() +{ + window()->setWindowState(m_state.testFlag(Qt::WindowMaximized) ? Qt::WindowNoState + : Qt::WindowMaximized); +} + +void QWasmWindow::onCloseClicked() +{ + window()->close(); +} + +void QWasmWindow::onNonClientAreaInteraction() +{ + requestActivateWindow(); + QGuiApplicationPrivate::instance()->closeAllPopups(); +} + +bool QWasmWindow::onNonClientEvent(const PointerEvent &event) +{ + QPointF pointInScreen = platformScreen()->mapFromLocal( + dom::mapPoint(event.target(), platformScreen()->element(), event.localPoint)); + return QWindowSystemInterface::handleMouseEvent( + window(), QWasmIntegration::getTimestamp(), window()->mapFromGlobal(pointInScreen), + pointInScreen, event.mouseButtons, event.mouseButton, + MouseEvent::mouseEventTypeFromEventType(event.type, WindowArea::NonClient), + event.modifiers); +} + +void QWasmWindow::initialize() +{ + auto initialGeometry = QPlatformWindow::initialGeometry(window(), + windowGeometry(), defaultWindowSize, defaultWindowSize); + m_normalGeometry = initialGeometry; setWindowState(window()->windowStates()); setWindowFlags(window()->flags()); setWindowTitle(window()->title()); + setMask(QHighDpi::toNativeLocalRegion(window()->mask(), window())); + if (window()->isTopLevel()) setWindowIcon(window()->icon()); - m_normalGeometry = rect; + QPlatformWindow::setGeometry(m_normalGeometry); + +#if QT_CONFIG(accessibility) + // Add accessibility-enable button. The user can activate this + // button to opt-in to accessibility. + if (window()->isTopLevel()) + QWasmAccessibility::addAccessibilityEnableButton(window()); +#endif } QWasmScreen *QWasmWindow::platformScreen() const @@ -98,314 +233,468 @@ QWasmScreen *QWasmWindow::platformScreen() const return static_cast<QWasmScreen *>(window()->screen()->handle()); } -void QWasmWindow::setGeometry(const QRect &rect) +void QWasmWindow::paint() { - 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); + if (!m_backingStore || !isVisible() || m_context2d.isUndefined()) + return; - QWindowSystemInterface::flushWindowSystemEvents(); - invalidate(); + auto image = m_backingStore->getUpdatedWebImage(this); + if (image.isUndefined()) + return; + m_context2d.call<void>("putImageData", image, emscripten::val(0), emscripten::val(0)); } -void QWasmWindow::setVisible(bool visible) +void QWasmWindow::setZOrder(int z) { - QRect newGeom; + m_qtWindow["style"].set("zIndex", std::to_string(z)); +} - if (visible) { - const bool forceFullScreen = !m_needsCompositor;//make gl apps fullscreen for now +void QWasmWindow::setWindowCursor(QByteArray cssCursorName) +{ + m_windowContents["style"].set("cursor", emscripten::val(cssCursorName.constData())); +} - if (forceFullScreen || (m_windowState & Qt::WindowFullScreen)) - newGeom = platformScreen()->geometry(); - else if (m_windowState & Qt::WindowMaximized) - newGeom = platformScreen()->availableGeometry(); +void QWasmWindow::setGeometry(const QRect &rect) +{ + const auto margins = frameMargins(); + + const QRect clientAreaRect = ([this, &rect, &margins]() { + if (m_state.testFlag(Qt::WindowFullScreen)) + return platformScreen()->geometry(); + if (m_state.testFlag(Qt::WindowMaximized)) + return platformScreen()->availableGeometry().marginsRemoved(frameMargins()); + + auto offset = rect.topLeft() - (!parent() ? screen()->geometry().topLeft() : QPoint()); + + // In viewport + auto containerGeometryInViewport = + QRectF::fromDOMRect(parentNode()->containerElement().call<emscripten::val>( + "getBoundingClientRect")) + .toRect(); + + auto rectInViewport = QRect(containerGeometryInViewport.topLeft() + offset, rect.size()); + + QRect cappedGeometry(rectInViewport); + if (!parent()) { + // Clamp top level windows top position to the screen bounds + cappedGeometry.moveTop( + std::max(std::min(rectInViewport.y(), containerGeometryInViewport.bottom()), + containerGeometryInViewport.y() + margins.top())); + } + cappedGeometry.setSize( + cappedGeometry.size().expandedTo(windowMinimumSize()).boundedTo(windowMaximumSize())); + return QRect(QPoint(rect.x(), rect.y() + cappedGeometry.y() - rectInViewport.y()), + rect.size()); + })(); + m_nonClientArea->onClientAreaWidthChange(clientAreaRect.width()); + + const auto frameRect = + clientAreaRect + .adjusted(-margins.left(), -margins.top(), margins.right(), margins.bottom()) + .translated(!parent() ? -screen()->geometry().topLeft() : QPoint()); + + m_qtWindow["style"].set("left", std::to_string(frameRect.left()) + "px"); + m_qtWindow["style"].set("top", std::to_string(frameRect.top()) + "px"); + m_canvasContainer["style"].set("width", std::to_string(clientAreaRect.width()) + "px"); + m_canvasContainer["style"].set("height", std::to_string(clientAreaRect.height()) + "px"); + m_a11yContainer["style"].set("width", std::to_string(clientAreaRect.width()) + "px"); + m_a11yContainer["style"].set("height", std::to_string(clientAreaRect.height()) + "px"); + + // Important for the title flexbox to shrink correctly + m_windowContents["style"].set("width", std::to_string(clientAreaRect.width()) + "px"); + + QSizeF canvasSize = clientAreaRect.size() * devicePixelRatio(); + + m_canvas.set("width", canvasSize.width()); + m_canvas.set("height", canvasSize.height()); + + bool shouldInvalidate = true; + if (!m_state.testFlag(Qt::WindowFullScreen) && !m_state.testFlag(Qt::WindowMaximized)) { + shouldInvalidate = m_normalGeometry.size() != clientAreaRect.size(); + m_normalGeometry = clientAreaRect; } - QPlatformWindow::setVisible(visible); + QWindowSystemInterface::handleGeometryChange(window(), clientAreaRect); + if (shouldInvalidate) + invalidate(); + else + m_compositor->requestUpdateWindow(this, QRect(QPoint(0, 0), geometry().size())); +} - m_compositor->setVisible(this, visible); +void QWasmWindow::setVisible(bool visible) +{ + // TODO(mikolajboc): isVisible()? + const bool nowVisible = m_qtWindow["style"]["display"].as<std::string>() == "block"; + if (visible == nowVisible) + return; - if (!newGeom.isEmpty()) - setGeometry(newGeom); // may or may not generate an expose + m_compositor->requestUpdateWindow(this, QRect(QPoint(0, 0), geometry().size()), QWasmCompositor::ExposeEventDelivery); + m_qtWindow["style"].set("display", visible ? "block" : "none"); + if (window()->isActive()) + m_canvas.call<void>("focus"); + if (visible) + applyWindowState(); +} - invalidate(); +bool QWasmWindow::isVisible() const +{ + return window()->isVisible(); } QMargins QWasmWindow::frameMargins() const { - int border = hasTitleBar() ? 4. * (qreal(qt_defaultDpiX()) / 96.0) : 0; - int titleBarHeight = hasTitleBar() ? titleHeight() : 0; - - QMargins margins; - margins.setLeft(border); - margins.setRight(border); - margins.setTop(2*border + titleBarHeight); - margins.setBottom(border); - - return margins; + const auto frameRect = + QRectF::fromDOMRect(m_qtWindow.call<emscripten::val>("getBoundingClientRect")); + const auto canvasRect = + QRectF::fromDOMRect(m_windowContents.call<emscripten::val>("getBoundingClientRect")); + return QMarginsF(canvasRect.left() - frameRect.left(), canvasRect.top() - frameRect.top(), + frameRect.right() - canvasRect.right(), + frameRect.bottom() - canvasRect.bottom()) + .toMargins(); } void QWasmWindow::raise() { - m_compositor->raise(this); - QWindowSystemInterface::handleExposeEvent(window(), QRect(QPoint(0, 0), geometry().size())); + bringToTop(); invalidate(); + if (QWasmIntegration::get()->inputContext()) + m_canvas.call<void>("focus"); } void QWasmWindow::lower() { - m_compositor->lower(this); - QWindowSystemInterface::handleExposeEvent(window(), QRect(QPoint(0, 0), geometry().size())); + sendToBottom(); invalidate(); } WId QWasmWindow::winId() const { - return m_winid; + return m_winId; } void QWasmWindow::propagateSizeHints() { -// get rid of base class warning + // setGeometry() will take care of minimum and maximum size constraints + setGeometry(windowGeometry()); + m_nonClientArea->propagateSizeHints(); } -void QWasmWindow::injectMousePressed(const QPoint &local, const QPoint &global, - Qt::MouseButton button, Qt::KeyboardModifiers mods) +void QWasmWindow::setOpacity(qreal level) { - Q_UNUSED(local); - Q_UNUSED(mods); + m_qtWindow["style"].set("opacity", qBound(0.0, level, 1.0)); +} - if (!hasTitleBar() || button != Qt::LeftButton) - return; +void QWasmWindow::invalidate() +{ + m_compositor->requestUpdateWindow(this, QRect(QPoint(0, 0), geometry().size())); +} + +void QWasmWindow::onActivationChanged(bool active) +{ + dom::syncCSSClassWith(m_qtWindow, "inactive", !active); +} - 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; +void QWasmWindow::setWindowFlags(Qt::WindowFlags flags) +{ + if (flags.testFlag(Qt::WindowStaysOnTopHint) != m_flags.testFlag(Qt::WindowStaysOnTopHint) + || flags.testFlag(Qt::WindowStaysOnBottomHint) + != m_flags.testFlag(Qt::WindowStaysOnBottomHint)) { + onPositionPreferenceChanged(positionPreferenceFromWindowFlags(flags)); + } + m_flags = flags; + dom::syncCSSClassWith(m_qtWindow, "frameless", !hasFrame() || !window()->isTopLevel()); + dom::syncCSSClassWith(m_qtWindow, "has-border", hasBorder()); + dom::syncCSSClassWith(m_qtWindow, "has-shadow", hasShadow()); + dom::syncCSSClassWith(m_qtWindow, "has-title", hasTitleBar()); + dom::syncCSSClassWith(m_qtWindow, "transparent-for-input", + flags.testFlag(Qt::WindowTransparentForInput)); - invalidate(); + m_nonClientArea->titleBar()->setMaximizeVisible(hasMaximizeButton()); + m_nonClientArea->titleBar()->setCloseVisible(m_flags.testFlag(Qt::WindowCloseButtonHint)); } -void QWasmWindow::injectMouseReleased(const QPoint &local, const QPoint &global, - Qt::MouseButton button, Qt::KeyboardModifiers mods) +void QWasmWindow::setWindowState(Qt::WindowStates newState) { - Q_UNUSED(local); - Q_UNUSED(mods); + // Child windows can not have window states other than Qt::WindowActive + if (parent()) + newState &= Qt::WindowActive; - if (!hasTitleBar() || button != Qt::LeftButton) - return; + const Qt::WindowStates oldState = m_state; - if (closeButtonRect().contains(global) && m_activeControl == QWasmCompositor::SC_TitleBarCloseButton) { - window()->close(); + if (newState.testFlag(Qt::WindowMinimized)) { + newState.setFlag(Qt::WindowMinimized, false); + qWarning("Qt::WindowMinimized is not implemented in wasm"); + window()->setWindowStates(newState); return; } - 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()); - } + if (newState == oldState) + return; - m_activeControl = QWasmCompositor::SC_None; + m_state = newState; + m_previousWindowState = oldState; - invalidate(); + applyWindowState(); } -int QWasmWindow::titleHeight() const +void QWasmWindow::setWindowTitle(const QString &title) { - return 18. * (qreal(qt_defaultDpiX()) / 96.0);//dpiScaled(18.); + m_nonClientArea->titleBar()->setTitle(title); } -int QWasmWindow::borderWidth() const +void QWasmWindow::setWindowIcon(const QIcon &icon) { - return 4. * (qreal(qt_defaultDpiX()) / 96.0);// dpiScaled(4.); + const auto dpi = screen()->devicePixelRatio(); + auto pixmap = icon.pixmap(10 * dpi, 10 * dpi); + if (pixmap.isNull()) { + m_nonClientArea->titleBar()->setIcon( + Base64IconStore::get()->getIcon(Base64IconStore::IconType::QtLogo), "svg+xml"); + return; + } + + QByteArray bytes; + QBuffer buffer(&bytes); + pixmap.save(&buffer, "png"); + m_nonClientArea->titleBar()->setIcon(bytes.toBase64().toStdString(), "png"); } -QRegion QWasmWindow::titleGeometry() const +void QWasmWindow::applyWindowState() { - int border = borderWidth(); + QRect newGeom; - QRegion result(window()->frameGeometry().x() + border, - window()->frameGeometry().y() + border, - window()->frameGeometry().width() - 2*border, - titleHeight()); + const bool isFullscreen = m_state.testFlag(Qt::WindowFullScreen); + const bool isMaximized = m_state.testFlag(Qt::WindowMaximized); + if (isFullscreen) + newGeom = platformScreen()->geometry(); + else if (isMaximized) + newGeom = platformScreen()->availableGeometry().marginsRemoved(frameMargins()); + else + newGeom = normalGeometry(); - result -= titleControlRegion(); + dom::syncCSSClassWith(m_qtWindow, "has-border", hasBorder()); + dom::syncCSSClassWith(m_qtWindow, "maximized", isMaximized); - return result; + m_nonClientArea->titleBar()->setRestoreVisible(isMaximized); + m_nonClientArea->titleBar()->setMaximizeVisible(hasMaximizeButton()); + + if (isVisible()) + QWindowSystemInterface::handleWindowStateChanged(window(), m_state, m_previousWindowState); + setGeometry(newGeom); } -QRegion QWasmWindow::resizeRegion() const +void QWasmWindow::commitParent(QWasmWindowTreeNode *parent) { - int border = borderWidth(); - QRegion result(window()->frameGeometry().adjusted(-border, -border, border, border)); - result -= window()->frameGeometry().adjusted(border, border, -border, -border); - - return result; + onParentChanged(m_commitedParent, parent, positionPreferenceFromWindowFlags(window()->flags())); + m_commitedParent = parent; } -bool QWasmWindow::isPointOnTitle(QPoint point) const +bool QWasmWindow::processKey(const KeyEvent &event) { - bool ok = titleGeometry().contains(point); - return ok; + constexpr bool ProceedToNativeEvent = false; + Q_ASSERT(event.type == EventType::KeyDown || event.type == EventType::KeyUp); + + const auto clipboardResult = + QWasmIntegration::get()->getWasmClipboard()->processKeyboard(event); + + using ProcessKeyboardResult = QWasmClipboard::ProcessKeyboardResult; + if (clipboardResult == ProcessKeyboardResult::NativeClipboardEventNeeded) + return ProceedToNativeEvent; + + const auto result = QWindowSystemInterface::handleKeyEvent( + 0, event.type == EventType::KeyDown ? QEvent::KeyPress : QEvent::KeyRelease, event.key, + event.modifiers, event.text, event.autoRepeat); + return clipboardResult == ProcessKeyboardResult::NativeClipboardEventAndCopiedDataNeeded + ? ProceedToNativeEvent + : result; } -bool QWasmWindow::isPointOnResizeRegion(QPoint point) const +bool QWasmWindow::processPointer(const PointerEvent &event) { - if (window()->flags().testFlag(Qt::Popup)) + if (event.pointerType != PointerType::Mouse && event.pointerType != PointerType::Pen) return false; - 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; + + switch (event.type) { + case EventType::PointerEnter: { + const auto pointInScreen = platformScreen()->mapFromLocal( + dom::mapPoint(event.target(), platformScreen()->element(), event.localPoint)); + QWindowSystemInterface::handleEnterEvent( + window(), m_window->mapFromGlobal(pointInScreen), pointInScreen); + break; } + case EventType::PointerLeave: + QWindowSystemInterface::handleLeaveEvent(window()); + break; + default: + break; + } + + return false; +} + +bool QWasmWindow::processWheel(const WheelEvent &event) +{ + // Web scroll deltas are inverted from Qt deltas - negate. + const int scrollFactor = -([&event]() { + switch (event.deltaMode) { + case DeltaMode::Pixel: + return 1; + case DeltaMode::Line: + return 12; + case DeltaMode::Page: + return 20; + }; + })(); + + const auto pointInScreen = platformScreen()->mapFromLocal( + dom::mapPoint(event.target(), platformScreen()->element(), event.localPoint)); - return ResizeNone; + return QWindowSystemInterface::handleWheelEvent( + window(), QWasmIntegration::getTimestamp(), window()->mapFromGlobal(pointInScreen), + pointInScreen, (event.delta * scrollFactor).toPoint(), + (event.delta * scrollFactor).toPoint(), event.modifiers, Qt::NoScrollPhase, + Qt::MouseEventNotSynthesized, event.webkitDirectionInvertedFromDevice); } -QRect getSubControlRect(const QWasmWindow *window, QWasmCompositor::SubControls subControl) +QRect QWasmWindow::normalGeometry() const { - QWasmCompositor::QWasmTitleBarOptions options = QWasmCompositor::makeTitleBarOptions(window); + return m_normalGeometry; +} - QRect r = QWasmCompositor::titlebarRect(options, subControl); - r.translate(window->window()->frameGeometry().x(), window->window()->frameGeometry().y()); +qreal QWasmWindow::devicePixelRatio() const +{ + return screen()->devicePixelRatio(); +} - return r; +void QWasmWindow::requestUpdate() +{ + m_compositor->requestUpdateWindow(this, QRect(QPoint(0, 0), geometry().size()), QWasmCompositor::UpdateRequestDelivery); } -QRect QWasmWindow::maxButtonRect() const +bool QWasmWindow::hasFrame() const { - return getSubControlRect(this, QWasmCompositor::SC_TitleBarMaxButton); + return !m_flags.testFlag(Qt::FramelessWindowHint); } -QRect QWasmWindow::minButtonRect() const +bool QWasmWindow::hasBorder() const { - return getSubControlRect(this, QWasmCompositor::SC_TitleBarMinButton); + return hasFrame() && !m_state.testFlag(Qt::WindowFullScreen) && !m_flags.testFlag(Qt::SubWindow) + && !windowIsPopupType(m_flags) && !parent(); } -QRect QWasmWindow::closeButtonRect() const +bool QWasmWindow::hasTitleBar() const { - return getSubControlRect(this, QWasmCompositor::SC_TitleBarCloseButton); + return hasBorder() && m_flags.testFlag(Qt::WindowTitleHint); } -QRect QWasmWindow::normButtonRect() const +bool QWasmWindow::hasShadow() const { - return getSubControlRect(this, QWasmCompositor::SC_TitleBarNormalButton); + return hasBorder() && !m_flags.testFlag(Qt::NoDropShadowWindowHint); } -QRect QWasmWindow::sysMenuRect() const +bool QWasmWindow::hasMaximizeButton() const { - return getSubControlRect(this, QWasmCompositor::SC_TitleBarSysMenu); + return !m_state.testFlag(Qt::WindowMaximized) && m_flags.testFlag(Qt::WindowMaximizeButtonHint); } -QRegion QWasmWindow::titleControlRegion() const +bool QWasmWindow::windowIsPopupType(Qt::WindowFlags flags) const { - QRegion result; - result += closeButtonRect(); - result += minButtonRect(); - result += maxButtonRect(); - result += sysMenuRect(); + if (flags.testFlag(Qt::Tool)) + return false; // Qt::Tool has the Popup bit set but isn't an actual Popup window - return result; + return (flags.testFlag(Qt::Popup)); } -void QWasmWindow::invalidate() +void QWasmWindow::requestActivateWindow() { - m_compositor->requestRedraw(); + QWindow *modalWindow; + if (QGuiApplicationPrivate::instance()->isWindowBlocked(window(), &modalWindow)) { + static_cast<QWasmWindow *>(modalWindow->handle())->requestActivateWindow(); + return; + } + + raise(); + setAsActiveNode(); + + if (!QWasmIntegration::get()->inputContext()) + m_canvas.call<void>("focus"); + + QPlatformWindow::requestActivateWindow(); } -QWasmCompositor::SubControls QWasmWindow::activeSubControl() const +bool QWasmWindow::setMouseGrabEnabled(bool grab) { - return m_activeControl; + Q_UNUSED(grab); + return false; } -void QWasmWindow::setWindowState(Qt::WindowStates states) +bool QWasmWindow::windowEvent(QEvent *event) { - 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; + switch (event->type()) { + case QEvent::WindowBlocked: + m_qtWindow["classList"].call<void>("add", emscripten::val("blocked")); + return false; // Propagate further + case QEvent::WindowUnblocked:; + m_qtWindow["classList"].call<void>("remove", emscripten::val("blocked")); + return false; // Propagate further + default: + return QPlatformWindow::windowEvent(event); + } } -QRect QWasmWindow::normalGeometry() const +void QWasmWindow::setMask(const QRegion ®ion) { - return m_normalGeometry; + if (region.isEmpty()) { + m_qtWindow["style"].set("clipPath", emscripten::val("")); + return; + } + + std::ostringstream cssClipPath; + cssClipPath << "path('"; + for (const auto &rect : region) { + const auto cssRect = rect.adjusted(0, 0, 1, 1); + cssClipPath << "M " << cssRect.left() << " " << cssRect.top() << " "; + cssClipPath << "L " << cssRect.right() << " " << cssRect.top() << " "; + cssClipPath << "L " << cssRect.right() << " " << cssRect.bottom() << " "; + cssClipPath << "L " << cssRect.left() << " " << cssRect.bottom() << " z "; + } + cssClipPath << "')"; + m_qtWindow["style"].set("clipPath", emscripten::val(cssClipPath.str())); } -qreal QWasmWindow::devicePixelRatio() const +void QWasmWindow::setParent(const QPlatformWindow *) { - return screen()->devicePixelRatio(); + commitParent(parentNode()); } -void QWasmWindow::requestUpdate() +std::string QWasmWindow::canvasSelector() const { - QPointer<QWindow> windowPointer(window()); - bool registered = QWasmEventDispatcher::registerRequestUpdateCallback([=](){ - if (windowPointer.isNull()) - return; + return "!qtwindow" + std::to_string(m_winId); +} - deliverUpdateRequest(); - }); +emscripten::val QWasmWindow::containerElement() +{ + return m_windowContents; +} + +QWasmWindowTreeNode *QWasmWindow::parentNode() +{ + if (parent()) + return static_cast<QWasmWindow *>(parent()); + return platformScreen(); +} - if (!registered) - QPlatformWindow::requestUpdate(); +QWasmWindow *QWasmWindow::asWasmWindow() +{ + return this; } -bool QWasmWindow::hasTitleBar() const +void QWasmWindow::onParentChanged(QWasmWindowTreeNode *previous, QWasmWindowTreeNode *current, + QWasmWindowStack::PositionPreference positionPreference) { - return !(m_windowState & Qt::WindowFullScreen) && (window()->flags().testFlag(Qt::WindowTitleHint) && m_needsCompositor) - && !window()->flags().testFlag(Qt::Popup); + if (previous) + previous->containerElement().call<void>("removeChild", m_qtWindow); + if (current) + current->containerElement().call<void>("appendChild", m_qtWindow); + QWasmWindowTreeNode::onParentChanged(previous, current, positionPreference); } QT_END_NAMESPACE |