wasm: Fix stacking order problem for transient parent windows

Windows with a transient parent does not reflect the relationship
in the stacking order. Essentially AboveTransientParent is missing
as a configuration choice.

What makes this slightly convoluted is that the window stack
does not depend on the window (for testability).

We solve this problem by making the stack and treenode templates,
and provide test class as arguments when testing.

QWasmWindow and QWasmScreen are not templated as before.

There is also a new order type StayAboveTransientParent. Which
means that we can no longer use order type to get to the
group location (Since StayAboveTransientParent can map to either
of the three types).

The window stack tests have been updated to handle the
StayAboveTransientParent type.

Finally, we do not do anything with a normal parent
child relationship as this should already work
correctly.

Fixes: QTBUG-131699
Change-Id: Ie08e18f9e0a2339175c4a09da0a831f031df71e1
Reviewed-by: Lorn Potter <lorn.potter@qt.io>
This commit is contained in:
Even Oscar Andersen 2025-05-23 12:37:25 +02:00
parent 3ad9d5777f
commit e48c19449e
13 changed files with 1079 additions and 436 deletions

View File

@ -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

View File

@ -325,7 +325,7 @@ emscripten::val QWasmScreen::containerElement()
return m_shadowContainer;
}
QWasmWindowTreeNode *QWasmScreen::parentNode()
QWasmWindowTreeNode<> *QWasmScreen::parentNode()
{
return nullptr;
}

View File

@ -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:

View File

