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.cpp890
1 files changed, 492 insertions, 398 deletions
diff --git a/src/plugins/platforms/wasm/qwasmwindow.cpp b/src/plugins/platforms/wasm/qwasmwindow.cpp
index 02298bb4a1..b8197c5113 100644
--- a/src/plugins/platforms/wasm/qwasmwindow.cpp
+++ b/src/plugins/platforms/wasm/qwasmwindow.cpp
@@ -3,388 +3,385 @@
#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 "qwasmstylepixmaps_p.h"
#include "qwasmcompositor.h"
+#include "qwasmevent.h"
#include "qwasmeventdispatcher.h"
+#include "qwasmaccessibility.h"
+#include "qwasmclipboard.h"
#include <iostream>
+#include <sstream>
+#include <emscripten/val.h>
-QT_BEGIN_NAMESPACE
+#include <QtCore/private/qstdweb_p.h>
-Q_GUI_EXPORT int qt_defaultDpiX();
+QT_BEGIN_NAMESPACE
namespace {
-// from commonstyle.cpp
-static QPixmap cachedPixmapFromXPM(const char *const *xpm)
-{
- QPixmap result;
- const QString tag = QString::asprintf("xpm:0x%p", static_cast<const void *>(xpm));
- if (!QPixmapCache::find(tag, &result)) {
- result = QPixmap(xpm);
- QPixmapCache::insert(tag, result);
- }
- return result;
-}
-
-QPalette makePalette()
+QWasmWindowStack::PositionPreference positionPreferenceFromWindowFlags(Qt::WindowFlags flags)
{
- QPalette palette;
- palette.setColor(QPalette::Active, QPalette::Highlight,
- palette.color(QPalette::Active, QPalette::Highlight));
- palette.setColor(QPalette::Active, QPalette::Base,
- palette.color(QPalette::Active, QPalette::Highlight));
- palette.setColor(QPalette::Inactive, QPalette::Highlight,
- palette.color(QPalette::Inactive, QPalette::Dark));
- palette.setColor(QPalette::Inactive, QPalette::Base,
- palette.color(QPalette::Inactive, QPalette::Dark));
- palette.setColor(QPalette::Inactive, QPalette::HighlightedText,
- palette.color(QPalette::Inactive, QPalette::Window));
-
- return palette;
+ if (flags.testFlag(Qt::WindowStaysOnTopHint))
+ return QWasmWindowStack::PositionPreference::StayOnTop;
+ if (flags.testFlag(Qt::WindowStaysOnBottomHint))
+ return QWasmWindowStack::PositionPreference::StayOnBottom;
+ return QWasmWindowStack::PositionPreference::Regular;
}
+} // namespace
-void drawItemPixmap(QPainter *painter, const QRect &rect, int alignment, const QPixmap &pixmap)
-{
- qreal scale = pixmap.devicePixelRatio();
- QSize size = pixmap.size() / scale;
- int x = rect.x();
- int y = rect.y();
- int w = size.width();
- int h = size.height();
- if ((alignment & Qt::AlignVCenter) == Qt::AlignVCenter)
- y += rect.size().height() / 2 - h / 2;
- else if ((alignment & Qt::AlignBottom) == Qt::AlignBottom)
- y += rect.size().height() - h;
- if ((alignment & Qt::AlignRight) == Qt::AlignRight)
- x += rect.size().width() - w;
- else if ((alignment & Qt::AlignHCenter) == Qt::AlignHCenter)
- x += rect.size().width() / 2 - w / 2;
-
- QRect aligned = QRect(x, y, w, h);
- QRect inter = aligned.intersected(rect);
-
- painter->drawPixmap(inter.x(), inter.y(), pixmap, inter.x() - aligned.x(),
- inter.y() - aligned.y(), inter.width() * scale, inter.height() * scale);
-}
-}
+Q_GUI_EXPORT int qt_defaultDpiX();
-QWasmWindow::QWasmWindow(QWindow *w, QWasmCompositor *compositor, QWasmBackingStore *backingStore)
+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();
-
- QPlatformWindow::setGeometry(rect);
-
- const QSize minimumSize = windowMinimumSize();
- if (rect.width() > 0 || rect.height() > 0) {
- rect.setWidth(qBound(1, rect.width(), 2000));
- rect.setHeight(qBound(1, rect.height(), 2000));
- } else if (minimumSize.width() > 0 || minimumSize.height() > 0) {
- rect.setSize(minimumSize);
- }
-
- setWindowState(window()->windowStates());
- setWindowFlags(window()->flags());
- setWindowTitle(window()->title());
- if (window()->isTopLevel())
- setWindowIcon(window()->icon());
- m_normalGeometry = rect;
+ return static_cast<QWasmWindow *>(window->handle());
}
-QWasmScreen *QWasmWindow::platformScreen() const
+void QWasmWindow::onRestoreClicked()
{
- return static_cast<QWasmScreen *>(window()->screen()->handle());
+ window()->setWindowState(Qt::WindowNoState);
}
-void QWasmWindow::setGeometry(const QRect &rect)
+void QWasmWindow::onMaximizeClicked()
{
- const QRect clientAreaRect = ([this, &rect]() {
- if (!m_needsCompositor)
- return rect;
+ window()->setWindowState(Qt::WindowMaximized);
+}
- const int captionHeight = window()->geometry().top() - window()->frameGeometry().top();
- const auto screenGeometry = screen()->geometry();
+void QWasmWindow::onToggleMaximized()
+{
+ window()->setWindowState(m_state.testFlag(Qt::WindowMaximized) ? Qt::WindowNoState
+ : Qt::WindowMaximized);
+}
- QRect result(rect);
- result.moveTop(std::max(std::min(rect.y(), screenGeometry.bottom()),
- screenGeometry.y() + captionHeight));
- return result;
- })();
- bool shouldInvalidate = true;
- if (!m_windowState.testFlag(Qt::WindowFullScreen)
- && !m_windowState.testFlag(Qt::WindowMaximized)) {
- shouldInvalidate = m_normalGeometry.size() != clientAreaRect.size();
- m_normalGeometry = clientAreaRect;
- }
- QWindowSystemInterface::handleGeometryChange(window(), clientAreaRect);
- if (shouldInvalidate)
- invalidate();
- else
- m_compositor->requestUpdateWindow(this);
+void QWasmWindow::onCloseClicked()
+{
+ window()->close();
}
-void QWasmWindow::setVisible(bool visible)
+void QWasmWindow::onNonClientAreaInteraction()
{
- if (visible)
- applyWindowState();
- m_compositor->setVisible(this, visible);
+ requestActivateWindow();
+ QGuiApplicationPrivate::instance()->closeAllPopups();
}
-bool QWasmWindow::isVisible()
+bool QWasmWindow::onNonClientEvent(const PointerEvent &event)
{
- return window()->isVisible();
+ 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);
}
-QMargins QWasmWindow::frameMargins() const
+void QWasmWindow::initialize()
{
- int border = hasTitleBar() ? 4. * (qreal(qt_defaultDpiX()) / 96.0) : 0;
- int titleBarHeight = hasTitleBar() ? titleHeight() : 0;
+ 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()));
- QMargins margins;
- margins.setLeft(border);
- margins.setRight(border);
- margins.setTop(2*border + titleBarHeight);
- margins.setBottom(border);
+ if (window()->isTopLevel())
+ setWindowIcon(window()->icon());
+ QPlatformWindow::setGeometry(m_normalGeometry);
- return margins;
+#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
}
-void QWasmWindow::raise()
+QWasmScreen *QWasmWindow::platformScreen() const
{
- m_compositor->raise(this);
- invalidate();
+ return static_cast<QWasmScreen *>(window()->screen()->handle());
}
-void QWasmWindow::lower()
+void QWasmWindow::paint()
{
- m_compositor->lower(this);
- invalidate();
+ 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));
}
-WId QWasmWindow::winId() const
+void QWasmWindow::setZOrder(int z)
{
- return m_winid;
+ m_qtWindow["style"].set("zIndex", std::to_string(z));
}
-void QWasmWindow::propagateSizeHints()
+void QWasmWindow::setWindowCursor(QByteArray cssCursorName)
{
- QRect rect = windowGeometry();
- if (rect.size().width() < windowMinimumSize().width()
- && rect.size().height() < windowMinimumSize().height()) {
- rect.setSize(windowMinimumSize());
- setGeometry(rect);
- }
+ m_windowContents["style"].set("cursor", emscripten::val(cssCursorName.constData()));
}
-bool QWasmWindow::startSystemResize(Qt::Edges edges)
+void QWasmWindow::setGeometry(const QRect &rect)
{
- m_compositor->startResize(edges);
+ const auto margins = frameMargins();
- return true;
-}
+ 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());
-void QWasmWindow::injectMousePressed(const QPoint &local, const QPoint &global,
- Qt::MouseButton button, Qt::KeyboardModifiers mods)
-{
- Q_UNUSED(local);
- Q_UNUSED(mods);
+ auto offset = rect.topLeft() - (!parent() ? screen()->geometry().topLeft() : QPoint());
- if (!hasTitleBar() || button != Qt::LeftButton)
- return;
+ // In viewport
+ auto containerGeometryInViewport =
+ QRectF::fromDOMRect(parentNode()->containerElement().call<emscripten::val>(
+ "getBoundingClientRect"))
+ .toRect();
- if (const auto controlHit = titleBarHitTest(global))
- m_activeControl = *controlHit;
+ auto rectInViewport = QRect(containerGeometryInViewport.topLeft() + offset, rect.size());
- invalidate();
-}
+ 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());
-void QWasmWindow::injectMouseReleased(const QPoint &local, const QPoint &global,
- Qt::MouseButton button, Qt::KeyboardModifiers mods)
-{
- Q_UNUSED(local);
- Q_UNUSED(mods);
+ const auto frameRect =
+ clientAreaRect
+ .adjusted(-margins.left(), -margins.top(), margins.right(), margins.bottom())
+ .translated(!parent() ? -screen()->geometry().topLeft() : QPoint());
- if (!hasTitleBar() || button != Qt::LeftButton)
- return;
+ 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");
- if (const auto controlHit = titleBarHitTest(global)) {
- if (m_activeControl == *controlHit) {
- switch (*controlHit) {
- case SC_TitleBarCloseButton:
- window()->close();
- break;
- case SC_TitleBarMaxButton:
- window()->setWindowState(Qt::WindowMaximized);
- break;
- case SC_TitleBarNormalButton:
- window()->setWindowState(Qt::WindowNoState);
- break;
- case SC_None:
- case SC_TitleBarLabel:
- case SC_TitleBarSysMenu:
- Q_ASSERT(false); // These types are not clickable
- return;
- }
- }
- }
+ // Important for the title flexbox to shrink correctly
+ m_windowContents["style"].set("width", std::to_string(clientAreaRect.width()) + "px");
- m_activeControl = SC_None;
+ QSizeF canvasSize = clientAreaRect.size() * devicePixelRatio();
- invalidate();
-}
+ m_canvas.set("width", canvasSize.width());
+ m_canvas.set("height", canvasSize.height());
-int QWasmWindow::titleHeight() const
-{
- return 18. * (qreal(qt_defaultDpiX()) / 96.0);//dpiScaled(18.);
+ bool shouldInvalidate = true;
+ if (!m_state.testFlag(Qt::WindowFullScreen) && !m_state.testFlag(Qt::WindowMaximized)) {
+ shouldInvalidate = m_normalGeometry.size() != clientAreaRect.size();
+ m_normalGeometry = clientAreaRect;
+ }
+ QWindowSystemInterface::handleGeometryChange(window(), clientAreaRect);
+ if (shouldInvalidate)
+ invalidate();
+ else
+ m_compositor->requestUpdateWindow(this);
}
-int QWasmWindow::borderWidth() const
+void QWasmWindow::setVisible(bool visible)
{
- return 4. * (qreal(qt_defaultDpiX()) / 96.0);// dpiScaled(4.);
+ // TODO(mikolajboc): isVisible()?
+ const bool nowVisible = m_qtWindow["style"]["display"].as<std::string>() == "block";
+ if (visible == nowVisible)
+ return;
+
+ 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();
}
-QRegion QWasmWindow::resizeRegion() const
+bool QWasmWindow::isVisible() const
{
- int border = borderWidth();
- QRegion result(window()->frameGeometry().adjusted(-border, -border, border, border));
- result -= window()->frameGeometry().adjusted(border, border, -border, -border);
-
- return result;
+ return window()->isVisible();
}
-bool QWasmWindow::isPointOnTitle(QPoint globalPoint) const
+QMargins QWasmWindow::frameMargins() const
{
- const auto pointInFrameCoords = globalPoint - windowFrameGeometry().topLeft();
- if (const auto titleRect =
- getTitleBarControlRect(makeTitleBarOptions(), TitleBarControl::SC_TitleBarLabel)) {
- return titleRect->contains(pointInFrameCoords);
- }
- return false;
+ 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();
}
-bool QWasmWindow::isPointOnResizeRegion(QPoint point) const
+void QWasmWindow::raise()
{
- // Certain windows, like undocked dock widgets, are both popups and dialogs. Those should be
- // resizable.
- if (windowIsPopupType(window()->flags()))
- return false;
- return (window()->maximumSize().isEmpty() || window()->minimumSize() != window()->maximumSize())
- && resizeRegion().contains(point);
+ bringToTop();
+ invalidate();
+ if (QWasmIntegration::get()->inputContext())
+ m_canvas.call<void>("focus");
}
-Qt::Edges QWasmWindow::resizeEdgesAtPoint(QPoint point) const
+void QWasmWindow::lower()
{
- const QPoint topLeft = window()->frameGeometry().topLeft() - QPoint(5, 5);
- const QPoint bottomRight = window()->frameGeometry().bottomRight() + QPoint(5, 5);
- const int gripAreaWidth = std::min(20, (bottomRight.y() - topLeft.y()) / 2);
-
- const QRect top(topLeft, QPoint(bottomRight.x(), topLeft.y() + gripAreaWidth));
- const QRect bottom(QPoint(topLeft.x(), bottomRight.y() - gripAreaWidth), bottomRight);
- const QRect left(topLeft, QPoint(topLeft.x() + gripAreaWidth, bottomRight.y()));
- const QRect right(QPoint(bottomRight.x() - gripAreaWidth, topLeft.y()), bottomRight);
-
- Q_ASSERT(!top.intersects(bottom));
- Q_ASSERT(!left.intersects(right));
-
- Qt::Edges edges(top.contains(point) ? Qt::Edge::TopEdge : Qt::Edge(0));
- edges |= bottom.contains(point) ? Qt::Edge::BottomEdge : Qt::Edge(0);
- edges |= left.contains(point) ? Qt::Edge::LeftEdge : Qt::Edge(0);
- return edges | (right.contains(point) ? Qt::Edge::RightEdge : Qt::Edge(0));
+ sendToBottom();
+ invalidate();
}
-std::optional<QRect> QWasmWindow::getTitleBarControlRect(const TitleBarOptions &tb,
- TitleBarControl control) const
+WId QWasmWindow::winId() const
{
- const auto leftToRightRect = getTitleBarControlRectLeftToRight(tb, control);
- if (!leftToRightRect)
- return std::nullopt;
- return qApp->layoutDirection() == Qt::LeftToRight
- ? leftToRightRect
- : leftToRightRect->translated(2 * (tb.rect.right() - leftToRightRect->right())
- + leftToRightRect->width() - tb.rect.width(),
- 0);
+ return m_winId;
}
-bool QWasmWindow::TitleBarOptions::hasControl(TitleBarControl control) const
+void QWasmWindow::propagateSizeHints()
{
- return subControls.testFlag(control);
+ // setGeometry() will take care of minimum and maximum size constraints
+ setGeometry(windowGeometry());
+ m_nonClientArea->propagateSizeHints();
}
-std::optional<QRect> QWasmWindow::getTitleBarControlRectLeftToRight(const TitleBarOptions &tb,
- TitleBarControl control) const
+void QWasmWindow::setOpacity(qreal level)
{
- if (!tb.hasControl(control))
- return std::nullopt;
-
- const int controlMargin = 2;
- const int controlHeight = tb.rect.height() - controlMargin * 2;
- const int controlWidth = controlHeight;
- const int delta = controlWidth + controlMargin;
- int offsetRight = 0;
-
- switch (control) {
- case SC_TitleBarLabel: {
- const int leftOffset = tb.hasControl(SC_TitleBarSysMenu) ? delta : 0;
- const int rightOffset = (tb.hasControl(SC_TitleBarCloseButton) ? delta : 0)
- + ((tb.hasControl(SC_TitleBarMaxButton) || tb.hasControl(SC_TitleBarNormalButton))
- ? delta
- : 0);
-
- return tb.rect.adjusted(leftOffset, 0, -rightOffset, 0);
- }
- case SC_TitleBarSysMenu:
- return QRect(tb.rect.left() + controlMargin, tb.rect.top() + controlMargin, controlWidth,
- controlHeight);
- case SC_TitleBarCloseButton:
- offsetRight = delta;
- break;
- case SC_TitleBarMaxButton:
- case SC_TitleBarNormalButton:
- offsetRight = delta + (tb.hasControl(SC_TitleBarCloseButton) ? delta : 0);
- break;
- case SC_None:
- Q_ASSERT(false);
- break;
- };
-
- return QRect(tb.rect.right() - offsetRight, tb.rect.top() + controlMargin, controlWidth,
- controlHeight);
+ m_qtWindow["style"].set("opacity", qBound(0.0, level, 1.0));
}
void QWasmWindow::invalidate()
@@ -392,162 +389,170 @@ void QWasmWindow::invalidate()
m_compositor->requestUpdateWindow(this);
}
-QWasmWindow::TitleBarControl QWasmWindow::activeTitleBarControl() const
+void QWasmWindow::onActivationChanged(bool active)
{
- return m_activeControl;
+ dom::syncCSSClassWith(m_qtWindow, "inactive", !active);
}
-std::optional<QWasmWindow::TitleBarControl>
-QWasmWindow::titleBarHitTest(const QPoint &globalPoint) const
+void QWasmWindow::setWindowFlags(Qt::WindowFlags flags)
{
- const auto pointInFrameCoords = globalPoint - windowFrameGeometry().topLeft();
- const auto options = makeTitleBarOptions();
+ 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));
- static constexpr TitleBarControl Controls[] = { SC_TitleBarMaxButton, SC_TitleBarCloseButton,
- SC_TitleBarNormalButton };
- auto found = std::find_if(std::begin(Controls), std::end(Controls),
- [this, &pointInFrameCoords, &options](TitleBarControl control) {
- auto controlRect = getTitleBarControlRect(options, control);
- return controlRect && controlRect->contains(pointInFrameCoords);
- });
- return found != std::end(Controls) ? *found : std::optional<TitleBarControl>();
+ m_nonClientArea->titleBar()->setMaximizeVisible(hasMaximizeButton());
+ m_nonClientArea->titleBar()->setCloseVisible(m_flags.testFlag(Qt::WindowCloseButtonHint));
}
void QWasmWindow::setWindowState(Qt::WindowStates newState)
{
- const Qt::WindowStates oldState = m_windowState;
- bool isActive = oldState.testFlag(Qt::WindowActive);
+ // Child windows can not have window states other than Qt::WindowActive
+ if (parent())
+ newState &= Qt::WindowActive;
+
+ const Qt::WindowStates oldState = m_state;
if (newState.testFlag(Qt::WindowMinimized)) {
newState.setFlag(Qt::WindowMinimized, false);
qWarning("Qt::WindowMinimized is not implemented in wasm");
+ window()->setWindowStates(newState);
+ return;
}
- // 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");
- }
-
- // Ignore WindowActive flag in comparison, as we want to preserve it either way
- if ((newState & ~Qt::WindowActive) == (oldState & ~Qt::WindowActive))
+ if (newState == oldState)
return;
- newState.setFlag(Qt::WindowActive, isActive);
-
+ m_state = newState;
m_previousWindowState = oldState;
- m_windowState = newState;
- if (isVisible()) {
- applyWindowState();
+ applyWindowState();
+}
+
+void QWasmWindow::setWindowTitle(const QString &title)
+{
+ m_nonClientArea->titleBar()->setTitle(title);
+}
+
+void QWasmWindow::setWindowIcon(const QIcon &icon)
+{
+ 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");
}
void QWasmWindow::applyWindowState()
{
QRect newGeom;
- if (m_windowState.testFlag(Qt::WindowFullScreen))
+ const bool isFullscreen = m_state.testFlag(Qt::WindowFullScreen);
+ const bool isMaximized = m_state.testFlag(Qt::WindowMaximized);
+ if (isFullscreen)
newGeom = platformScreen()->geometry();
- else if (m_windowState.testFlag(Qt::WindowMaximized))
- newGeom = platformScreen()->availableGeometry();
+ else if (isMaximized)
+ newGeom = platformScreen()->availableGeometry().marginsRemoved(frameMargins());
else
newGeom = normalGeometry();
- QWindowSystemInterface::handleWindowStateChanged(window(), m_windowState, m_previousWindowState);
+ dom::syncCSSClassWith(m_qtWindow, "has-border", hasBorder());
+ dom::syncCSSClassWith(m_qtWindow, "maximized", isMaximized);
+
+ m_nonClientArea->titleBar()->setRestoreVisible(isMaximized);
+ m_nonClientArea->titleBar()->setMaximizeVisible(hasMaximizeButton());
+
+ if (isVisible())
+ QWindowSystemInterface::handleWindowStateChanged(window(), m_state, m_previousWindowState);
setGeometry(newGeom);
}
-void QWasmWindow::drawTitleBar(QPainter *painter) const
+void QWasmWindow::commitParent(QWasmWindowTreeNode *parent)
{
- const auto tb = makeTitleBarOptions();
- if (const auto ir = getTitleBarControlRect(tb, SC_TitleBarLabel)) {
- QColor left = tb.palette.highlight().color();
- QColor right = tb.palette.base().color();
-
- QBrush fillBrush(left);
- if (left != right) {
- QPoint p1(tb.rect.x(), tb.rect.top() + tb.rect.height() / 2);
- QPoint p2(tb.rect.right(), tb.rect.top() + tb.rect.height() / 2);
- QLinearGradient lg(p1, p2);
- lg.setColorAt(0, left);
- lg.setColorAt(1, right);
- fillBrush = lg;
- }
-
- painter->fillRect(tb.rect, fillBrush);
- painter->setPen(tb.palette.highlightedText().color());
- painter->drawText(ir->x() + 2, ir->y(), ir->width() - 2, ir->height(),
- Qt::AlignLeft | Qt::AlignVCenter | Qt::TextSingleLine,
- tb.titleBarOptionsString);
- }
+ onParentChanged(m_commitedParent, parent, positionPreferenceFromWindowFlags(window()->flags()));
+ m_commitedParent = parent;
+}
- if (const auto ir = getTitleBarControlRect(tb, SC_TitleBarCloseButton)) {
- drawItemPixmap(painter, *ir, Qt::AlignCenter,
- cachedPixmapFromXPM(qt_close_xpm).scaled(QSize(10, 10)));
- }
+bool QWasmWindow::processKey(const KeyEvent &event)
+{
+ constexpr bool ProceedToNativeEvent = false;
+ Q_ASSERT(event.type == EventType::KeyDown || event.type == EventType::KeyUp);
- if (const auto ir = getTitleBarControlRect(tb, SC_TitleBarMaxButton)) {
- drawItemPixmap(painter, *ir, Qt::AlignCenter,
- cachedPixmapFromXPM(qt_maximize_xpm).scaled(QSize(10, 10)));
- }
+ const auto clipboardResult =
+ QWasmIntegration::get()->getWasmClipboard()->processKeyboard(event);
- if (const auto ir = getTitleBarControlRect(tb, SC_TitleBarNormalButton)) {
- drawItemPixmap(painter, *ir, Qt::AlignCenter,
- cachedPixmapFromXPM(qt_normalizeup_xpm).scaled(QSize(10, 10)));
- }
+ using ProcessKeyboardResult = QWasmClipboard::ProcessKeyboardResult;
+ if (clipboardResult == ProcessKeyboardResult::NativeClipboardEventNeeded)
+ return ProceedToNativeEvent;
- if (const auto ir = getTitleBarControlRect(tb, SC_TitleBarSysMenu)) {
- if (!tb.windowIcon.isNull()) {
- tb.windowIcon.paint(painter, *ir, Qt::AlignCenter);
- } else {
- drawItemPixmap(painter, *ir, Qt::AlignCenter,
- cachedPixmapFromXPM(qt_menu_xpm).scaled(QSize(10, 10)));
- }
- }
+ 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;
}
-QWasmWindow::TitleBarOptions QWasmWindow::makeTitleBarOptions() const
+bool QWasmWindow::processPointer(const PointerEvent &event)
{
- int width = windowFrameGeometry().width();
- int border = borderWidth();
-
- TitleBarOptions titleBarOptions;
-
- titleBarOptions.rect = QRect(border, border, width - 2 * border, titleHeight());
- titleBarOptions.flags = window()->flags();
- titleBarOptions.state = window()->windowState();
-
- bool isMaximized =
- titleBarOptions.state & Qt::WindowMaximized; // this gets reset when maximized
+ if (event.pointerType != PointerType::Mouse && event.pointerType != PointerType::Pen)
+ return false;
- if (titleBarOptions.flags & (Qt::WindowTitleHint))
- titleBarOptions.subControls |= SC_TitleBarLabel;
- if (titleBarOptions.flags & Qt::WindowMaximizeButtonHint) {
- if (isMaximized)
- titleBarOptions.subControls |= SC_TitleBarNormalButton;
- else
- titleBarOptions.subControls |= SC_TitleBarMaxButton;
+ 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;
}
- if (titleBarOptions.flags & Qt::WindowSystemMenuHint) {
- titleBarOptions.subControls |= SC_TitleBarCloseButton;
- titleBarOptions.subControls |= SC_TitleBarSysMenu;
+ case EventType::PointerLeave:
+ QWindowSystemInterface::handleLeaveEvent(window());
+ break;
+ default:
+ break;
}
- titleBarOptions.palette = makePalette();
-
- titleBarOptions.palette.setCurrentColorGroup(
- QGuiApplication::focusWindow() == window() ? QPalette::Active : QPalette::Inactive);
-
- if (activeTitleBarControl() != SC_None)
- titleBarOptions.subControls |= activeTitleBarControl();
+ return false;
+}
- if (!window()->title().isEmpty())
- titleBarOptions.titleBarOptionsString = window()->title();
+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;
+ };
+ })();
- titleBarOptions.windowIcon = window()->icon();
+ const auto pointInScreen = platformScreen()->mapFromLocal(
+ dom::mapPoint(event.target(), platformScreen()->element(), event.localPoint));
- return titleBarOptions;
+ 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::normalGeometry() const
@@ -565,19 +570,36 @@ void QWasmWindow::requestUpdate()
m_compositor->requestUpdateWindow(this, QWasmCompositor::UpdateRequestDelivery);
}
+bool QWasmWindow::hasFrame() const
+{
+ return !m_flags.testFlag(Qt::FramelessWindowHint);
+}
+
+bool QWasmWindow::hasBorder() const
+{
+ return hasFrame() && !m_state.testFlag(Qt::WindowFullScreen) && !m_flags.testFlag(Qt::SubWindow)
+ && !windowIsPopupType(m_flags) && !parent();
+}
+
bool QWasmWindow::hasTitleBar() const
{
- Qt::WindowFlags flags = window()->flags();
- return !(m_windowState & Qt::WindowFullScreen)
- && flags.testFlag(Qt::WindowTitleHint)
- && !(windowIsPopupType(flags))
- && m_needsCompositor;
+ return hasBorder() && m_flags.testFlag(Qt::WindowTitleHint);
+}
+
+bool QWasmWindow::hasShadow() const
+{
+ return hasBorder() && !m_flags.testFlag(Qt::NoDropShadowWindowHint);
+}
+
+bool QWasmWindow::hasMaximizeButton() const
+{
+ return !m_state.testFlag(Qt::WindowMaximized) && m_flags.testFlag(Qt::WindowMaximizeButtonHint);
}
bool QWasmWindow::windowIsPopupType(Qt::WindowFlags flags) const
{
if (flags.testFlag(Qt::Tool))
- return false; // Qt::Tool has the Popup bit set but isn't
+ return false; // Qt::Tool has the Popup bit set but isn't an actual Popup window
return (flags.testFlag(Qt::Popup));
}
@@ -590,18 +612,90 @@ void QWasmWindow::requestActivateWindow()
return;
}
- if (window()->isTopLevel())
- raise();
+ raise();
+ setAsActiveNode();
+
+ if (!QWasmIntegration::get()->inputContext())
+ m_canvas.call<void>("focus");
+
QPlatformWindow::requestActivateWindow();
}
bool QWasmWindow::setMouseGrabEnabled(bool grab)
{
- if (grab)
- m_compositor->setCapture(this);
- else
- m_compositor->releaseCapture();
- return true;
+ Q_UNUSED(grab);
+ return false;
+}
+
+bool QWasmWindow::windowEvent(QEvent *event)
+{
+ 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);
+ }
+}
+
+void QWasmWindow::setMask(const QRegion &region)
+{
+ 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()));
+}
+
+void QWasmWindow::setParent(const QPlatformWindow *)
+{
+ commitParent(parentNode());
+}
+
+std::string QWasmWindow::canvasSelector() const
+{
+ return "!qtwindow" + std::to_string(m_winId);
+}
+
+emscripten::val QWasmWindow::containerElement()
+{
+ return m_windowContents;
+}
+
+QWasmWindowTreeNode *QWasmWindow::parentNode()
+{
+ if (parent())
+ return static_cast<QWasmWindow *>(parent());
+ return platformScreen();
+}
+
+QWasmWindow *QWasmWindow::asWasmWindow()
+{
+ return this;
+}
+
+void QWasmWindow::onParentChanged(QWasmWindowTreeNode *previous, QWasmWindowTreeNode *current,
+ QWasmWindowStack::PositionPreference positionPreference)
+{
+ 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