diff --git a/src/plugins/platforms/wasm/CMakeLists.txt b/src/plugins/platforms/wasm/CMakeLists.txt index b00684a1378..03cb0d52ca1 100644 --- a/src/plugins/platforms/wasm/CMakeLists.txt +++ b/src/plugins/platforms/wasm/CMakeLists.txt @@ -32,7 +32,7 @@ qt_internal_add_plugin(QWasmIntegrationPlugin qwasmwindowtreenode.cpp qwasmwindowtreenode.h qwasmwindownonclientarea.cpp qwasmwindownonclientarea.h qwasminputcontext.cpp qwasminputcontext.h - qwasmwindowstack.cpp qwasmwindowstack.h + qwasmwindowstack.h DEFINES QT_EGL_NO_X11 QT_NO_FOREACH diff --git a/src/plugins/platforms/wasm/qwasmscreen.cpp b/src/plugins/platforms/wasm/qwasmscreen.cpp index badc5468c8b..a2c8306b13b 100644 --- a/src/plugins/platforms/wasm/qwasmscreen.cpp +++ b/src/plugins/platforms/wasm/qwasmscreen.cpp @@ -325,7 +325,7 @@ emscripten::val QWasmScreen::containerElement() return m_shadowContainer; } -QWasmWindowTreeNode *QWasmScreen::parentNode() +QWasmWindowTreeNode<> *QWasmScreen::parentNode() { return nullptr; } diff --git a/src/plugins/platforms/wasm/qwasmscreen.h b/src/plugins/platforms/wasm/qwasmscreen.h index 66e7e39d008..a19818af2ff 100644 --- a/src/plugins/platforms/wasm/qwasmscreen.h +++ b/src/plugins/platforms/wasm/qwasmscreen.h @@ -25,7 +25,7 @@ class QWasmCompositor; class QWasmDeadKeySupport; class QOpenGLContext; -class QWasmScreen : public QObject, public QPlatformScreen, public QWasmWindowTreeNode +class QWasmScreen : public QObject, public QPlatformScreen, public QWasmWindowTreeNode<> { Q_OBJECT public: diff --git a/src/plugins/platforms/wasm/qwasmwindow.cpp b/src/plugins/platforms/wasm/qwasmwindow.cpp index 3337be184d6..3135bb6d655 100644 --- a/src/plugins/platforms/wasm/qwasmwindow.cpp +++ b/src/plugins/platforms/wasm/qwasmwindow.cpp @@ -37,17 +37,6 @@ 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, @@ -132,6 +121,16 @@ QWasmWindow::QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport, registerEventHandlers(); + m_transientWindowChangedConnection = + QObject::connect( + window(), &QWindow::transientParentChanged, + window(), [this](QWindow *tp) { onTransientParentChanged(tp); }); + + m_modalityChangedConnection = + QObject::connect( + window(), &QWindow::modalityChanged, + window(), [this](Qt::WindowModality) { onModalityChanged(); }); + setParent(parent()); } @@ -214,6 +213,9 @@ QWasmWindow::~QWasmWindow() #if QT_CONFIG(accessibility) QWasmAccessibility::onRemoveWindow(window()); #endif + QObject::disconnect(m_transientWindowChangedConnection); + QObject::disconnect(m_modalityChangedConnection); + shutdown(); emscripten::val::module_property("specialHTMLTargets").delete_(canvasSelector()); @@ -225,6 +227,37 @@ QWasmWindow::~QWasmWindow() emscripten_cancel_animation_frame(m_requestAnimationFrameId); } +void QWasmWindow::shutdown() +{ + if (!window() || + (QGuiApplication::focusWindow() && // Don't act if we have a focus window different from this + QGuiApplication::focusWindow() != window())) + return; + + // Make a list of all windows sorted on active index. + // Skip windows with active index 0 as they have + // never been active. + std::map allWindows; + for (const auto &w : platformScreen()->allWindows()) { + if (w->getActiveIndex() > 0) + allWindows.insert({w->getActiveIndex(), w}); + } + + // window is not in all windows + if (getActiveIndex() > 0) + allWindows.insert({getActiveIndex(), this}); + + if (allWindows.size() >= 2) { + const auto lastIt = std::prev(allWindows.end()); + const auto prevIt = std::prev(lastIt); + const auto lastW = lastIt->second; + const auto prevW = prevIt->second; + + if (lastW == this) // Only act if window is last to be active + prevW->requestActivateWindow(); + } +} + QSurfaceFormat QWasmWindow::format() const { return window()->requestedFormat(); @@ -237,6 +270,24 @@ QWasmWindow *QWasmWindow::fromWindow(const QWindow *window) return static_cast(window->handle()); } +QWasmWindow *QWasmWindow::transientParent() const +{ + if (!window()) + return nullptr; + + return fromWindow(window()->transientParent()); +} + +Qt::WindowFlags QWasmWindow::windowFlags() const +{ + return window()->flags(); +} + +bool QWasmWindow::isModal() const +{ + return window()->isModal(); +} + void QWasmWindow::onRestoreClicked() { window()->setWindowState(Qt::WindowNoState); @@ -471,30 +522,14 @@ void QWasmWindow::onActivationChanged(bool active) dom::syncCSSClassWith(m_decoratedWindow, "inactive", !active); } -// Fix top level window flags in case only the type flags are passed. -static inline Qt::WindowFlags fixTopLevelWindowFlags(Qt::WindowFlags flags) -{ - if (!(flags.testFlag(Qt::CustomizeWindowHint))) { - if (flags.testFlag(Qt::Window)) { - flags |= Qt::WindowTitleHint | Qt::WindowSystemMenuHint - |Qt::WindowMaximizeButtonHint|Qt::WindowCloseButtonHint; - } - if (flags.testFlag(Qt::Dialog) || flags.testFlag(Qt::Tool)) - flags |= Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint; - - if ((flags & Qt::WindowType_Mask) == Qt::SplashScreen) - flags |= Qt::FramelessWindowHint; - } - return flags; -} - void QWasmWindow::setWindowFlags(Qt::WindowFlags flags) { flags = fixTopLevelWindowFlags(flags); - if (flags.testFlag(Qt::WindowStaysOnTopHint) != m_flags.testFlag(Qt::WindowStaysOnTopHint) - || flags.testFlag(Qt::WindowStaysOnBottomHint) - != m_flags.testFlag(Qt::WindowStaysOnBottomHint)) { + if ((flags.testFlag(Qt::WindowStaysOnTopHint) != m_flags.testFlag(Qt::WindowStaysOnTopHint)) + || (flags.testFlag(Qt::WindowStaysOnBottomHint) + != m_flags.testFlag(Qt::WindowStaysOnBottomHint)) + || shouldBeAboveTransientParentFlags(flags) != shouldBeAboveTransientParentFlags(m_flags)) { onPositionPreferenceChanged(positionPreferenceFromWindowFlags(flags)); } m_flags = flags; @@ -886,6 +921,55 @@ bool QWasmWindow::processWheel(const WheelEvent &event) Qt::MouseEventNotSynthesized, event.webkitDirectionInvertedFromDevice); } +// Fix top level window flags in case only the type flags are passed. +Qt::WindowFlags QWasmWindow::fixTopLevelWindowFlags(Qt::WindowFlags flags) const +{ + if (!(flags.testFlag(Qt::CustomizeWindowHint))) { + if (flags.testFlag(Qt::Window)) { + flags |= Qt::WindowTitleHint | Qt::WindowSystemMenuHint + |Qt::WindowMaximizeButtonHint|Qt::WindowCloseButtonHint; + } + if (flags.testFlag(Qt::Dialog) || flags.testFlag(Qt::Tool)) + flags |= Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint; + + if ((flags & Qt::WindowType_Mask) == Qt::SplashScreen) + flags |= Qt::FramelessWindowHint; + } + return flags; +} + +bool QWasmWindow::shouldBeAboveTransientParentFlags(Qt::WindowFlags flags) const +{ + if (!transientParent()) + return false; + + if (isModal()) + return true; + + if (flags.testFlag(Qt::Tool) || + flags.testFlag(Qt::SplashScreen) || + flags.testFlag(Qt::ToolTip) || + flags.testFlag(Qt::Popup)) + { + return true; + } + + return false; +} + +QWasmWindowStack<>::PositionPreference QWasmWindow::positionPreferenceFromWindowFlags(Qt::WindowFlags flags) const +{ + flags = fixTopLevelWindowFlags(flags); + + if (flags.testFlag(Qt::WindowStaysOnTopHint)) + return QWasmWindowStack<>::PositionPreference::StayOnTop; + if (flags.testFlag(Qt::WindowStaysOnBottomHint)) + return QWasmWindowStack<>::PositionPreference::StayOnBottom; + if (shouldBeAboveTransientParentFlags(flags)) + return QWasmWindowStack<>::PositionPreference::StayAboveTransientParent; + return QWasmWindowStack<>::PositionPreference::Regular; +} + QRect QWasmWindow::normalGeometry() const { return m_normalGeometry; @@ -996,6 +1080,22 @@ void QWasmWindow::setMask(const QRegion ®ion) m_decoratedWindow["style"].set("clipPath", emscripten::val(cssClipPath.str())); } +void QWasmWindow::onTransientParentChanged(QWindow *newTransientParent) +{ + Q_UNUSED(newTransientParent); + + const auto positionPreference = positionPreferenceFromWindowFlags(window()->flags()); + QWasmWindowTreeNode::onParentChanged(parentNode(), nullptr, positionPreference); + QWasmWindowTreeNode::onParentChanged(nullptr, parentNode(), positionPreference); +} + +void QWasmWindow::onModalityChanged() +{ + const auto positionPreference = positionPreferenceFromWindowFlags(window()->flags()); + QWasmWindowTreeNode::onParentChanged(parentNode(), nullptr, positionPreference); + QWasmWindowTreeNode::onParentChanged(nullptr, parentNode(), positionPreference); +} + void QWasmWindow::setParent(const QPlatformWindow *) { // The window flags depend on whether we are a @@ -1015,7 +1115,7 @@ emscripten::val QWasmWindow::containerElement() return m_window; } -QWasmWindowTreeNode *QWasmWindow::parentNode() +QWasmWindowTreeNode<> *QWasmWindow::parentNode() { if (parent()) return static_cast(parent()); @@ -1028,7 +1128,7 @@ QWasmWindow *QWasmWindow::asWasmWindow() } void QWasmWindow::onParentChanged(QWasmWindowTreeNode *previous, QWasmWindowTreeNode *current, - QWasmWindowStack::PositionPreference positionPreference) + QWasmWindowStack<>::PositionPreference positionPreference) { if (previous) previous->containerElement().call("removeChild", m_decoratedWindow); diff --git a/src/plugins/platforms/wasm/qwasmwindow.h b/src/plugins/platforms/wasm/qwasmwindow.h index 67a3e8ea293..904e736a7e7 100644 --- a/src/plugins/platforms/wasm/qwasmwindow.h +++ b/src/plugins/platforms/wasm/qwasmwindow.h @@ -37,7 +37,7 @@ struct WheelEvent; Q_DECLARE_LOGGING_CATEGORY(qLcQpaWasmInputContext) class QWasmWindow final : public QPlatformWindow, - public QWasmWindowTreeNode, + public QWasmWindowTreeNode<>, public QNativeInterface::Private::QWasmWindow { public: @@ -46,6 +46,9 @@ public: ~QWasmWindow() final; static QWasmWindow *fromWindow(const QWindow *window); + QWasmWindow *transientParent() const; + Qt::WindowFlags windowFlags() const; + bool isModal() const; QSurfaceFormat format() const override; void registerEventHandlers(); @@ -106,15 +109,23 @@ public: emscripten::val containerElement() final; QWasmWindowTreeNode *parentNode() final; +public slots: + void onTransientParentChanged(QWindow *newTransientParent); + void onModalityChanged(); + private: friend class QWasmScreen; static constexpr auto defaultWindowSize = 160; + QMetaObject::Connection m_transientWindowChangedConnection; + QMetaObject::Connection m_modalityChangedConnection; + // QWasmWindowTreeNode: QWasmWindow *asWasmWindow() final; void onParentChanged(QWasmWindowTreeNode *previous, QWasmWindowTreeNode *current, - QWasmWindowStack::PositionPreference positionPreference) final; + QWasmWindowStack<>::PositionPreference positionPreference) final; + void shutdown(); void invalidate(); bool hasFrame() const; bool hasTitleBar() const; @@ -134,6 +145,9 @@ private: bool deliverPointerEvent(const PointerEvent &event); void handleWheelEvent(const emscripten::val &event); bool processWheel(const WheelEvent &event); + Qt::WindowFlags fixTopLevelWindowFlags(Qt::WindowFlags) const; + bool shouldBeAboveTransientParentFlags(Qt::WindowFlags flags) const; + QWasmWindowStack<>::PositionPreference positionPreferenceFromWindowFlags(Qt::WindowFlags) const; QWasmCompositor *m_compositor = nullptr; QWasmBackingStore *m_backingStore = nullptr; diff --git a/src/plugins/platforms/wasm/qwasmwindowstack.cpp b/src/plugins/platforms/wasm/qwasmwindowstack.cpp deleted file mode 100644 index d3769c7a1bb..00000000000 --- a/src/plugins/platforms/wasm/qwasmwindowstack.cpp +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only - -#include "qwasmwindowstack.h" - -QT_BEGIN_NAMESPACE - -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, PositionPreference position) -{ - Q_ASSERT(m_windowStack.count(window) == 0); - - 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) -{ - Q_ASSERT(m_windowStack.count(window) == 1); - - auto it = std::find(m_windowStack.begin(), m_windowStack.end(), window); - 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); - - 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 == topWindow()) - return; - - 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 == *m_windowStack.begin()) - return; - - 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(position) - static_cast(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() -{ - return m_windowStack.rbegin(); -} - -QWasmWindowStack::iterator QWasmWindowStack::end() -{ - return m_windowStack.rend(); -} - -QWasmWindowStack::const_iterator QWasmWindowStack::begin() const -{ - return m_windowStack.rbegin(); -} - -QWasmWindowStack::const_iterator QWasmWindowStack::end() const -{ - return m_windowStack.rend(); -} - -QWasmWindowStack::const_reverse_iterator QWasmWindowStack::rbegin() const -{ - return m_windowStack.begin(); -} - -QWasmWindowStack::const_reverse_iterator QWasmWindowStack::rend() const -{ - return m_windowStack.end(); -} - -bool QWasmWindowStack::empty() const -{ - return m_windowStack.empty(); -} - -size_t QWasmWindowStack::size() const -{ - return m_windowStack.size(); -} - -QWasmWindow *QWasmWindowStack::topWindow() const -{ - return m_windowStack.empty() ? nullptr : m_windowStack.last(); -} - -QWasmWindowStack::PositionPreference -QWasmWindowStack::getWindowPositionPreference(StorageType::iterator windowIt) const -{ - 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 c75001157a0..90ad4c9dd15 100644 --- a/src/plugins/platforms/wasm/qwasmwindowstack.h +++ b/src/plugins/platforms/wasm/qwasmwindowstack.h @@ -6,6 +6,7 @@ #include #include +#include #include @@ -21,31 +22,49 @@ class QWasmWindow; // Access to the top element is facilitated by |topWindow|. // Changes to the top element are signaled via the |topWindowChangedCallback| supplied at // construction. + +// Requirement Window +// +// type Window { +// Window *transientParent() const; +// Qt::WindowFlags windowFlags() const; +// bool isModal() const; +// }; + +template class Q_AUTOTEST_EXPORT QWasmWindowStack { +private: + QWasmWindowStack(const QWasmWindowStack &) = delete; + QWasmWindowStack(QWasmWindowStack &&) = delete; + + QWasmWindowStack &operator=(const QWasmWindowStack &) = delete; + QWasmWindowStack &&operator=(QWasmWindowStack &&) = delete; + public: - using WindowOrderChangedCallbackType = std::function; - - using StorageType = QList; - - using iterator = StorageType::reverse_iterator; - using const_iterator = StorageType::const_reverse_iterator; - using const_reverse_iterator = StorageType::const_iterator; - enum class PositionPreference { StayOnBottom, Regular, StayOnTop, + StayAboveTransientParent // Parent is transientParent() }; + using WindowOrderChangedCallbackType = std::function; + using StorageType = QList; + + using iterator = typename StorageType::reverse_iterator; + using const_iterator = typename StorageType::const_reverse_iterator; + using const_reverse_iterator = typename StorageType::const_iterator; + explicit QWasmWindowStack(WindowOrderChangedCallbackType topWindowChangedCallback); ~QWasmWindowStack(); - void pushWindow(QWasmWindow *window, PositionPreference position); - void removeWindow(QWasmWindow *window); - void raise(QWasmWindow *window); - void lower(QWasmWindow *window); - void windowPositionPreferenceChanged(QWasmWindow *window, PositionPreference position); + void pushWindow(Window *window, PositionPreference position, bool insertAtRegionBegin = false, + bool callCallbacks = true); + void removeWindow(Window *window, bool callCallbacks = true); + void raise(Window *window); + void lower(Window *window); + void windowPositionPreferenceChanged(Window *window, PositionPreference position); // Iterates top-to-bottom iterator begin(); @@ -59,17 +78,25 @@ public: bool empty() const; size_t size() const; - QWasmWindow *topWindow() const; + Window *topWindow() const; + PositionPreference getWindowPositionPreference(typename StorageType::const_iterator windowIt, + bool testStayAbove = true) const; private: - PositionPreference getWindowPositionPreference(StorageType::iterator windowIt) const; - + bool raiseImpl(Window *window); + bool lowerImpl(Window *window); + bool shouldBeAboveTransientParent(const Window *window) const; + bool shouldBeAboveTransientParentFlags(Qt::WindowFlags flags) const; + void invariant(); WindowOrderChangedCallbackType m_windowOrderChangedCallback; - QList m_windowStack; - StorageType::iterator m_regularWindowsBegin; - StorageType::iterator m_alwaysOnTopWindowsBegin; + + StorageType m_windowStack; + typename StorageType::iterator m_regularWindowsBegin; + typename StorageType::iterator m_alwaysOnTopWindowsBegin; }; +#include "qwasmwindowstack.inc" + QT_END_NAMESPACE #endif // QWASMWINDOWSTACK_H diff --git a/src/plugins/platforms/wasm/qwasmwindowstack.inc b/src/plugins/platforms/wasm/qwasmwindowstack.inc new file mode 100644 index 00000000000..9f0b5b2d491 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmwindowstack.inc @@ -0,0 +1,384 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef QWASMWINDOWSTACK_INC +#define QWASMWINDOWSTACK_INC + +template +QWasmWindowStack::QWasmWindowStack( + WindowOrderChangedCallbackType windowOrderChangedCallback) + : m_windowOrderChangedCallback(std::move(windowOrderChangedCallback)), + m_regularWindowsBegin(m_windowStack.begin()), + m_alwaysOnTopWindowsBegin(m_windowStack.begin()) +{ + invariant(); +} + +template +QWasmWindowStack::~QWasmWindowStack() +{ + invariant(); +} + +template +void QWasmWindowStack::invariant() +{ + Q_ASSERT(m_regularWindowsBegin >= m_windowStack.begin()); + Q_ASSERT(m_regularWindowsBegin <= m_alwaysOnTopWindowsBegin); + Q_ASSERT(m_alwaysOnTopWindowsBegin <= m_windowStack.end()); +} + +/* insert a window at the correct location. + * + * There are three groups + * StayOnBottom + * Regular + * StayOnTop + * + * In addition there is StayAboveParent which + * can place the window in either of the + * three above groups, depending on which + * group the transient parent resides in. + * + * insertAtRegionBegin controls the placement + * within each of the groups. Either at the end + * or at the beginning. + * +*/ +template +void QWasmWindowStack::pushWindow(Window *window, PositionPreference position, + bool insertAtRegionBegin, bool callCallbacks) +{ + invariant(); + Q_ASSERT(m_windowStack.count(window) == 0); + + auto regularDistance = std::distance(m_windowStack.begin(), m_regularWindowsBegin); + auto stayOnTopDistance = std::distance(m_windowStack.begin(), m_alwaysOnTopWindowsBegin); + + if (position == PositionPreference::StayAboveTransientParent) { + Q_ASSERT(window->transientParent()); + const auto it = + std::find(m_windowStack.begin(), m_windowStack.end(), window->transientParent()); + if (it == m_windowStack.end()) { + qWarning() << "QWasmWindowStack::pushWindow - missing parent" + << window->transientParent(); + pushWindow(window, PositionPreference::Regular, insertAtRegionBegin, callCallbacks); + return; + } else { + if (it >= m_alwaysOnTopWindowsBegin) + ; + else if (it >= m_regularWindowsBegin) + ++stayOnTopDistance; + else { + ++regularDistance; + ++stayOnTopDistance; + } + m_windowStack.insert(it + 1, window); + } + } else if (position == PositionPreference::StayOnTop) { + if (insertAtRegionBegin) + m_windowStack.insert(m_alwaysOnTopWindowsBegin, window); + else + m_windowStack.insert(m_windowStack.end(), window); + + } else if (position == PositionPreference::Regular) { + ++stayOnTopDistance; + if (insertAtRegionBegin) + m_windowStack.insert(m_regularWindowsBegin, window); + else + m_windowStack.insert(m_alwaysOnTopWindowsBegin, window); + + } else { + // StayOnBottom + ++regularDistance; + ++stayOnTopDistance; + + if (insertAtRegionBegin) + m_windowStack.insert(m_windowStack.begin(), window); + else + m_windowStack.insert(m_regularWindowsBegin, window); + } + + m_regularWindowsBegin = m_windowStack.begin() + regularDistance; + m_alwaysOnTopWindowsBegin = m_windowStack.begin() + stayOnTopDistance; + + if (callCallbacks) + m_windowOrderChangedCallback(); + + Q_ASSERT(m_windowStack.count(window) == 1); + invariant(); +} + +template +void QWasmWindowStack::removeWindow(Window *window, bool callCallbacks) +{ + invariant(); + Q_ASSERT(m_windowStack.count(window) == 1); + + auto regularDistance = std::distance(m_windowStack.begin(), m_regularWindowsBegin); + auto stayOnTopDistance = std::distance(m_windowStack.begin(), m_alwaysOnTopWindowsBegin); + + auto it = std::find(m_windowStack.begin(), m_windowStack.end(), window); + + Q_ASSERT(it != m_windowStack.end()); + + if (it < m_regularWindowsBegin) + --regularDistance; + if (it < m_alwaysOnTopWindowsBegin) + --stayOnTopDistance; + + m_windowStack.erase(it); + + m_regularWindowsBegin = m_windowStack.begin() + regularDistance; + m_alwaysOnTopWindowsBegin = m_windowStack.begin() + stayOnTopDistance; + + if (callCallbacks) + m_windowOrderChangedCallback(); + + Q_ASSERT(m_windowStack.count(window) == 0); + invariant(); +} + +template +void QWasmWindowStack::raise(Window *window) +{ + if (raiseImpl(window)) + m_windowOrderChangedCallback(); +} + +template +bool QWasmWindowStack::raiseImpl(Window *window) +{ + invariant(); + Q_ASSERT(m_windowStack.count(window) == 1); + + { + const auto it = std::find(m_windowStack.begin(), m_windowStack.end(), window); + const auto itEnd = ([this, it]() { + if (it < m_regularWindowsBegin) + return m_regularWindowsBegin; + if (it < m_alwaysOnTopWindowsBegin) + return m_alwaysOnTopWindowsBegin; + return m_windowStack.end(); + })(); + + if (it + 1 == itEnd) + return false; + + std::rotate(it, it + 1, itEnd); + } + + std::vector windowsToRaise; + { + for (auto trit = m_windowStack.begin(); trit != m_windowStack.end(); ++trit) { + const auto w = *trit; + if ((w != window) && + (getWindowPositionPreference(trit) == PositionPreference::StayAboveTransientParent) && + (w->transientParent() == window)) { + windowsToRaise.push_back(w); + } + } + } + + for (const auto w : windowsToRaise) + { + raiseImpl(w); + } + invariant(); + return true; +} + +template +void QWasmWindowStack::lower(Window *window) +{ + if (lowerImpl(window)) + m_windowOrderChangedCallback(); +} + +template +bool QWasmWindowStack::lowerImpl(Window *window) +{ + invariant(); + Q_ASSERT(m_windowStack.count(window) == 1); + + { + const auto it = std::find(m_windowStack.begin(), m_windowStack.end(), window); + const auto itBegin = ([this, it]() { + if (it >= m_alwaysOnTopWindowsBegin) + return m_alwaysOnTopWindowsBegin; + if (it >= m_regularWindowsBegin) + return m_regularWindowsBegin; + return m_windowStack.begin(); + })(); + + if (itBegin == it) + return false; + + std::rotate(itBegin, it, it + 1); + } + + std::vector windowsToLower; + { + for (auto trit = m_windowStack.begin(); trit != m_windowStack.end(); ++trit) { + const auto w = *trit; + if ((w != window) && + (getWindowPositionPreference(trit) == PositionPreference::StayAboveTransientParent) && + (w->transientParent() == window)) { + windowsToLower.push_back(w); + } + } + } + + for (const auto w : windowsToLower) + { + lowerImpl(w); + } + invariant(); + return true; +} + +template +void QWasmWindowStack::windowPositionPreferenceChanged(Window *window, + PositionPreference position) +{ + invariant(); + + auto it = std::find(m_windowStack.begin(), m_windowStack.end(), window); + const auto currentPosition = getWindowPositionPreference(it); + + if (position == currentPosition) { + ; + } else if (currentPosition == PositionPreference::StayAboveTransientParent) { + // Keep position if possible + const bool isStayOnBottom ( it < m_regularWindowsBegin); + const bool isRegular( !isStayOnBottom && (it < m_alwaysOnTopWindowsBegin)); + const bool isStayOnTop(!isStayOnBottom && !isRegular); + + if (isStayOnBottom && (position == PositionPreference::StayOnBottom)) + ; + else if (isRegular && (position == PositionPreference::Regular)) + ; + else if (isStayOnTop && (position == PositionPreference::StayOnTop)) + ; + else { + auto current = *it; + removeWindow(current, false); + pushWindow(current, position, false, false); + m_windowOrderChangedCallback(); + } + } else { + const bool insertAtRegionBegin = ( + (currentPosition != PositionPreference::StayAboveTransientParent) && + (position != PositionPreference::StayAboveTransientParent) && + ((currentPosition == PositionPreference::StayOnBottom) || + (position == PositionPreference::StayOnTop))); + + auto current = *it; + removeWindow(current, false); + pushWindow(current, position, insertAtRegionBegin, false); + m_windowOrderChangedCallback(); + } + invariant(); +} + +template +typename QWasmWindowStack::iterator QWasmWindowStack::begin() +{ + return m_windowStack.rbegin(); +} + +template +typename QWasmWindowStack::iterator QWasmWindowStack::end() +{ + return m_windowStack.rend(); +} + +template +typename QWasmWindowStack::const_iterator QWasmWindowStack::begin() const +{ + return m_windowStack.rbegin(); +} + +template +typename QWasmWindowStack::const_iterator QWasmWindowStack::end() const +{ + return m_windowStack.rend(); +} + +template +typename QWasmWindowStack::const_reverse_iterator +QWasmWindowStack::rbegin() const +{ + return m_windowStack.begin(); +} + +template +typename QWasmWindowStack::const_reverse_iterator +QWasmWindowStack::rend() const +{ + return m_windowStack.end(); +} + +template +bool QWasmWindowStack::empty() const +{ + return m_windowStack.empty(); +} + +template +size_t QWasmWindowStack::size() const +{ + return m_windowStack.size(); +} + +template +Window *QWasmWindowStack::topWindow() const +{ + return m_windowStack.empty() ? nullptr : m_windowStack.last(); +} + +template +bool QWasmWindowStack::shouldBeAboveTransientParentFlags(Qt::WindowFlags flags) const +{ + if (flags.testFlag(Qt::Tool) || + flags.testFlag(Qt::SplashScreen) || + flags.testFlag(Qt::ToolTip) || + flags.testFlag(Qt::Popup)) + { + return true; + } + + return false; +} + +template +bool QWasmWindowStack::shouldBeAboveTransientParent(const Window *window) const +{ + if (!window->transientParent()) + return false; + + if (window->isModal()) + return true; + + if (shouldBeAboveTransientParentFlags(window->windowFlags())) + return true; + + return false; +} + +template +typename QWasmWindowStack::PositionPreference +QWasmWindowStack::getWindowPositionPreference( + typename StorageType::const_iterator windowIt, bool testStayAbove) const +{ + Window *window = *windowIt; + if (testStayAbove && shouldBeAboveTransientParent(window)) + return PositionPreference::StayAboveTransientParent; + if (windowIt >= m_alwaysOnTopWindowsBegin) + return PositionPreference::StayOnTop; + if (windowIt >= m_regularWindowsBegin) + return PositionPreference::Regular; + return PositionPreference::StayOnBottom; +} + +#endif /* QWASMWINDOWSTACK_INC */ diff --git a/src/plugins/platforms/wasm/qwasmwindowtreenode.cpp b/src/plugins/platforms/wasm/qwasmwindowtreenode.cpp index 08eb7e327b1..6f068b82a39 100644 --- a/src/plugins/platforms/wasm/qwasmwindowtreenode.cpp +++ b/src/plugins/platforms/wasm/qwasmwindowtreenode.cpp @@ -3,149 +3,4 @@ #include "qwasmwindowtreenode.h" -#include "qwasmwindow.h" -#include "qwasmscreen.h" - -uint64_t QWasmWindowTreeNode::s_nextActiveIndex = 0; - -QWasmWindowTreeNode::QWasmWindowTreeNode() - : m_childStack(std::bind(&QWasmWindowTreeNode::onTopWindowChanged, this)) -{ -} - -QWasmWindowTreeNode::~QWasmWindowTreeNode() = default; - -void QWasmWindowTreeNode::shutdown() -{ - QWasmWindow *window = asWasmWindow(); - if (!window || - !window->window() || - (QGuiApplication::focusWindow() && // Don't act if we have a focus window different from this - QGuiApplication::focusWindow() != window->window())) - return; - - // Make a list of all windows sorted on active index. - // Skip windows with active index 0 as they have - // never been active. - std::map allWindows; - for (const auto &w : window->platformScreen()->allWindows()) { - if (w->getActiveIndex() > 0) - allWindows.insert({w->getActiveIndex(), w}); - } - - // window is not in all windows - if (window->getActiveIndex() > 0) - allWindows.insert({window->getActiveIndex(), window}); - - if (allWindows.size() >= 2) { - const auto lastIt = std::prev(allWindows.end()); - const auto prevIt = std::prev(lastIt); - const auto lastW = lastIt->second; - const auto prevW = prevIt->second; - - if (lastW == window) // Only act if window is last to be active - prevW->requestActivateWindow(); - } -} - -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()->window()) { - - const auto flags = m_childStack.topWindow()->window()->flags(); - const bool notToolOrPopup = ((flags & Qt::ToolTip) != Qt::ToolTip) && ((flags & Qt::Popup) != Qt::Popup); - const QVariant showWithoutActivating = m_childStack.topWindow()->window()->property("_q_showWithoutActivating"); - if (!showWithoutActivating.isValid() || !showWithoutActivating.toBool()) { - if (notToolOrPopup) - 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()); - - // At the end, this is a recursive function - m_activeIndex = ++s_nextActiveIndex; -} - -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(); -} +uint64_t QWasmWindowTreeNodeBase::s_nextActiveIndex = 0; diff --git a/src/plugins/platforms/wasm/qwasmwindowtreenode.h b/src/plugins/platforms/wasm/qwasmwindowtreenode.h index 170d777f02a..c955cd21740 100644 --- a/src/plugins/platforms/wasm/qwasmwindowtreenode.h +++ b/src/plugins/platforms/wasm/qwasmwindowtreenode.h @@ -6,6 +6,8 @@ #include "qwasmwindowstack.h" +#include + namespace emscripten { class val; } @@ -17,7 +19,14 @@ enum class QWasmWindowTreeNodeChangeType { NodeRemoval, }; -class QWasmWindowTreeNode +class QWasmWindowTreeNodeBase +{ +protected: + static uint64_t s_nextActiveIndex; +}; + +template +class QWasmWindowTreeNode : public QWasmWindowTreeNodeBase { public: QWasmWindowTreeNode(); @@ -28,20 +37,20 @@ public: protected: virtual void onParentChanged(QWasmWindowTreeNode *previous, QWasmWindowTreeNode *current, - QWasmWindowStack::PositionPreference positionPreference); - virtual QWasmWindow *asWasmWindow(); + typename QWasmWindowStack::PositionPreference positionPreference); + virtual Window *asWasmWindow(); virtual void onSubtreeChanged(QWasmWindowTreeNodeChangeType changeType, - QWasmWindowTreeNode *parent, QWasmWindow *child); - virtual void setWindowZOrder(QWasmWindow *window, int z); + QWasmWindowTreeNode *parent, Window *child); + virtual void setWindowZOrder(Window *window, int z); - void onPositionPreferenceChanged(QWasmWindowStack::PositionPreference positionPreference); + void onPositionPreferenceChanged(typename QWasmWindowStack::PositionPreference positionPreference); void setAsActiveNode(); void bringToTop(); void sendToBottom(); - void shutdown(); - const QWasmWindowStack &childStack() const { return m_childStack; } - QWasmWindow *activeChild() const { return m_activeChild; } + const QWasmWindowStack &childStack() const { return m_childStack; } + QWasmWindowStack &childStack() { return m_childStack; } + Window *activeChild() const { return m_activeChild; } uint64_t getActiveIndex() const { return m_activeIndex; @@ -49,13 +58,13 @@ protected: private: void onTopWindowChanged(); - void setActiveChildNode(QWasmWindow *activeChild); + void setActiveChildNode(Window *activeChild); uint64_t m_activeIndex = 0; - static uint64_t s_nextActiveIndex; - QWasmWindowStack m_childStack; - QWasmWindow *m_activeChild = nullptr; + QWasmWindowStack m_childStack; + Window *m_activeChild = nullptr; }; +#endif -#endif // QWASMWINDOWTREENODE_H +#include "qwasmwindowtreenode.inc" diff --git a/src/plugins/platforms/wasm/qwasmwindowtreenode.inc b/src/plugins/platforms/wasm/qwasmwindowtreenode.inc new file mode 100644 index 00000000000..c5a228d7fab --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmwindowtreenode.inc @@ -0,0 +1,128 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef QWASMWINDOWTREENODE_INC +#define QWASMWINDOWTREENODE_INC + +template +QWasmWindowTreeNode::QWasmWindowTreeNode() + : m_childStack(std::bind(&QWasmWindowTreeNode::onTopWindowChanged, this)) +{ +} + +template +QWasmWindowTreeNode::~QWasmWindowTreeNode() = default; + +template +void QWasmWindowTreeNode::onParentChanged(QWasmWindowTreeNode *previousParent, + QWasmWindowTreeNode *currentParent, + typename 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); + } +} + +template +Window *QWasmWindowTreeNode::asWasmWindow() +{ + return nullptr; +} + +template +void QWasmWindowTreeNode::onSubtreeChanged(QWasmWindowTreeNodeChangeType changeType, + QWasmWindowTreeNode *parent, Window *child) +{ + if (changeType == QWasmWindowTreeNodeChangeType::NodeInsertion && parent == this + && m_childStack.topWindow() + && m_childStack.topWindow()->window()) { + + const auto flags = m_childStack.topWindow()->window()->flags(); + const bool notToolOrPopup = ((flags & Qt::ToolTip) != Qt::ToolTip) && ((flags & Qt::Popup) != Qt::Popup); + const QVariant showWithoutActivating = m_childStack.topWindow()->window()->property("_q_showWithoutActivating"); + if (!showWithoutActivating.isValid() || !showWithoutActivating.toBool()) { + if (notToolOrPopup) + m_childStack.topWindow()->requestActivateWindow(); + } + } + + if (parentNode()) + parentNode()->onSubtreeChanged(changeType, parent, child); +} + +template +void QWasmWindowTreeNode::setWindowZOrder(Window *window, int z) +{ + window->setZOrder(z); +} + +template +void QWasmWindowTreeNode::onPositionPreferenceChanged( + typename QWasmWindowStack::PositionPreference positionPreference) +{ + if (parentNode()) { + parentNode()->m_childStack.windowPositionPreferenceChanged(asWasmWindow(), + positionPreference); + } +} + +template +void QWasmWindowTreeNode::setAsActiveNode() +{ + if (parentNode()) + parentNode()->setActiveChildNode(asWasmWindow()); + + // At the end, this is a recursive function + m_activeIndex = ++s_nextActiveIndex; +} + +template +void QWasmWindowTreeNode::bringToTop() +{ + if (!parentNode()) + return; + parentNode()->m_childStack.raise(asWasmWindow()); + parentNode()->bringToTop(); +} + +template +void QWasmWindowTreeNode::sendToBottom() +{ + if (!parentNode()) + return; + m_childStack.lower(asWasmWindow()); +} + +template +void QWasmWindowTreeNode::onTopWindowChanged() +{ + constexpr int zOrderForElementInFrontOfScreen = 3; + int z = zOrderForElementInFrontOfScreen; + std::for_each(m_childStack.rbegin(), m_childStack.rend(), + [this, &z](Window *window) { setWindowZOrder(window, z++); }); +} + +template +void QWasmWindowTreeNode::setActiveChildNode(Window *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(); +} + +#endif /* QWASMWINDOWTREENODE_INC */ diff --git a/tests/auto/wasm/qwasmwindowstack/tst_qwasmwindowstack.cpp b/tests/auto/wasm/qwasmwindowstack/tst_qwasmwindowstack.cpp index fe169b52dca..3df72ba6379 100644 --- a/tests/auto/wasm/qwasmwindowstack/tst_qwasmwindowstack.cpp +++ b/tests/auto/wasm/qwasmwindowstack/tst_qwasmwindowstack.cpp @@ -2,15 +2,75 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "../../../../src/plugins/platforms/wasm/qwasmwindowstack.h" + #include #include #include -class QWasmWindow +class TestWindow { +public: + TestWindow *transientParent() const { return m_transientParent; } + Qt::WindowFlags windowFlags() const { return m_windowFlags; } + bool isModal() const { return m_isModal; } + +public: + TestWindow *m_transientParent = nullptr; + Qt::WindowFlags m_windowFlags = {}; + bool m_isModal = false; }; +#define QWasmWindowStack QWasmWindowStack +#define QWasmWindow TestWindow + namespace { + +QDebug operator<<(QDebug d, const QWasmWindowStack::PositionPreference &pref) +{ + switch (pref) { + case QWasmWindowStack::PositionPreference::StayOnBottom: + d << "StayOnBottom"; + break; + case QWasmWindowStack::PositionPreference::Regular: + d << "Regular"; + break; + case QWasmWindowStack::PositionPreference::StayOnTop: + d << "StayOnTop"; + break; + case QWasmWindowStack::PositionPreference::StayAboveTransientParent: + d << "StayAboveParent"; + break; + } /* end-switch */ + return d; +} + +class LogWindows +{ +public: + LogWindows(QWasmWindow *window, const QWasmWindowStack &stack) + : m_window(window), m_stack(&stack) + { + } + +public: + friend QDebug operator<<(QDebug d, const LogWindows &cl) + { + LogWindows &l = const_cast(cl); + d << "\n"; + for (auto it = l.m_stack->rend(); it != l.m_stack->rbegin();) { + --it; + d << " Window " << (*it) - l.m_window + << l.m_stack->getWindowPositionPreference(it, false) + << l.m_stack->getWindowPositionPreference(it, true) << "\n"; + } + return d; + } + +private: + QWasmWindow *m_window; + const QWasmWindowStack *m_stack; +}; + std::vector getWindowsFrontToBack(const QWasmWindowStack *stack) { return std::vector(stack->begin(), stack->end()); @@ -42,6 +102,11 @@ private slots: void removingWithAlwaysOnTop(); void positionPreferenceChanges(); void clearing(); + void stayAboveParentOnBottom1(); + void stayAboveParentOnBottom2(); + void stayAboveParentOnBottom3(); + void stayAboveParentRegular(); + void stayAboveParentOnTop(); private: void onTopWindowChanged() @@ -97,7 +162,6 @@ void tst_QWasmWindowStack::insertion() void tst_QWasmWindowStack::raising() { QWasmWindowStack stack(m_mockCallback); - stack.pushWindow(&m_root, QWasmWindowStack::PositionPreference::StayOnBottom); stack.pushWindow(&m_window1, QWasmWindowStack::PositionPreference::Regular); stack.pushWindow(&m_window2, QWasmWindowStack::PositionPreference::Regular); @@ -106,7 +170,6 @@ void tst_QWasmWindowStack::raising() stack.pushWindow(&m_window5, QWasmWindowStack::PositionPreference::Regular); clearCallbackCounter(); - QCOMPARE(&m_window5, stack.topWindow()); m_onTopLevelChangedAction = [this, &stack]() { QVERIFY(stack.topWindow() == &m_window1); }; @@ -710,5 +773,254 @@ void tst_QWasmWindowStack::clearing() QCOMPARE(0u, stack.size()); } +void tst_QWasmWindowStack::stayAboveParentOnBottom1() +{ + QWasmWindow windows[5]; + windows[4].m_transientParent = &windows[0]; + windows[4].m_windowFlags = Qt::Tool; + + QWasmWindowStack stack(m_mockCallback); + + stack.pushWindow(windows + 0, QWasmWindowStack::PositionPreference::StayOnBottom); + stack.pushWindow(windows + 1, QWasmWindowStack::PositionPreference::StayOnBottom); + stack.pushWindow(windows + 2, QWasmWindowStack::PositionPreference::Regular); + stack.pushWindow(windows + 3, QWasmWindowStack::PositionPreference::StayOnTop); + stack.pushWindow(windows + 4, QWasmWindowStack::PositionPreference::StayAboveTransientParent); + + { + const std::vector expectedWindowOrder = { + windows + 3, + windows + 2, + windows + 1, + windows + 4, + windows + 0 + }; + + qDebug() << LogWindows(windows + 0, stack); + + QVERIFY(std::equal(expectedWindowOrder.begin(), expectedWindowOrder.end(), + getWindowsFrontToBack(&stack).begin())); + } + { + // Check that window is moved to correct group: + // it was: StayAboveParent, in group StayOnBottom + // it is: StayOnTop + stack.windowPositionPreferenceChanged( + windows + 4, + QWasmWindowStack::PositionPreference::StayOnTop); + + const std::vector expectedWindowOrder = { + windows + 4, + windows + 3, + windows + 2, + windows + 1, + windows + 0 + }; + + qDebug() << LogWindows(windows + 0, stack); + + QVERIFY(std::equal(expectedWindowOrder.begin(), expectedWindowOrder.end(), + getWindowsFrontToBack(&stack).begin())); + } +} + +void tst_QWasmWindowStack::stayAboveParentOnBottom2() +{ + QWasmWindow windows[5]; + windows[4].m_transientParent = &windows[0]; + windows[4].m_windowFlags = Qt::Tool; + + QWasmWindowStack stack(m_mockCallback); + + stack.pushWindow(windows + 0, QWasmWindowStack::PositionPreference::StayOnBottom); + stack.pushWindow(windows + 1, QWasmWindowStack::PositionPreference::StayOnBottom); + stack.pushWindow(windows + 2, QWasmWindowStack::PositionPreference::Regular); + stack.pushWindow(windows + 3, QWasmWindowStack::PositionPreference::StayOnTop); + stack.pushWindow(windows + 4, QWasmWindowStack::PositionPreference::StayAboveTransientParent); + + { + const std::vector expectedWindowOrder = { + windows + 3, + windows + 2, + windows + 1, + windows + 4, + windows + 0 + }; + + qDebug() << LogWindows(windows + 0, stack); + + QVERIFY(std::equal(expectedWindowOrder.begin(), expectedWindowOrder.end(), + getWindowsFrontToBack(&stack).begin())); + } + { + // Check that order does not change: + // it was: StayAboveParent, in group StayOnBottom + // it is: StayOnBottom + stack.windowPositionPreferenceChanged( + windows + 4, + QWasmWindowStack::PositionPreference::StayOnBottom); + + const std::vector expectedWindowOrder = { + windows + 3, + windows + 2, + windows + 1, + windows + 4, + windows + 0 + }; + + qDebug() << LogWindows(windows + 0, stack); + + QVERIFY(std::equal(expectedWindowOrder.begin(), expectedWindowOrder.end(), + getWindowsFrontToBack(&stack).begin())); + } +} + +void tst_QWasmWindowStack::stayAboveParentOnBottom3() +{ + QWasmWindow windows[5]; + windows[4].m_transientParent = &windows[0]; + windows[4].m_windowFlags = Qt::Tool; + + QWasmWindowStack stack(m_mockCallback); + + stack.pushWindow(windows + 0, QWasmWindowStack::PositionPreference::StayOnBottom); + stack.pushWindow(windows + 1, QWasmWindowStack::PositionPreference::StayOnBottom); + stack.pushWindow(windows + 2, QWasmWindowStack::PositionPreference::Regular); + stack.pushWindow(windows + 3, QWasmWindowStack::PositionPreference::StayOnTop); + stack.pushWindow(windows + 4, QWasmWindowStack::PositionPreference::StayAboveTransientParent); + + { + const std::vector expectedWindowOrder = { + windows + 3, + windows + 2, + windows + 1, + windows + 4, + windows + 0 + }; + + qDebug() << LogWindows(windows + 0, stack); + + QVERIFY(std::equal(expectedWindowOrder.begin(), expectedWindowOrder.end(), + getWindowsFrontToBack(&stack).begin())); + } + { + // Check that windows is moved to correct group + // it was: StayAboveParent, in group StayOnBottom + // it is: Regular + stack.windowPositionPreferenceChanged( + windows + 4, + QWasmWindowStack::PositionPreference::Regular); + + const std::vector expectedWindowOrder = { + windows + 3, + windows + 4, + windows + 2, + windows + 1, + windows + 0 + }; + + qDebug() << LogWindows(windows + 0, stack); + + QVERIFY(std::equal(expectedWindowOrder.begin(), expectedWindowOrder.end(), + getWindowsFrontToBack(&stack).begin())); + } +} + +void tst_QWasmWindowStack::stayAboveParentRegular() +{ + QWasmWindow windows[5]; + windows[4].m_transientParent = &windows[1]; + windows[4].m_windowFlags = Qt::Tool; + + QWasmWindowStack stack(m_mockCallback); + + stack.pushWindow(windows + 0, QWasmWindowStack::PositionPreference::StayOnBottom); + stack.pushWindow(windows + 1, QWasmWindowStack::PositionPreference::Regular); + stack.pushWindow(windows + 2, QWasmWindowStack::PositionPreference::Regular); + stack.pushWindow(windows + 3, QWasmWindowStack::PositionPreference::StayOnTop); + stack.pushWindow(windows + 4, QWasmWindowStack::PositionPreference::StayAboveTransientParent); + + { + const std::vector expectedWindowOrder = { + windows + 3, + windows + 2, + windows + 4, + windows + 1, + windows + 0 + }; + + qDebug() << LogWindows(windows + 0, stack); + + QVERIFY(std::equal(expectedWindowOrder.begin(), expectedWindowOrder.end(), + getWindowsFrontToBack(&stack).begin())); + } + { + stack.windowPositionPreferenceChanged( + windows + 4, + QWasmWindowStack::PositionPreference::StayOnTop); + + const std::vector expectedWindowOrder = { + windows + 4, + windows + 3, + windows + 2, + windows + 1, + windows + 0 + }; + + qDebug() << LogWindows(windows + 0, stack); + + QVERIFY(std::equal(expectedWindowOrder.begin(), expectedWindowOrder.end(), + getWindowsFrontToBack(&stack).begin())); + } +} + +void tst_QWasmWindowStack::stayAboveParentOnTop() +{ + QWasmWindow windows[5]; + windows[3].m_transientParent = &windows[2]; + windows[3].m_windowFlags = Qt::Tool; + + QWasmWindowStack stack(m_mockCallback); + + stack.pushWindow(windows + 0, QWasmWindowStack::PositionPreference::StayOnBottom); + stack.pushWindow(windows + 1, QWasmWindowStack::PositionPreference::Regular); + stack.pushWindow(windows + 2, QWasmWindowStack::PositionPreference::StayOnTop); + stack.pushWindow(windows + 3, QWasmWindowStack::PositionPreference::StayAboveTransientParent); + stack.pushWindow(windows + 4, QWasmWindowStack::PositionPreference::StayOnTop); + + { + const std::vector expectedWindowOrder = { + windows + 4, + windows + 3, + windows + 2, + windows + 1, + windows + 0 + }; + + qDebug() << LogWindows(windows + 0, stack); + QVERIFY(std::equal(expectedWindowOrder.begin(), expectedWindowOrder.end(), + getWindowsFrontToBack(&stack).begin())); + } + { + stack.windowPositionPreferenceChanged( + windows + 3, + QWasmWindowStack::PositionPreference::StayOnTop); + + const std::vector expectedWindowOrder = { + windows + 4, + windows + 3, + windows + 2, + windows + 1, + windows + 0 + }; + + qDebug() << LogWindows(windows + 0, stack); + + QVERIFY(std::equal(expectedWindowOrder.begin(), expectedWindowOrder.end(), + getWindowsFrontToBack(&stack).begin())); + } +} + + QTEST_MAIN(tst_QWasmWindowStack) #include "tst_qwasmwindowstack.moc" diff --git a/tests/auto/wasm/qwasmwindowtreenode/tst_qwasmwindowtreenode.cpp b/tests/auto/wasm/qwasmwindowtreenode/tst_qwasmwindowtreenode.cpp index 763dbf9a073..62d8114befb 100644 --- a/tests/auto/wasm/qwasmwindowtreenode/tst_qwasmwindowtreenode.cpp +++ b/tests/auto/wasm/qwasmwindowtreenode/tst_qwasmwindowtreenode.cpp @@ -6,28 +6,33 @@ #include #include -class QWasmWindow +class TestQWindow { +public: + int flags() { return 0; } + QVariant property(const char *) { return QVariant(); } }; +class TestWindowTreeNode; + using OnSubtreeChangedCallback = std::function; -using SetWindowZOrderCallback = std::function; + QWasmWindowTreeNodeChangeType changeType, QWasmWindowTreeNode *parent, TestWindowTreeNode *child)>; +using SetWindowZOrderCallback = std::function; struct OnSubtreeChangedCallData { QWasmWindowTreeNodeChangeType changeType; - QWasmWindowTreeNode *parent; - QWasmWindow *child; + QWasmWindowTreeNode *parent; + TestWindowTreeNode *child; }; struct SetWindowZOrderCallData { - QWasmWindow *window; + TestWindowTreeNode *window; int z; }; -class TestWindowTreeNode final : public QWasmWindowTreeNode, public QWasmWindow +class TestWindowTreeNode final : public QWasmWindowTreeNode { public: TestWindowTreeNode(OnSubtreeChangedCallback onSubtreeChangedCallback, @@ -42,7 +47,7 @@ public: { auto *previous = m_parent; m_parent = parent; - onParentChanged(previous, parent, QWasmWindowStack::PositionPreference::Regular); + onParentChanged(previous, parent, QWasmWindowStack::PositionPreference::Regular); } void setContainerElement(emscripten::val container) { m_containerElement = container; } @@ -51,30 +56,42 @@ public: void sendToBottom() { QWasmWindowTreeNode::sendToBottom(); } - const QWasmWindowStack &childStack() { return QWasmWindowTreeNode::childStack(); } + const QWasmWindowStack &childStack() { return QWasmWindowTreeNode::childStack(); } emscripten::val containerElement() final { return m_containerElement; } QWasmWindowTreeNode *parentNode() final { return m_parent; } - QWasmWindow *asWasmWindow() final { return this; } + TestWindowTreeNode *asWasmWindow() final { return this; } + TestWindowTreeNode *transientParent() const { + return nullptr; + } + TestQWindow *window() { return &m_qWindow; } + void requestActivateWindow() { ; } + void setZOrder(int) { ; } + bool isModal() const { return false; } + Qt::WindowFlags windowFlags() const { return Qt::WindowFlags(); } protected: - void onSubtreeChanged(QWasmWindowTreeNodeChangeType changeType, QWasmWindowTreeNode *parent, - QWasmWindow *child) final + void onSubtreeChanged(QWasmWindowTreeNodeChangeType changeType, QWasmWindowTreeNode *parent, + TestWindowTreeNode *child) final { m_onSubtreeChangedCallback(changeType, parent, child); } - void setWindowZOrder(QWasmWindow *window, int z) final { m_setWindowZOrderCallback(window, z); } + void setWindowZOrder(TestWindowTreeNode *window, int z) final { m_setWindowZOrderCallback(window, z); } TestWindowTreeNode *m_parent = nullptr; emscripten::val m_containerElement = emscripten::val::undefined(); OnSubtreeChangedCallback m_onSubtreeChangedCallback; SetWindowZOrderCallback m_setWindowZOrderCallback; + TestQWindow m_qWindow; }; +#define QWasmWindowTreeNode QWasmWindowTreeNode +#define QWasmWindow TestWindowTreeNode + class tst_QWasmWindowTreeNode : public QObject { Q_OBJECT