@ -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<uint64_t, QWasmWindow *> 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<QWasmWindow *>(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 &region)
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<QWasmWindow *>(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<void>("removeChild", m_decoratedWindow);

View File

@ -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;

View File

@ -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<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()
{
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

View File

@ -6,6 +6,7 @@
#include <qglobal.h>
#include <QtCore/qlist.h>
#include <QDebug>
#include <vector>
@ -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 <typename Window=QWasmWindow>
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<void()>;
using StorageType = QList<QWasmWindow *>;
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<void()>;
using StorageType = QList<Window *>;
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<QWasmWindow *> 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

View File

@ -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 <typename Window>
QWasmWindowStack<Window>::QWasmWindowStack(
WindowOrderChangedCallbackType windowOrderChangedCallback)
: m_windowOrderChangedCallback(std::move(windowOrderChangedCallback)),
m_regularWindowsBegin(m_windowStack.begin()),
m_alwaysOnTopWindowsBegin(m_windowStack.begin())
{
invariant();
}
template <typename Window>
QWasmWindowStack<Window>::~QWasmWindowStack()
{
invariant();
}
template <typename Window>
void QWasmWindowStack<Window>::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 <typename Window>
void QWasmWindowStack<Window>::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<Window>::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 <typename Window>
void QWasmWindowStack<Window>::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 <typename Window>
void QWasmWindowStack<Window>::raise(Window *window)
{
if (raiseImpl(window))
m_windowOrderChangedCallback();
}
template <typename Window>
bool QWasmWindowStack<Window>::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<Window *> 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 <typename Window>
void QWasmWindowStack<Window>::lower(Window *window)
{
if (lowerImpl(window))
m_windowOrderChangedCallback();
}
template <typename Window>
bool QWasmWindowStack<Window>::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<Window *> 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 <typename Window>
void QWasmWindowStack<Window>::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 Window>
typename QWasmWindowStack<Window>::iterator QWasmWindowStack<Window>::begin()
{
return m_windowStack.rbegin();
}
template <typename Window>
typename QWasmWindowStack<Window>::iterator QWasmWindowStack<Window>::end()
{
return m_windowStack.rend();
}
template <typename Window>
typename QWasmWindowStack<Window>::const_iterator QWasmWindowStack<Window>::begin() const
{
return m_windowStack.rbegin();
}
template <typename Window>
typename QWasmWindowStack<Window>::const_iterator QWasmWindowStack<Window>::end() const
{
return m_windowStack.rend();
}
template <typename Window>
typename QWasmWindowStack<Window>::const_reverse_iterator
QWasmWindowStack<Window>::rbegin() const
{
return m_windowStack.begin();
}
template <typename Window>
typename QWasmWindowStack<Window>::const_reverse_iterator
QWasmWindowStack<Window>::rend() const
{
return m_windowStack.end();
}
template <typename Window>
bool QWasmWindowStack<Window>::empty() const
{
return m_windowStack.empty();
}
template <typename Window>
size_t QWasmWindowStack<Window>::size() const
{
return m_windowStack.size();
}
template <typename Window>
Window *QWasmWindowStack<Window>::topWindow() const
{
return m_windowStack.empty() ? nullptr : m_windowStack.last();
}
template <typename Window>
bool QWasmWindowStack<Window>::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 <typename Window>
bool QWasmWindowStack<Window>::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 Window>
typename QWasmWindowStack<Window>::PositionPreference
QWasmWindowStack<Window>::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 */

View File

@ -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<uint64_t, QWasmWindow *> 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;

View File

@ -6,6 +6,8 @@
#include "qwasmwindowstack.h"
#include <QVariant>
namespace emscripten {
class val;
}
@ -17,7 +19,14 @@ enum class QWasmWindowTreeNodeChangeType {
NodeRemoval,
};
class QWasmWindowTreeNode
class QWasmWindowTreeNodeBase
{
protected:
static uint64_t s_nextActiveIndex;
};
template<class Window = QWasmWindow>
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<Window>::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<Window>::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<Window> &childStack() const { return m_childStack; }
QWasmWindowStack<Window> &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<Window> m_childStack;
Window *m_activeChild = nullptr;
};
#endif
#endif // QWASMWINDOWTREENODE_H
#include "qwasmwindowtreenode.inc"

View File

@ -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<class Window>
QWasmWindowTreeNode<Window>::QWasmWindowTreeNode()
: m_childStack(std::bind(&QWasmWindowTreeNode<Window>::onTopWindowChanged, this))
{
}
template<class Window>
QWasmWindowTreeNode<Window>::~QWasmWindowTreeNode() = default;
template<class Window>
void QWasmWindowTreeNode<Window>::onParentChanged(QWasmWindowTreeNode *previousParent,
QWasmWindowTreeNode *currentParent,
typename QWasmWindowStack<Window>::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<class Window>
Window *QWasmWindowTreeNode<Window>::asWasmWindow()
{
return nullptr;
}
template<class Window>
void QWasmWindowTreeNode<Window>::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<class Window>
void QWasmWindowTreeNode<Window>::setWindowZOrder(Window *window, int z)
{
window->setZOrder(z);
}
template<class Window>
void QWasmWindowTreeNode<Window>::onPositionPreferenceChanged(
typename QWasmWindowStack<Window>::PositionPreference positionPreference)
{
if (parentNode()) {
parentNode()->m_childStack.windowPositionPreferenceChanged(asWasmWindow(),
positionPreference);
}
}
template<class Window>
void QWasmWindowTreeNode<Window>::setAsActiveNode()
{
if (parentNode())
parentNode()->setActiveChildNode(asWasmWindow());
// At the end, this is a recursive function
m_activeIndex = ++s_nextActiveIndex;
}
template<class Window>
void QWasmWindowTreeNode<Window>::bringToTop()
{
if (!parentNode())
return;
parentNode()->m_childStack.raise(asWasmWindow());
parentNode()->bringToTop();
}
template<class Window>
void QWasmWindowTreeNode<Window>::sendToBottom()
{
if (!parentNode())
return;
m_childStack.lower(asWasmWindow());
}
template<class Window>
void QWasmWindowTreeNode<Window>::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<class Window>
void QWasmWindowTreeNode<Window>::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 */

View File

@ -2,15 +2,75 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "../../../../src/plugins/platforms/wasm/qwasmwindowstack.h"
#include <QtGui/QWindow>
#include <QTest>
#include <emscripten/val.h>
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<TestWindow>
#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<LogWindows &>(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<QWasmWindow *> getWindowsFrontToBack(const QWasmWindowStack *stack)
{
return std::vector<QWasmWindow *>(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"

View File

@ -6,28 +6,33 @@
#include <QTest>
#include <emscripten/val.h>
class QWasmWindow
class TestQWindow
{
public:
int flags() { return 0; }
QVariant property(const char *) { return QVariant(); }
};
class TestWindowTreeNode;
using OnSubtreeChangedCallback = std::function<void(
QWasmWindowTreeNodeChangeType changeType, QWasmWindowTreeNode *parent, QWasmWindow *child)>;
using SetWindowZOrderCallback = std::function<void(QWasmWindow *window, int z)>;
QWasmWindowTreeNodeChangeType changeType, QWasmWindowTreeNode<TestWindowTreeNode> *parent, TestWindowTreeNode *child)>;
using SetWindowZOrderCallback = std::function<void(TestWindowTreeNode *window, int z)>;
struct OnSubtreeChangedCallData
{
QWasmWindowTreeNodeChangeType changeType;
QWasmWindowTreeNode *parent;
QWasmWindow *child;
QWasmWindowTreeNode<TestWindowTreeNode> *parent;
TestWindowTreeNode *child;
};
struct SetWindowZOrderCallData
{
QWasmWindow *window;
TestWindowTreeNode *window;
int z;
};
class TestWindowTreeNode final : public QWasmWindowTreeNode, public QWasmWindow
class TestWindowTreeNode final : public QWasmWindowTreeNode<TestWindowTreeNode>
{
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<TestWindowTreeNode>::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<TestWindowTreeNode> &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<TestWindowTreeNode> *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<TestWindowTreeNode>
#define QWasmWindow TestWindowTreeNode
class tst_QWasmWindowTreeNode : public QObject
{
Q_OBJECT