diff options
author | Piotr Wierciński <[email protected]> | 2023-10-24 11:00:10 +0200 |
---|---|---|
committer | Piotr Wierciński <[email protected]> | 2023-11-02 13:56:52 +0200 |
commit | d90692ce96fec6c3e74a609d77691e2304fb0690 (patch) | |
tree | d71b066470718510fac50fec71d45f3ba40627ed | |
parent | 99c0ffb71a41fe5667921e24b019a414d4178d1e (diff) |
wasm: Backport support for child windows
Backport support for child windows on WASM. The main source of changes
is commit fc4fca6d9dc22839ca73898c362faff96c81214c.
Fixes: QTBUG-117897
Change-Id: Iaf1016e32581c2c138d5a4dceb3c050aa88cc636
Reviewed-by: Morten Johan Sørvig <[email protected]>
Reviewed-by: Qt CI Bot <[email protected]>
Reviewed-by: Piotr Wierciński <[email protected]>
-rw-r--r-- | src/plugins/platforms/wasm/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/plugins/platforms/wasm/qwasmcompositor.cpp | 123 | ||||
-rw-r--r-- | src/plugins/platforms/wasm/qwasmcompositor.h | 21 | ||||
-rw-r--r-- | src/plugins/platforms/wasm/qwasmcssstyle.cpp | 7 | ||||
-rw-r--r-- | src/plugins/platforms/wasm/qwasmscreen.cpp | 45 | ||||
-rw-r--r-- | src/plugins/platforms/wasm/qwasmscreen.h | 14 | ||||
-rw-r--r-- | src/plugins/platforms/wasm/qwasmwindow.cpp | 111 | ||||
-rw-r--r-- | src/plugins/platforms/wasm/qwasmwindow.h | 18 | ||||
-rw-r--r-- | src/plugins/platforms/wasm/qwasmwindownonclientarea.cpp | 12 | ||||
-rw-r--r-- | src/plugins/platforms/wasm/qwasmwindowstack.cpp | 142 | ||||
-rw-r--r-- | src/plugins/platforms/wasm/qwasmwindowstack.h | 23 | ||||
-rw-r--r-- | src/plugins/platforms/wasm/qwasmwindowtreenode.cpp | 104 | ||||
-rw-r--r-- | src/plugins/platforms/wasm/qwasmwindowtreenode.h | 53 |
13 files changed, 485 insertions, 189 deletions
diff --git a/src/plugins/platforms/wasm/CMakeLists.txt b/src/plugins/platforms/wasm/CMakeLists.txt index deedf243751..b977322e5db 100644 --- a/src/plugins/platforms/wasm/CMakeLists.txt +++ b/src/plugins/platforms/wasm/CMakeLists.txt @@ -32,6 +32,7 @@ qt_internal_add_plugin(QWasmIntegrationPlugin qwasmstring.cpp qwasmstring.h qwasmtheme.cpp qwasmtheme.h qwasmwindow.cpp qwasmwindow.h + qwasmwindowtreenode.cpp qwasmwindowtreenode.h qwasmwindowclientarea.cpp qwasmwindowclientarea.h qwasmwindownonclientarea.cpp qwasmwindownonclientarea.h qwasminputcontext.cpp qwasminputcontext.h diff --git a/src/plugins/platforms/wasm/qwasmcompositor.cpp b/src/plugins/platforms/wasm/qwasmcompositor.cpp index 7fcdc196a71..1fc71fd65dd 100644 --- a/src/plugins/platforms/wasm/qwasmcompositor.cpp +++ b/src/plugins/platforms/wasm/qwasmcompositor.cpp @@ -21,8 +21,7 @@ using namespace emscripten; Q_GUI_EXPORT int qt_defaultDpiX(); -QWasmCompositor::QWasmCompositor(QWasmScreen *screen) - : QObject(screen), m_windowStack(std::bind(&QWasmCompositor::onTopWindowChanged, this)) +QWasmCompositor::QWasmCompositor(QWasmScreen *screen) : QObject(screen) { QWindowSystemInterface::setSynchronousWindowSystemEvents(true); } @@ -35,6 +34,22 @@ QWasmCompositor::~QWasmCompositor() destroy(); } +void QWasmCompositor::onWindowTreeChanged(QWasmWindowTreeNodeChangeType changeType, + QWasmWindow *window) +{ + auto allWindows = screen()->allWindows(); + setEnabled(std::any_of(allWindows.begin(), allWindows.end(), [](QWasmWindow *element) { + return !element->context2d().isUndefined(); + })); + if (changeType == QWasmWindowTreeNodeChangeType::NodeRemoval) + m_requestUpdateWindows.remove(window); +} + +void QWasmCompositor::setEnabled(bool enabled) +{ + m_isEnabled = enabled; +} + void QWasmCompositor::onScreenDeleting() { deregisterEventHandlers(); @@ -58,59 +73,6 @@ void QWasmCompositor::destroy() m_isEnabled = false; // prevent frame() from creating a new m_context } -void QWasmCompositor::addWindow(QWasmWindow *window) -{ - m_windowStack.pushWindow(window); - m_windowStack.topWindow()->requestActivateWindow(); - - updateEnabledState(); -} - -void QWasmCompositor::removeWindow(QWasmWindow *window) -{ - m_requestUpdateWindows.remove(window); - m_windowStack.removeWindow(window); - if (m_windowStack.topWindow()) - m_windowStack.topWindow()->requestActivateWindow(); - - updateEnabledState(); -} - -void QWasmCompositor::updateEnabledState() -{ - m_isEnabled = std::any_of(m_windowStack.begin(), m_windowStack.end(), [](QWasmWindow *window) { - return !window->context2d().isUndefined(); - }); -} - -void QWasmCompositor::raise(QWasmWindow *window) -{ - m_windowStack.raise(window); -} - -void QWasmCompositor::lower(QWasmWindow *window) -{ - m_windowStack.lower(window); -} - -QWindow *QWasmCompositor::windowAt(QPoint targetPointInScreenCoords, int padding) const -{ - const auto found = std::find_if( - m_windowStack.begin(), m_windowStack.end(), - [padding, &targetPointInScreenCoords](const QWasmWindow *window) { - const QRect geometry = window->windowFrameGeometry().adjusted(-padding, -padding, - padding, padding); - - return window->isVisible() && geometry.contains(targetPointInScreenCoords); - }); - return found != m_windowStack.end() ? (*found)->window() : nullptr; -} - -QWindow *QWasmCompositor::keyWindow() const -{ - return m_windowStack.topWindow() ? m_windowStack.topWindow()->window() : nullptr; -} - void QWasmCompositor::requestUpdateAllWindows() { m_requestUpdateAllWindows = true; @@ -158,29 +120,19 @@ void QWasmCompositor::deliverUpdateRequests() // update set. auto requestUpdateWindows = m_requestUpdateWindows; m_requestUpdateWindows.clear(); - bool requestUpdateAllWindows = m_requestUpdateAllWindows; - m_requestUpdateAllWindows = false; // Update window content, either all windows or a spesific set of windows. Use the correct // update type: QWindow subclasses expect that requested and delivered updateRequests matches // exactly. m_inDeliverUpdateRequest = true; - if (requestUpdateAllWindows) { - for (QWasmWindow *window : m_windowStack) { - auto it = requestUpdateWindows.find(window); - UpdateRequestDeliveryType updateType = - (it == m_requestUpdateWindows.end() ? ExposeEventDelivery : it.value()); - deliverUpdateRequest(window, updateType); - } - } else { - for (auto it = requestUpdateWindows.constBegin(); it != requestUpdateWindows.constEnd(); ++it) { - auto *window = it.key(); - UpdateRequestDeliveryType updateType = it.value(); - deliverUpdateRequest(window, updateType); - } + for (auto it = requestUpdateWindows.constBegin(); it != requestUpdateWindows.constEnd(); ++it) { + auto *window = it.key(); + UpdateRequestDeliveryType updateType = it.value(); + deliverUpdateRequest(window, updateType); } + m_inDeliverUpdateRequest = false; - frame(requestUpdateAllWindows, requestUpdateWindows.keys()); + frame(requestUpdateWindows.keys()); } void QWasmCompositor::deliverUpdateRequest(QWasmWindow *window, UpdateRequestDeliveryType updateType) @@ -213,36 +165,15 @@ int dpiScaled(qreal value) return value * (qreal(qt_defaultDpiX()) / 96.0); } -void QWasmCompositor::frame(bool all, const QList<QWasmWindow *> &windows) +void QWasmCompositor::frame(const QList<QWasmWindow *> &windows) { - if (!m_isEnabled || m_windowStack.empty() || !screen()) + if (!m_isEnabled || !screen()) return; - if (all) { - std::for_each(m_windowStack.rbegin(), m_windowStack.rend(), - [](QWasmWindow *window) { window->paint(); }); - } else { - std::for_each(windows.begin(), windows.end(), [](QWasmWindow *window) { window->paint(); }); - } + for (QWasmWindow *window : windows) + window->paint(); } -void QWasmCompositor::onTopWindowChanged() -{ - constexpr int zOrderForElementInFrontOfScreen = 3; - int z = zOrderForElementInFrontOfScreen; - std::for_each(m_windowStack.rbegin(), m_windowStack.rend(), - [&z](QWasmWindow *window) { window->setZOrder(z++); }); - - auto it = m_windowStack.begin(); - if (it == m_windowStack.end()) { - return; - } - (*it)->onActivationChanged(true); - ++it; - for (; it != m_windowStack.end(); ++it) { - (*it)->onActivationChanged(false); - } -} QWasmScreen *QWasmCompositor::screen() { diff --git a/src/plugins/platforms/wasm/qwasmcompositor.h b/src/plugins/platforms/wasm/qwasmcompositor.h index 5b8f22aaac3..055e83ab019 100644 --- a/src/plugins/platforms/wasm/qwasmcompositor.h +++ b/src/plugins/platforms/wasm/qwasmcompositor.h @@ -23,6 +23,8 @@ class QWasmScreen; class QOpenGLContext; class QOpenGLTexture; +enum class QWasmWindowTreeNodeChangeType; + class QWasmCompositor final : public QObject { Q_OBJECT @@ -30,30 +32,21 @@ public: QWasmCompositor(QWasmScreen *screen); ~QWasmCompositor() final; - void addWindow(QWasmWindow *window); - void removeWindow(QWasmWindow *window); - void setVisible(QWasmWindow *window, bool visible); - void raise(QWasmWindow *window); - void lower(QWasmWindow *window); - void onScreenDeleting(); - QWindow *windowAt(QPoint globalPoint, int padding = 0) const; - QWindow *keyWindow() const; - QWasmScreen *screen(); + void setEnabled(bool enabled); enum UpdateRequestDeliveryType { ExposeEventDelivery, UpdateRequestDelivery }; void requestUpdateAllWindows(); void requestUpdateWindow(QWasmWindow *window, UpdateRequestDeliveryType updateType = ExposeEventDelivery); void handleBackingStoreFlush(QWindow *window); + void onWindowTreeChanged(QWasmWindowTreeNodeChangeType changeType, QWasmWindow *window); private: - void frame(bool all, const QList<QWasmWindow *> &windows); - - void onTopWindowChanged(); + void frame(const QList<QWasmWindow *> &windows); void deregisterEventHandlers(); void destroy(); @@ -66,10 +59,6 @@ private: bool processTouch(int eventType, const EmscriptenTouchEvent *touchEvent); - void updateEnabledState(); - - QWasmWindowStack m_windowStack; - bool m_isEnabled = true; QMap<QWasmWindow *, UpdateRequestDeliveryType> m_requestUpdateWindows; bool m_requestUpdateAllWindows = false; diff --git a/src/plugins/platforms/wasm/qwasmcssstyle.cpp b/src/plugins/platforms/wasm/qwasmcssstyle.cpp index c0873880b6d..56c5fbc7922 100644 --- a/src/plugins/platforms/wasm/qwasmcssstyle.cpp +++ b/src/plugins/platforms/wasm/qwasmcssstyle.cpp @@ -35,6 +35,11 @@ const char *Style = R"css( background-color: lightgray; } +.qt-window-contents { + overflow: hidden; + position: relative; +} + .qt-window.transparent-for-input { pointer-events: none; } @@ -133,7 +138,7 @@ const char *Style = R"css( padding-bottom: 4px; } -.qt-window.has-border .title-bar { +.qt-window.has-border > .title-bar { display: flex; } diff --git a/src/plugins/platforms/wasm/qwasmscreen.cpp b/src/plugins/platforms/wasm/qwasmscreen.cpp index 20dbbebc5d9..0a99b46af84 100644 --- a/src/plugins/platforms/wasm/qwasmscreen.cpp +++ b/src/plugins/platforms/wasm/qwasmscreen.cpp @@ -201,12 +201,18 @@ void QWasmScreen::resizeMaximizedWindows() QWindow *QWasmScreen::topWindow() const { - return m_compositor->keyWindow(); + return activeChild() ? activeChild()->window() : nullptr; } QWindow *QWasmScreen::topLevelAt(const QPoint &p) const { - return m_compositor->windowAt(p); + const auto found = + std::find_if(childStack().begin(), childStack().end(), [&p](const QWasmWindow *window) { + const QRect geometry = window->windowFrameGeometry(); + + return window->isVisible() && geometry.contains(p); + }); + return found != childStack().end() ? (*found)->window() : nullptr; } QPointF QWasmScreen::mapFromLocal(const QPointF &p) const @@ -234,6 +240,18 @@ void QWasmScreen::setGeometry(const QRect &rect) resizeMaximizedWindows(); } +void QWasmScreen::onSubtreeChanged(QWasmWindowTreeNodeChangeType changeType, + QWasmWindowTreeNode *parent, QWasmWindow *child) +{ + Q_UNUSED(parent); + if (changeType == QWasmWindowTreeNodeChangeType::NodeInsertion && parent == this + && childStack().size() == 1) { + child->window()->setFlag(Qt::WindowStaysOnBottomHint); + } + QWasmWindowTreeNode::onSubtreeChanged(changeType, parent, child); + m_compositor->onWindowTreeChanged(changeType, child); +} + void QWasmScreen::updateQScreenAndCanvasRenderSize() { // The HTML canvas has two sizes: the CSS size and the canvas render size. @@ -308,4 +326,27 @@ void QWasmScreen::installCanvasResizeObserver() resizeObserver.call<void>("observe", m_shadowContainer); } +emscripten::val QWasmScreen::containerElement() +{ + return m_shadowContainer; +} + +QWasmWindowTreeNode *QWasmScreen::parentNode() +{ + return nullptr; +} + +QList<QWasmWindow *> QWasmScreen::allWindows() +{ + QList<QWasmWindow *> windows; + for (auto *child : childStack()) { + QWindowList list = child->window()->findChildren<QWindow *>(Qt::FindChildrenRecursively); + std::transform( + list.begin(), list.end(), std::back_inserter(windows), + [](const QWindow *window) { return static_cast<QWasmWindow *>(window->handle()); }); + windows.push_back(child); + } + return windows; +} + QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmscreen.h b/src/plugins/platforms/wasm/qwasmscreen.h index 633cf853f7e..47ce11495f2 100644 --- a/src/plugins/platforms/wasm/qwasmscreen.h +++ b/src/plugins/platforms/wasm/qwasmscreen.h @@ -6,6 +6,8 @@ #include "qwasmcursor.h" +#include "qwasmwindowtreenode.h" + #include <qpa/qplatformscreen.h> #include <QtCore/qscopedpointer.h> @@ -23,7 +25,7 @@ class QWasmCompositor; class QWasmDeadKeySupport; class QOpenGLContext; -class QWasmScreen : public QObject, public QPlatformScreen +class QWasmScreen : public QObject, public QPlatformScreen, public QWasmWindowTreeNode { Q_OBJECT public: @@ -41,6 +43,8 @@ public: QWasmCompositor *compositor(); QWasmDeadKeySupport *deadKeySupport() { return m_deadKeySupport.get(); } + QList<QWasmWindow *> allWindows(); + QRect geometry() const override; int depth() const override; QImage::Format format() const override; @@ -53,6 +57,10 @@ public: QWindow *topWindow() const; QWindow *topLevelAt(const QPoint &p) const override; + // QWasmWindowTreeNode: + emscripten::val containerElement() final; + QWasmWindowTreeNode *parentNode() final; + QPointF mapFromLocal(const QPointF &p) const; QPointF clipPoint(const QPointF &p) const; @@ -65,6 +73,10 @@ public slots: void setGeometry(const QRect &rect); private: + // QWasmWindowTreeNode: + void onSubtreeChanged(QWasmWindowTreeNodeChangeType changeType, QWasmWindowTreeNode *parent, + QWasmWindow *child) final; + emscripten::val m_container; emscripten::val m_shadowContainer; std::unique_ptr<QWasmCompositor> m_compositor; diff --git a/src/plugins/platforms/wasm/qwasmwindow.cpp b/src/plugins/platforms/wasm/qwasmwindow.cpp index 11513fdd8b2..d655f272205 100644 --- a/src/plugins/platforms/wasm/qwasmwindow.cpp +++ b/src/plugins/platforms/wasm/qwasmwindow.cpp @@ -33,6 +33,17 @@ QT_BEGIN_NAMESPACE +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, @@ -57,6 +68,7 @@ QWasmWindow::QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport, 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")); @@ -83,8 +95,6 @@ QWasmWindow::QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport, m_canvasContainer.call<void>("appendChild", m_a11yContainer); m_a11yContainer["classList"].call<void>("add", emscripten::val("qt-window-a11y-container")); - compositor->screen()->element().call<void>("appendChild", m_qtWindow); - const bool rendersTo2dContext = w->surfaceType() != QSurface::OpenGLSurface; if (rendersTo2dContext) m_context2d = m_canvas.call<emscripten::val>("getContext", emscripten::val("2d")); @@ -93,8 +103,6 @@ QWasmWindow::QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport, m_qtWindow.set("id", "qt-window-" + std::to_string(m_winId)); emscripten::val::module_property("specialHTMLTargets").set(canvasSelector(), m_canvas); - m_compositor->addWindow(this); - const auto pointerCallback = std::function([this](emscripten::val event) { if (processPointer(*PointerEvent::fromWeb(event))) event.call<void>("preventDefault"); @@ -125,13 +133,16 @@ QWasmWindow::QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport, m_keyDownCallback = std::make_unique<qstdweb::EventCallback>(m_qtWindow, "keydown", keyCallback); m_keyUpCallback = std::make_unique<qstdweb::EventCallback>(m_qtWindow, "keyup", keyCallback); + + setParent(parent()); } QWasmWindow::~QWasmWindow() { emscripten::val::module_property("specialHTMLTargets").delete_(canvasSelector()); - destroy(); - m_compositor->removeWindow(this); + 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) @@ -167,8 +178,7 @@ void QWasmWindow::onCloseClicked() void QWasmWindow::onNonClientAreaInteraction() { - if (!isActive()) - requestActivateWindow(); + requestActivateWindow(); } bool QWasmWindow::onNonClientEvent(const PointerEvent &event) @@ -182,14 +192,6 @@ bool QWasmWindow::onNonClientEvent(const PointerEvent &event) event.modifiers); } -void QWasmWindow::destroy() -{ - m_qtWindow["parentElement"].call<emscripten::val>("removeChild", m_qtWindow); - - m_canvasContainer.call<void>("removeChild", m_canvas); - m_context2d = emscripten::val::undefined(); -} - void QWasmWindow::initialize() { QRect rect = windowGeometry(); @@ -262,21 +264,31 @@ void QWasmWindow::setGeometry(const QRect &rect) if (m_state.testFlag(Qt::WindowMaximized)) return platformScreen()->availableGeometry().marginsRemoved(frameMargins()); - const auto screenGeometry = screen()->geometry(); + auto offset = rect.topLeft() - (!parent() ? screen()->geometry().topLeft() : QPoint()); + + // In viewport + auto containerGeometryInViewport = + QRectF::fromDOMRect(parentNode()->containerElement().call<emscripten::val>( + "getBoundingClientRect")) + .toRect(); - QRect result(rect); - result.moveTop(std::max(std::min(rect.y(), screenGeometry.bottom()), - screenGeometry.y() + margins.top())); - result.setSize( - result.size().expandedTo(windowMinimumSize()).boundedTo(windowMaximumSize())); - return result; + auto rectInViewport = QRect(containerGeometryInViewport.topLeft() + offset, rect.size()); + + QRect cappedGeometry(rectInViewport); + 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(-screen()->geometry().topLeft()); + .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"); @@ -337,13 +349,13 @@ QMargins QWasmWindow::frameMargins() const void QWasmWindow::raise() { - m_compositor->raise(this); + bringToTop(); invalidate(); } void QWasmWindow::lower() { - m_compositor->lower(this); + sendToBottom(); invalidate(); } @@ -374,6 +386,11 @@ void QWasmWindow::onActivationChanged(bool active) 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()); @@ -455,6 +472,12 @@ void QWasmWindow::applyWindowState() setGeometry(newGeom); } +void QWasmWindow::commitParent(QWasmWindowTreeNode *parent) +{ + onParentChanged(m_commitedParent, parent, positionPreferenceFromWindowFlags(window()->flags())); + m_commitedParent = parent; +} + bool QWasmWindow::processKey(const KeyEvent &event) { constexpr bool ProceedToNativeEvent = false; @@ -603,8 +626,8 @@ void QWasmWindow::requestActivateWindow() return; } - if (window()->isTopLevel()) - raise(); + raise(); + setAsActiveNode(); if (!QWasmIntegration::get()->inputContext()) m_canvas.call<void>("focus"); @@ -652,9 +675,41 @@ void QWasmWindow::setMask(const QRegion ®ion) 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 diff --git a/src/plugins/platforms/wasm/qwasmwindow.h b/src/plugins/platforms/wasm/qwasmwindow.h index 7db125f48e8..bfeb46972ad 100644 --- a/src/plugins/platforms/wasm/qwasmwindow.h +++ b/src/plugins/platforms/wasm/qwasmwindow.h @@ -11,6 +11,8 @@ #include "qwasmscreen.h" #include "qwasmcompositor.h" #include "qwasmwindownonclientarea.h" +#include "qwasmwindowstack.h" +#include "qwasmwindowtreenode.h" #include <QtCore/private/qstdweb_p.h> #include "QtGui/qopenglcontext.h" @@ -37,7 +39,7 @@ struct PointerEvent; class QWasmDeadKeySupport; struct WheelEvent; -class QWasmWindow final : public QPlatformWindow +class QWasmWindow final : public QPlatformWindow, public QWasmWindowTreeNode { public: QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport, QWasmCompositor *compositor, @@ -46,7 +48,6 @@ public: QSurfaceFormat format() const override; - void destroy(); void paint(); void setZOrder(int order); void setWindowCursor(QByteArray cssCursorName); @@ -81,6 +82,7 @@ public: bool setMouseGrabEnabled(bool grab) final; bool windowEvent(QEvent *event) final; void setMask(const QRegion ®ion) final; + void setParent(const QPlatformWindow *window) final; QWasmScreen *platformScreen() const; void setBackingStore(QWasmBackingStore *store) { m_backingStore = store; } @@ -92,10 +94,19 @@ public: emscripten::val a11yContainer() const { return m_a11yContainer; } emscripten::val inputHandlerElement() const { return m_windowContents; } + // QWasmWindowTreeNode: + emscripten::val containerElement() final; + QWasmWindowTreeNode *parentNode() final; + private: friend class QWasmScreen; static constexpr auto minSizeForRegularWindows = 100; + // QWasmWindowTreeNode: + QWasmWindow *asWasmWindow() final; + void onParentChanged(QWasmWindowTreeNode *previous, QWasmWindowTreeNode *current, + QWasmWindowStack::PositionPreference positionPreference) final; + void invalidate(); bool hasFrame() const; bool hasTitleBar() const; @@ -103,6 +114,7 @@ private: bool hasShadow() const; bool hasMaximizeButton() const; void applyWindowState(); + void commitParent(QWasmWindowTreeNode *parent); bool processKey(const KeyEvent &event); bool processPointer(const PointerEvent &event); @@ -126,6 +138,8 @@ private: std::unique_ptr<NonClientArea> m_nonClientArea; std::unique_ptr<ClientArea> m_clientArea; + QWasmWindowTreeNode *m_commitedParent = nullptr; + std::unique_ptr<qstdweb::EventCallback> m_keyDownCallback; std::unique_ptr<qstdweb::EventCallback> m_keyUpCallback; diff --git a/src/plugins/platforms/wasm/qwasmwindownonclientarea.cpp b/src/plugins/platforms/wasm/qwasmwindownonclientarea.cpp index 68f14425109..07c90d5fe92 100644 --- a/src/plugins/platforms/wasm/qwasmwindownonclientarea.cpp +++ b/src/plugins/platforms/wasm/qwasmwindownonclientarea.cpp @@ -408,9 +408,15 @@ bool TitleBar::onDoubleClick() QPointF TitleBar::clipPointWithScreen(const QPointF &pointInTitleBarCoords) const { - auto *screen = m_window->platformScreen(); - return screen->clipPoint(screen->mapFromLocal( - dom::mapPoint(m_element, screen->element(), pointInTitleBarCoords))); + auto containerRect = + QRectF::fromDOMRect(m_window->parentNode()->containerElement().call<emscripten::val>( + "getBoundingClientRect")); + const auto p = dom::mapPoint(m_element, m_window->parentNode()->containerElement(), + pointInTitleBarCoords); + + auto result = QPointF(qBound(0., qreal(p.x()), containerRect.width()), + qBound(0., qreal(p.y()), containerRect.height())); + return m_window->parent() ? result : m_window->platformScreen()->mapFromLocal(result).toPoint(); } NonClientArea::NonClientArea(QWasmWindow *window, emscripten::val qtWindowElement) diff --git a/src/plugins/platforms/wasm/qwasmwindowstack.cpp b/src/plugins/platforms/wasm/qwasmwindowstack.cpp index 098f1c1ff20..d3769c7a1bb 100644 --- a/src/plugins/platforms/wasm/qwasmwindowstack.cpp +++ b/src/plugins/platforms/wasm/qwasmwindowstack.cpp @@ -5,20 +5,38 @@ QT_BEGIN_NAMESPACE -QWasmWindowStack::QWasmWindowStack(TopWindowChangedCallbackType topWindowChangedCallback) - : m_topWindowChangedCallback(std::move(topWindowChangedCallback)) +QWasmWindowStack::QWasmWindowStack(WindowOrderChangedCallbackType windowOrderChangedCallback) + : m_windowOrderChangedCallback(std::move(windowOrderChangedCallback)), + m_regularWindowsBegin(m_windowStack.begin()), + m_alwaysOnTopWindowsBegin(m_windowStack.begin()) { } QWasmWindowStack::~QWasmWindowStack() = default; -void QWasmWindowStack::pushWindow(QWasmWindow *window) +void QWasmWindowStack::pushWindow(QWasmWindow *window, PositionPreference position) { Q_ASSERT(m_windowStack.count(window) == 0); - m_windowStack.push_back(window); - - m_topWindowChangedCallback(); + if (position == PositionPreference::StayOnTop) { + const auto stayOnTopDistance = + std::distance(m_windowStack.begin(), m_alwaysOnTopWindowsBegin); + const auto regularDistance = std::distance(m_windowStack.begin(), m_regularWindowsBegin); + m_windowStack.push_back(window); + m_alwaysOnTopWindowsBegin = m_windowStack.begin() + stayOnTopDistance; + m_regularWindowsBegin = m_windowStack.begin() + regularDistance; + } else if (position == PositionPreference::Regular) { + const auto regularDistance = std::distance(m_windowStack.begin(), m_regularWindowsBegin); + m_alwaysOnTopWindowsBegin = m_windowStack.insert(m_alwaysOnTopWindowsBegin, window) + 1; + m_regularWindowsBegin = m_windowStack.begin() + regularDistance; + } else { + const auto stayOnTopDistance = + std::distance(m_windowStack.begin(), m_alwaysOnTopWindowsBegin); + m_regularWindowsBegin = m_windowStack.insert(m_regularWindowsBegin, window) + 1; + m_alwaysOnTopWindowsBegin = m_windowStack.begin() + stayOnTopDistance + 1; + } + + m_windowOrderChangedCallback(); } void QWasmWindowStack::removeWindow(QWasmWindow *window) @@ -26,41 +44,105 @@ void QWasmWindowStack::removeWindow(QWasmWindow *window) Q_ASSERT(m_windowStack.count(window) == 1); auto it = std::find(m_windowStack.begin(), m_windowStack.end(), window); - const bool removingBottom = m_windowStack.begin() == it; - const bool removingTop = m_windowStack.end() - 1 == it; - if (removingBottom) - m_firstWindowTreatment = FirstWindowTreatment::Regular; + const auto position = getWindowPositionPreference(it); + const auto stayOnTopDistance = std::distance(m_windowStack.begin(), m_alwaysOnTopWindowsBegin); + const auto regularDistance = std::distance(m_windowStack.begin(), m_regularWindowsBegin); m_windowStack.erase(it); - if (removingTop) - m_topWindowChangedCallback(); + m_alwaysOnTopWindowsBegin = m_windowStack.begin() + stayOnTopDistance + - (position != PositionPreference::StayOnTop ? 1 : 0); + m_regularWindowsBegin = m_windowStack.begin() + regularDistance + - (position == PositionPreference::StayOnBottom ? 1 : 0); + + m_windowOrderChangedCallback(); } void QWasmWindowStack::raise(QWasmWindow *window) { Q_ASSERT(m_windowStack.count(window) == 1); - if (window == rootWindow() || window == topWindow()) + if (window == topWindow()) return; - auto it = std::find(regularWindowsBegin(), m_windowStack.end(), window); - std::rotate(it, it + 1, m_windowStack.end()); - m_topWindowChangedCallback(); + auto it = std::find(m_windowStack.begin(), m_windowStack.end(), window); + auto itEnd = ([this, position = getWindowPositionPreference(it)]() { + switch (position) { + case PositionPreference::StayOnTop: + return m_windowStack.end(); + case PositionPreference::Regular: + return m_alwaysOnTopWindowsBegin; + case PositionPreference::StayOnBottom: + return m_regularWindowsBegin; + } + })(); + std::rotate(it, it + 1, itEnd); + m_windowOrderChangedCallback(); } void QWasmWindowStack::lower(QWasmWindow *window) { Q_ASSERT(m_windowStack.count(window) == 1); - if (window == rootWindow()) + if (window == *m_windowStack.begin()) return; - const bool loweringTopWindow = topWindow() == window; - auto it = std::find(regularWindowsBegin(), m_windowStack.end(), window); - std::rotate(regularWindowsBegin(), it, it + 1); - if (loweringTopWindow && topWindow() != window) - m_topWindowChangedCallback(); + auto it = std::find(m_windowStack.begin(), m_windowStack.end(), window); + auto itBegin = ([this, position = getWindowPositionPreference(it)]() { + switch (position) { + case PositionPreference::StayOnTop: + return m_alwaysOnTopWindowsBegin; + case PositionPreference::Regular: + return m_regularWindowsBegin; + case PositionPreference::StayOnBottom: + return m_windowStack.begin(); + } + })(); + + std::rotate(itBegin, it, it + 1); + m_windowOrderChangedCallback(); +} + +void QWasmWindowStack::windowPositionPreferenceChanged(QWasmWindow *window, + PositionPreference position) +{ + auto it = std::find(m_windowStack.begin(), m_windowStack.end(), window); + const auto currentPosition = getWindowPositionPreference(it); + + const auto zones = static_cast<int>(position) - static_cast<int>(currentPosition); + Q_ASSERT(zones != 0); + + if (zones < 0) { + // Perform right rotation so that the window lands on top of regular windows + const auto begin = std::make_reverse_iterator(it + 1); + const auto end = position == PositionPreference::Regular + ? std::make_reverse_iterator(m_alwaysOnTopWindowsBegin) + : std::make_reverse_iterator(m_regularWindowsBegin); + std::rotate(begin, begin + 1, end); + if (zones == -2) { + ++m_alwaysOnTopWindowsBegin; + ++m_regularWindowsBegin; + } else if (position == PositionPreference::Regular) { + ++m_alwaysOnTopWindowsBegin; + } else { + ++m_regularWindowsBegin; + } + } else { + // Perform left rotation so that the window lands at the bottom of always on top windows + const auto begin = it; + const auto end = position == PositionPreference::Regular ? m_regularWindowsBegin + : m_alwaysOnTopWindowsBegin; + std::rotate(begin, begin + 1, end); + if (zones == 2) { + --m_alwaysOnTopWindowsBegin; + --m_regularWindowsBegin; + } else if (position == PositionPreference::Regular) { + --m_regularWindowsBegin; + } else { + --m_alwaysOnTopWindowsBegin; + } + } + m_windowOrderChangedCallback(); } QWasmWindowStack::iterator QWasmWindowStack::begin() @@ -103,21 +185,19 @@ size_t QWasmWindowStack::size() const return m_windowStack.size(); } -QWasmWindow *QWasmWindowStack::rootWindow() const -{ - return m_firstWindowTreatment == FirstWindowTreatment::AlwaysAtBottom ? m_windowStack.first() - : nullptr; -} - QWasmWindow *QWasmWindowStack::topWindow() const { return m_windowStack.empty() ? nullptr : m_windowStack.last(); } -QWasmWindowStack::StorageType::iterator QWasmWindowStack::regularWindowsBegin() +QWasmWindowStack::PositionPreference +QWasmWindowStack::getWindowPositionPreference(StorageType::iterator windowIt) const { - return m_windowStack.begin() - + (m_firstWindowTreatment == FirstWindowTreatment::AlwaysAtBottom ? 1 : 0); + if (windowIt >= m_alwaysOnTopWindowsBegin) + return PositionPreference::StayOnTop; + if (windowIt >= m_regularWindowsBegin) + return PositionPreference::Regular; + return PositionPreference::StayOnBottom; } QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmwindowstack.h b/src/plugins/platforms/wasm/qwasmwindowstack.h index e98ebf904c2..32090b9039f 100644 --- a/src/plugins/platforms/wasm/qwasmwindowstack.h +++ b/src/plugins/platforms/wasm/qwasmwindowstack.h @@ -24,7 +24,7 @@ class QWasmWindow; Q_AUTOTEST_EXPORT class QWasmWindowStack { public: - using TopWindowChangedCallbackType = std::function<void()>; + using WindowOrderChangedCallbackType = std::function<void()>; using StorageType = QList<QWasmWindow *>; @@ -32,13 +32,20 @@ public: using const_iterator = StorageType::const_reverse_iterator; using const_reverse_iterator = StorageType::const_iterator; - explicit QWasmWindowStack(TopWindowChangedCallbackType topWindowChangedCallback); + enum class PositionPreference { + StayOnBottom, + Regular, + StayOnTop, + }; + + explicit QWasmWindowStack(WindowOrderChangedCallbackType topWindowChangedCallback); ~QWasmWindowStack(); - void pushWindow(QWasmWindow *window); + void pushWindow(QWasmWindow *window, PositionPreference position); void removeWindow(QWasmWindow *window); void raise(QWasmWindow *window); void lower(QWasmWindow *window); + void windowPositionPreferenceChanged(QWasmWindow *window, PositionPreference position); // Iterates top-to-bottom iterator begin(); @@ -55,14 +62,12 @@ public: QWasmWindow *topWindow() const; private: - enum class FirstWindowTreatment { AlwaysAtBottom, Regular }; - - QWasmWindow *rootWindow() const; - StorageType::iterator regularWindowsBegin(); + PositionPreference getWindowPositionPreference(StorageType::iterator windowIt) const; - TopWindowChangedCallbackType m_topWindowChangedCallback; + WindowOrderChangedCallbackType m_windowOrderChangedCallback; QList<QWasmWindow *> m_windowStack; - FirstWindowTreatment m_firstWindowTreatment = FirstWindowTreatment::AlwaysAtBottom; + StorageType::iterator m_regularWindowsBegin; + StorageType::iterator m_alwaysOnTopWindowsBegin; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmwindowtreenode.cpp b/src/plugins/platforms/wasm/qwasmwindowtreenode.cpp new file mode 100644 index 00000000000..e16410dcde2 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmwindowtreenode.cpp @@ -0,0 +1,104 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "qwasmwindowtreenode.h" + +#include "qwasmwindow.h" + +QWasmWindowTreeNode::QWasmWindowTreeNode() + : m_childStack(std::bind(&QWasmWindowTreeNode::onTopWindowChanged, this)) +{ +} + +QWasmWindowTreeNode::~QWasmWindowTreeNode() = default; + +void QWasmWindowTreeNode::onParentChanged(QWasmWindowTreeNode *previousParent, + QWasmWindowTreeNode *currentParent, + QWasmWindowStack::PositionPreference positionPreference) +{ + auto *window = asWasmWindow(); + if (previousParent) { + previousParent->m_childStack.removeWindow(window); + previousParent->onSubtreeChanged(QWasmWindowTreeNodeChangeType::NodeRemoval, previousParent, + window); + } + + if (currentParent) { + currentParent->m_childStack.pushWindow(window, positionPreference); + currentParent->onSubtreeChanged(QWasmWindowTreeNodeChangeType::NodeInsertion, currentParent, + window); + } +} + +QWasmWindow *QWasmWindowTreeNode::asWasmWindow() +{ + return nullptr; +} + +void QWasmWindowTreeNode::onSubtreeChanged(QWasmWindowTreeNodeChangeType changeType, + QWasmWindowTreeNode *parent, QWasmWindow *child) +{ + if (changeType == QWasmWindowTreeNodeChangeType::NodeInsertion && parent == this + && m_childStack.topWindow()) { + m_childStack.topWindow()->requestActivateWindow(); + } + + if (parentNode()) + parentNode()->onSubtreeChanged(changeType, parent, child); +} + +void QWasmWindowTreeNode::setWindowZOrder(QWasmWindow *window, int z) +{ + window->setZOrder(z); +} + +void QWasmWindowTreeNode::onPositionPreferenceChanged( + QWasmWindowStack::PositionPreference positionPreference) +{ + if (parentNode()) { + parentNode()->m_childStack.windowPositionPreferenceChanged(asWasmWindow(), + positionPreference); + } +} + +void QWasmWindowTreeNode::setAsActiveNode() +{ + if (parentNode()) + parentNode()->setActiveChildNode(asWasmWindow()); +} + +void QWasmWindowTreeNode::bringToTop() +{ + if (!parentNode()) + return; + parentNode()->m_childStack.raise(asWasmWindow()); + parentNode()->bringToTop(); +} + +void QWasmWindowTreeNode::sendToBottom() +{ + if (!parentNode()) + return; + m_childStack.lower(asWasmWindow()); +} + +void QWasmWindowTreeNode::onTopWindowChanged() +{ + constexpr int zOrderForElementInFrontOfScreen = 3; + int z = zOrderForElementInFrontOfScreen; + std::for_each(m_childStack.rbegin(), m_childStack.rend(), + [this, &z](QWasmWindow *window) { setWindowZOrder(window, z++); }); +} + +void QWasmWindowTreeNode::setActiveChildNode(QWasmWindow *activeChild) +{ + m_activeChild = activeChild; + + auto it = m_childStack.begin(); + if (it == m_childStack.end()) + return; + for (; it != m_childStack.end(); ++it) + (*it)->onActivationChanged(*it == m_activeChild); + + setAsActiveNode(); +} diff --git a/src/plugins/platforms/wasm/qwasmwindowtreenode.h b/src/plugins/platforms/wasm/qwasmwindowtreenode.h new file mode 100644 index 00000000000..344fdb43cbd --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmwindowtreenode.h @@ -0,0 +1,53 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef QWASMWINDOWTREENODE_H +#define QWASMWINDOWTREENODE_H + +#include "qwasmwindowstack.h" + +namespace emscripten { +class val; +} + +class QWasmWindow; + +enum class QWasmWindowTreeNodeChangeType { + NodeInsertion, + NodeRemoval, +}; + +class QWasmWindowTreeNode +{ +public: + QWasmWindowTreeNode(); + virtual ~QWasmWindowTreeNode(); + + virtual emscripten::val containerElement() = 0; + virtual QWasmWindowTreeNode *parentNode() = 0; + +protected: + virtual void onParentChanged(QWasmWindowTreeNode *previous, QWasmWindowTreeNode *current, + QWasmWindowStack::PositionPreference positionPreference); + virtual QWasmWindow *asWasmWindow(); + virtual void onSubtreeChanged(QWasmWindowTreeNodeChangeType changeType, + QWasmWindowTreeNode *parent, QWasmWindow *child); + virtual void setWindowZOrder(QWasmWindow *window, int z); + + void onPositionPreferenceChanged(QWasmWindowStack::PositionPreference positionPreference); + void setAsActiveNode(); + void bringToTop(); + void sendToBottom(); + + const QWasmWindowStack &childStack() const { return m_childStack; } + QWasmWindow *activeChild() const { return m_activeChild; } + +private: + void onTopWindowChanged(); + void setActiveChildNode(QWasmWindow *activeChild); + + QWasmWindowStack m_childStack; + QWasmWindow *m_activeChild = nullptr; +}; + +#endif // QWASMWINDOWTREENODE_H |