diff options
Diffstat (limited to 'src/plugins/platforms/wasm/qwasmwindow.cpp')
-rw-r--r-- | src/plugins/platforms/wasm/qwasmwindow.cpp | 761 |
1 files changed, 510 insertions, 251 deletions
diff --git a/src/plugins/platforms/wasm/qwasmwindow.cpp b/src/plugins/platforms/wasm/qwasmwindow.cpp index 1633ccf058..6fa56f1d06 100644 --- a/src/plugins/platforms/wasm/qwasmwindow.cpp +++ b/src/plugins/platforms/wasm/qwasmwindow.cpp @@ -3,75 +3,229 @@ #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; - static int serialNo = 0; - m_winid = ++serialNo; + 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")); - m_compositor->addWindow(this); + 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_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; + } - // Pure OpenGL windows draw directly using egl, disable the compositor. - m_compositor->setEnabled(w->surfaceType() != QSurface::OpenGLSurface); + m_keyDownCallback = + std::make_unique<qstdweb::EventCallback>(keyFocusWindow, "keydown", keyCallback); + m_keyUpCallback = std::make_unique<qstdweb::EventCallback>(keyFocusWindow, "keyup", keyCallback); - static std::unique_ptr<qstdweb::EventCallback> windowResizeEvent; - auto resizeCallback = [=](emscripten::val) { QPlatformWindow::requestActivateWindow();}; - windowResizeEvent.reset(new qstdweb::EventCallback(emscripten::val::global("window"), - "resize", resizeCallback)); + 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) +{ + return static_cast<QWasmWindow *>(window->handle()); +} + +void QWasmWindow::onRestoreClicked() { - QRect rect = windowGeometry(); + window()->setWindowState(Qt::WindowNoState); +} - QPlatformWindow::setGeometry(rect); +void QWasmWindow::onMaximizeClicked() +{ + window()->setWindowState(Qt::WindowMaximized); +} - 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::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 @@ -79,363 +233,468 @@ QWasmScreen *QWasmWindow::platformScreen() const return static_cast<QWasmScreen *>(window()->screen()->handle()); } -void QWasmWindow::setGeometry(const QRect &rect) +void QWasmWindow::paint() +{ + if (!m_backingStore || !isVisible() || m_context2d.isUndefined()) + return; + + auto image = m_backingStore->getUpdatedWebImage(this); + if (image.isUndefined()) + return; + m_context2d.call<void>("putImageData", image, emscripten::val(0), emscripten::val(0)); +} + +void QWasmWindow::setZOrder(int z) +{ + m_qtWindow["style"].set("zIndex", std::to_string(z)); +} + +void QWasmWindow::setWindowCursor(QByteArray cssCursorName) { - QRect r = rect; - if (m_needsCompositor) { - int yMin = window()->geometry().top() - window()->frameGeometry().top(); + m_windowContents["style"].set("cursor", emscripten::val(cssCursorName.constData())); +} - if (r.y() < yMin) - r.moveTop(yMin); +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::setGeometry(r); - QWindowSystemInterface::handleGeometryChange(window(), r); - invalidate(); + QWindowSystemInterface::handleGeometryChange(window(), clientAreaRect); + if (shouldInvalidate) + invalidate(); + else + m_compositor->requestUpdateWindow(this, QRect(QPoint(0, 0), geometry().size())); } void QWasmWindow::setVisible(bool visible) { + // TODO(mikolajboc): isVisible()? + const bool nowVisible = m_qtWindow["style"]["display"].as<std::string>() == "block"; + if (visible == nowVisible) + return; + + m_compositor->requestUpdateWindow(this, 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(); - m_compositor->setVisible(this, visible); } -bool QWasmWindow::isVisible() +bool QWasmWindow::isVisible() const { return window()->isVisible(); } QMargins QWasmWindow::frameMargins() const { - int border = hasTitleBar() ? 4. * (qreal(qt_defaultDpiX()) / 96.0) : 0; - int titleBarHeight = hasTitleBar() ? titleHeight() : 0; - - 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() { - QRect rect = windowGeometry(); - if (rect.size().width() < windowMinimumSize().width() - && rect.size().height() < windowMinimumSize().height()) { - rect.setSize(windowMinimumSize()); - setGeometry(rect); - } + // 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())); +} - 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::onActivationChanged(bool active) +{ + dom::syncCSSClassWith(m_qtWindow, "inactive", !active); +} - invalidate(); +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)); + + 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); - } - - if (normButtonRect().contains(global) && m_activeControl == QWasmCompositor::SC_TitleBarNormalButton) { - window()->setWindowState(Qt::WindowNoState); - } + 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; + + 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(); - QRegion result(window()->frameGeometry().x() + border, - window()->frameGeometry().y() + border, - window()->frameGeometry().width() - 2*border, - titleHeight()); + dom::syncCSSClassWith(m_qtWindow, "has-border", hasBorder()); + dom::syncCSSClassWith(m_qtWindow, "maximized", isMaximized); - result -= titleControlRegion(); + m_nonClientArea->titleBar()->setRestoreVisible(isMaximized); + m_nonClientArea->titleBar()->setMaximizeVisible(hasMaximizeButton()); - return result; + if (isVisible()) + QWindowSystemInterface::handleWindowStateChanged(window(), m_state, m_previousWindowState); + setGeometry(newGeom); } -QRegion QWasmWindow::resizeRegion() const +void QWasmWindow::commitParent(QWasmWindowTreeNode *parent) { - qDebug() << Q_FUNC_INFO; - 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); -} - -QWasmCompositor::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 QWasmCompositor::ResizeTopLeft; - if (center.contains(point)) - return QWasmCompositor::ResizeTop; - if (right.contains(point)) - return QWasmCompositor::ResizeTopRight; - } else if (middle.contains(point)) { - // Middle - if (left.contains(point)) - return QWasmCompositor::ResizeLeft; - if (right.contains(point)) - return QWasmCompositor::ResizeRight; - } else if (bottom.contains(point)) { - // Bottom - if (left.contains(point)) - return QWasmCompositor::ResizeBottomLeft; - if (center.contains(point)) - return QWasmCompositor::ResizeBottom; - if (right.contains(point)) - return QWasmCompositor::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 QWasmCompositor::ResizeNone; + return false; } -QRect getSubControlRect(const QWasmWindow *window, QWasmCompositor::SubControls subControl) +bool QWasmWindow::processWheel(const WheelEvent &event) { - QWasmCompositor::QWasmTitleBarOptions options = QWasmCompositor::makeTitleBarOptions(window); + // 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; + }; + })(); - QRect r = QWasmCompositor::titlebarRect(options, subControl); - r.translate(window->window()->frameGeometry().x(), window->window()->frameGeometry().y()); + const auto pointInScreen = platformScreen()->mapFromLocal( + dom::mapPoint(event.target(), platformScreen()->element(), event.localPoint)); - return r; + 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 QWasmWindow::maxButtonRect() const +QRect QWasmWindow::normalGeometry() const { - return getSubControlRect(this, QWasmCompositor::SC_TitleBarMaxButton); + return m_normalGeometry; } -QRect QWasmWindow::minButtonRect() const +qreal QWasmWindow::devicePixelRatio() const { - return getSubControlRect(this, QWasmCompositor::SC_TitleBarMinButton); + return screen()->devicePixelRatio(); } -QRect QWasmWindow::closeButtonRect() const +void QWasmWindow::requestUpdate() { - return getSubControlRect(this, QWasmCompositor::SC_TitleBarCloseButton); + m_compositor->requestUpdateWindow(this, QRect(QPoint(0, 0), geometry().size()), QWasmCompositor::UpdateRequestDelivery); } -QRect QWasmWindow::normButtonRect() const +bool QWasmWindow::hasFrame() const { - return getSubControlRect(this, QWasmCompositor::SC_TitleBarNormalButton); + return !m_flags.testFlag(Qt::FramelessWindowHint); } -QRect QWasmWindow::sysMenuRect() const +bool QWasmWindow::hasBorder() const { - return getSubControlRect(this, QWasmCompositor::SC_TitleBarSysMenu); + return hasFrame() && !m_state.testFlag(Qt::WindowFullScreen) && !m_flags.testFlag(Qt::SubWindow) + && !windowIsPopupType(m_flags) && !parent(); } -QRegion QWasmWindow::titleControlRegion() const +bool QWasmWindow::hasTitleBar() const { - QRegion result; - result += closeButtonRect(); - result += minButtonRect(); - result += maxButtonRect(); - result += sysMenuRect(); - - return result; + return hasBorder() && m_flags.testFlag(Qt::WindowTitleHint); } -void QWasmWindow::invalidate() +bool QWasmWindow::hasShadow() const { - m_compositor->requestUpdateWindow(this); + return hasBorder() && !m_flags.testFlag(Qt::NoDropShadowWindowHint); } -QWasmCompositor::SubControls QWasmWindow::activeSubControl() const +bool QWasmWindow::hasMaximizeButton() const { - return m_activeControl; + return !m_state.testFlag(Qt::WindowMaximized) && m_flags.testFlag(Qt::WindowMaximizeButtonHint); } -void QWasmWindow::setWindowState(Qt::WindowStates newState) +bool QWasmWindow::windowIsPopupType(Qt::WindowFlags flags) const { - const Qt::WindowStates oldState = m_windowState; - bool isActive = oldState.testFlag(Qt::WindowActive); + if (flags.testFlag(Qt::Tool)) + return false; // Qt::Tool has the Popup bit set but isn't an actual Popup window - if (newState.testFlag(Qt::WindowMinimized)) { - newState.setFlag(Qt::WindowMinimized, false); - qWarning("Qt::WindowMinimized is not implemented in wasm"); - } + return (flags.testFlag(Qt::Popup)); +} - // Always keep OpenGL apps fullscreen - if (!m_needsCompositor && !newState.testFlag(Qt::WindowFullScreen)) { - newState.setFlag(Qt::WindowFullScreen, true); - qWarning("Qt::WindowFullScreen must be set for OpenGL surfaces"); +void QWasmWindow::requestActivateWindow() +{ + QWindow *modalWindow; + if (QGuiApplicationPrivate::instance()->isWindowBlocked(window(), &modalWindow)) { + static_cast<QWasmWindow *>(modalWindow->handle())->requestActivateWindow(); + return; } - // Ignore WindowActive flag in comparison, as we want to preserve it either way - if ((newState & ~Qt::WindowActive) == (oldState & ~Qt::WindowActive)) - return; + raise(); + setAsActiveNode(); - newState.setFlag(Qt::WindowActive, isActive); + if (!QWasmIntegration::get()->inputContext()) + m_canvas.call<void>("focus"); - m_previousWindowState = oldState; - m_windowState = newState; + QPlatformWindow::requestActivateWindow(); +} - if (isVisible()) { - applyWindowState(); - } +bool QWasmWindow::setMouseGrabEnabled(bool grab) +{ + Q_UNUSED(grab); + return false; } -void QWasmWindow::applyWindowState() +bool QWasmWindow::windowEvent(QEvent *event) { - QRect newGeom; + 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); + } +} - if (m_windowState.testFlag(Qt::WindowFullScreen)) - newGeom = platformScreen()->geometry(); - else if (m_windowState.testFlag(Qt::WindowMaximized)) - newGeom = platformScreen()->availableGeometry(); - else - newGeom = normalGeometry(); +void QWasmWindow::setMask(const QRegion ®ion) +{ + if (region.isEmpty()) { + m_qtWindow["style"].set("clipPath", emscripten::val("")); + return; + } - QWindowSystemInterface::handleWindowStateChanged(window(), m_windowState, m_previousWindowState); - setGeometry(newGeom); + 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())); } -QRect QWasmWindow::normalGeometry() const +void QWasmWindow::setParent(const QPlatformWindow *) { - return m_normalGeometry; + commitParent(parentNode()); } -qreal QWasmWindow::devicePixelRatio() const +std::string QWasmWindow::canvasSelector() const { - return screen()->devicePixelRatio(); + return "!qtwindow" + std::to_string(m_winId); } -void QWasmWindow::requestUpdate() +emscripten::val QWasmWindow::containerElement() { - qDebug() << Q_FUNC_INFO; - if (m_compositor) { - m_compositor->requestUpdateWindow(this, QWasmCompositor::UpdateRequestDelivery); - return; - } - - static auto frame = [](double time, void *context) -> int { - Q_UNUSED(time); - QWasmWindow *window = static_cast<QWasmWindow *>(context); - window->m_requestAnimationFrameId = -1; - window->deliverUpdateRequest(); - return 0; - }; - m_requestAnimationFrameId = emscripten_request_animation_frame(frame, this); + return m_windowContents; } -bool QWasmWindow::hasTitleBar() const +QWasmWindowTreeNode *QWasmWindow::parentNode() { - Qt::WindowFlags flags = window()->flags(); - return !(m_windowState & Qt::WindowFullScreen) - && flags.testFlag(Qt::WindowTitleHint) - && !(windowIsPopupType(flags)) - && m_needsCompositor; + if (parent()) + return static_cast<QWasmWindow *>(parent()); + return platformScreen(); } -bool QWasmWindow::windowIsPopupType(Qt::WindowFlags flags) const +QWasmWindow *QWasmWindow::asWasmWindow() { - if (flags.testFlag(Qt::Tool)) - return false; // Qt::Tool has the Popup bit set but isn't - - return (flags.testFlag(Qt::Popup)); + return this; } -void QWasmWindow::requestActivateWindow() +void QWasmWindow::onParentChanged(QWasmWindowTreeNode *previous, QWasmWindowTreeNode *current, + QWasmWindowStack::PositionPreference positionPreference) { - qDebug() << Q_FUNC_INFO; - if (window()->isTopLevel()) - raise(); - QPlatformWindow::requestActivateWindow(); + 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 |