summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms/wasm/qwasmwindow.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/platforms/wasm/qwasmwindow.cpp')
-rw-r--r--src/plugins/platforms/wasm/qwasmwindow.cpp781
1 files changed, 529 insertions, 252 deletions
diff --git a/src/plugins/platforms/wasm/qwasmwindow.cpp b/src/plugins/platforms/wasm/qwasmwindow.cpp
index 1bcc407bc1..b8197c5113 100644
--- a/src/plugins/platforms/wasm/qwasmwindow.cpp
+++ b/src/plugins/platforms/wasm/qwasmwindow.cpp
@@ -1,96 +1,232 @@
-/****************************************************************************
-**
-** 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 (QWasmIntegration::get()->inputContext()) {
+ QWasmInputContext *wasmContext =
+ static_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,327 +234,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);
+}
- 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, 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);
+}
+
+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());
+ 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);
+ 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;
+ };
+ })();
- return ResizeNone;
+ const auto pointInScreen = platformScreen()->mapFromLocal(
+ dom::mapPoint(event.target(), platformScreen()->element(), event.localPoint));
+
+ 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, 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 &region)
{
- 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;
+}
- if (!registered)
- QPlatformWindow::requestUpdate();
+QWasmWindowTreeNode *QWasmWindow::parentNode()
+{
+ if (parent())
+ return static_cast<QWasmWindow *>(parent());
+ return platformScreen();
}
-bool QWasmWindow::hasTitleBar() const
+QWasmWindow *QWasmWindow::asWasmWindow()
{
- auto flags = window()->flags();
- return !(m_windowState & Qt::WindowFullScreen)
- && flags.testFlag(Qt::WindowTitleHint)
- && !flags.testFlag(Qt::Popup)
- && !flags.testFlag(Qt::ToolTip)
- && m_needsCompositor;
+ return this;
}
-bool QWasmWindow::windowIsPopupType(Qt::WindowType type) const
+void QWasmWindow::onParentChanged(QWasmWindowTreeNode *previous, QWasmWindowTreeNode *current,
+ QWasmWindowStack::PositionPreference positionPreference)
{
- if (type == Qt::Widget)
- type = window()->type();
- if (type != Qt::Tool)
- return true;
- return false;
+ 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