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 qwasmwindowtreenode.cpp qwasmwindowtreenode.h
qwasmwindownonclientarea.cpp qwasmwindownonclientarea.h qwasmwindownonclientarea.cpp qwasmwindownonclientarea.h
qwasminputcontext.cpp qwasminputcontext.h qwasminputcontext.cpp qwasminputcontext.h
qwasmwindowstack.cpp qwasmwindowstack.h qwasmwindowstack.h
DEFINES DEFINES
QT_EGL_NO_X11 QT_EGL_NO_X11
QT_NO_FOREACH QT_NO_FOREACH

View File

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

View File

@ -25,7 +25,7 @@ class QWasmCompositor;
class QWasmDeadKeySupport; class QWasmDeadKeySupport;
class QOpenGLContext; class QOpenGLContext;
class QWasmScreen : public QObject, public QPlatformScreen, public QWasmWindowTreeNode class QWasmScreen : public QObject, public QPlatformScreen, public QWasmWindowTreeNode<>
{ {
Q_OBJECT Q_OBJECT
public: public:

View File

@ -37,17 +37,6 @@
QT_BEGIN_NAMESPACE 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(); Q_GUI_EXPORT int qt_defaultDpiX();
QWasmWindow::QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport, QWasmWindow::QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport,
@ -132,6 +121,16 @@ QWasmWindow::QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport,
registerEventHandlers(); 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()); setParent(parent());
} }
@ -214,6 +213,9 @@ QWasmWindow::~QWasmWindow()
#if QT_CONFIG(accessibility) #if QT_CONFIG(accessibility)
QWasmAccessibility::onRemoveWindow(window()); QWasmAccessibility::onRemoveWindow(window());
#endif #endif
QObject::disconnect(m_transientWindowChangedConnection);
QObject::disconnect(m_modalityChangedConnection);
shutdown(); shutdown();
emscripten::val::module_property("specialHTMLTargets").delete_(canvasSelector()); emscripten::val::module_property("specialHTMLTargets").delete_(canvasSelector());
@ -225,6 +227,37 @@ QWasmWindow::~QWasmWindow()
emscripten_cancel_animation_frame(m_requestAnimationFrameId); 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 QSurfaceFormat QWasmWindow::format() const
{ {
return window()->requestedFormat(); return window()->requestedFormat();
@ -237,6 +270,24 @@ QWasmWindow *QWasmWindow::fromWindow(const QWindow *window)
return static_cast<QWasmWindow *>(window->handle()); 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() void QWasmWindow::onRestoreClicked()
{ {
window()->setWindowState(Qt::WindowNoState); window()->setWindowState(Qt::WindowNoState);
@ -471,30 +522,14 @@ void QWasmWindow::onActivationChanged(bool active)
dom::syncCSSClassWith(m_decoratedWindow, "inactive", !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) void QWasmWindow::setWindowFlags(Qt::WindowFlags flags)
{ {
flags = fixTopLevelWindowFlags(flags); flags = fixTopLevelWindowFlags(flags);
if (flags.testFlag(Qt::WindowStaysOnTopHint) != m_flags.testFlag(Qt::WindowStaysOnTopHint) if ((flags.testFlag(Qt::WindowStaysOnTopHint) != m_flags.testFlag(Qt::WindowStaysOnTopHint))
|| flags.testFlag(Qt::WindowStaysOnBottomHint) || (flags.testFlag(Qt::WindowStaysOnBottomHint)
!= m_flags.testFlag(Qt::WindowStaysOnBottomHint)) { != m_flags.testFlag(Qt::WindowStaysOnBottomHint))
|| shouldBeAboveTransientParentFlags(flags) != shouldBeAboveTransientParentFlags(m_flags)) {
onPositionPreferenceChanged(positionPreferenceFromWindowFlags(flags)); onPositionPreferenceChanged(positionPreferenceFromWindowFlags(flags));
} }
m_flags = flags; m_flags = flags;
@ -886,6 +921,55 @@ bool QWasmWindow::processWheel(const WheelEvent &event)
Qt::MouseEventNotSynthesized, event.webkitDirectionInvertedFromDevice); 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 QRect QWasmWindow::normalGeometry() const
{ {
return m_normalGeometry; return m_normalGeometry;
@ -996,6 +1080,22 @@ void QWasmWindow::setMask(const QRegion &region)
m_decoratedWindow["style"].set("clipPath", emscripten::val(cssClipPath.str())); 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 *) void QWasmWindow::setParent(const QPlatformWindow *)
{ {
// The window flags depend on whether we are a // The window flags depend on whether we are a
@ -1015,7 +1115,7 @@ emscripten::val QWasmWindow::containerElement()
return m_window; return m_window;
} }
QWasmWindowTreeNode *QWasmWindow::parentNode() QWasmWindowTreeNode<> *QWasmWindow::parentNode()
{ {
if (parent()) if (parent())
return static_cast<QWasmWindow *>(parent()); return static_cast<QWasmWindow *>(parent());
@ -1028,7 +1128,7 @@ QWasmWindow *QWasmWindow::asWasmWindow()
} }
void QWasmWindow::onParentChanged(QWasmWindowTreeNode *previous, QWasmWindowTreeNode *current, void QWasmWindow::onParentChanged(QWasmWindowTreeNode *previous, QWasmWindowTreeNode *current,
QWasmWindowStack::PositionPreference positionPreference) QWasmWindowStack<>::PositionPreference positionPreference)
{ {
if (previous) if (previous)
previous->containerElement().call<void>("removeChild", m_decoratedWindow); previous->containerElement().call<void>("removeChild", m_decoratedWindow);

View File

@ -37,7 +37,7 @@ struct WheelEvent;
Q_DECLARE_LOGGING_CATEGORY(qLcQpaWasmInputContext) Q_DECLARE_LOGGING_CATEGORY(qLcQpaWasmInputContext)
class QWasmWindow final : public QPlatformWindow, class QWasmWindow final : public QPlatformWindow,
public QWasmWindowTreeNode, public QWasmWindowTreeNode<>,
public QNativeInterface::Private::QWasmWindow public QNativeInterface::Private::QWasmWindow
{ {
public: public:
@ -46,6 +46,9 @@ public:
~QWasmWindow() final; ~QWasmWindow() final;
static QWasmWindow *fromWindow(const QWindow *window); static QWasmWindow *fromWindow(const QWindow *window);
QWasmWindow *transientParent() const;
Qt::WindowFlags windowFlags() const;
bool isModal() const;
QSurfaceFormat format() const override; QSurfaceFormat format() const override;
void registerEventHandlers(); void registerEventHandlers();
@ -106,15 +109,23 @@ public:
emscripten::val containerElement() final; emscripten::val containerElement() final;
QWasmWindowTreeNode *parentNode() final; QWasmWindowTreeNode *parentNode() final;
public slots:
void onTransientParentChanged(QWindow *newTransientParent);
void onModalityChanged();
private: private:
friend class QWasmScreen; friend class QWasmScreen;
static constexpr auto defaultWindowSize = 160; static constexpr auto defaultWindowSize = 160;
QMetaObject::Connection m_transientWindowChangedConnection;
QMetaObject::Connection m_modalityChangedConnection;
// QWasmWindowTreeNode: // QWasmWindowTreeNode:
QWasmWindow *asWasmWindow() final; QWasmWindow *asWasmWindow() final;
void onParentChanged(QWasmWindowTreeNode *previous, QWasmWindowTreeNode *current, void onParentChanged(QWasmWindowTreeNode *previous, QWasmWindowTreeNode *current,
QWasmWindowStack::PositionPreference positionPreference) final; QWasmWindowStack<>::PositionPreference positionPreference) final;
void shutdown();
void invalidate(); void invalidate();
bool hasFrame() const; bool hasFrame() const;
bool hasTitleBar() const; bool hasTitleBar() const;
@ -134,6 +145,9 @@ private:
bool deliverPointerEvent(const PointerEvent &event); bool deliverPointerEvent(const PointerEvent &event);
void handleWheelEvent(const emscripten::val &event); void handleWheelEvent(const emscripten::val &event);
bool processWheel(const WheelEvent &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; QWasmCompositor *m_compositor = nullptr;
QWasmBackingStore *m_backingStore = 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 <qglobal.h>
#include <QtCore/qlist.h> #include <QtCore/qlist.h>
#include <QDebug>
#include <vector> #include <vector>
@ -21,31 +22,49 @@ class QWasmWindow;
// Access to the top element is facilitated by |topWindow|. // Access to the top element is facilitated by |topWindow|.
// Changes to the top element are signaled via the |topWindowChangedCallback| supplied at // Changes to the top element are signaled via the |topWindowChangedCallback| supplied at
// construction. // construction.
// Requirement Window
//
// type Window {
// Window *transientParent() const;
// Qt::WindowFlags windowFlags() const;
// bool isModal() const;
// };
template <typename Window=QWasmWindow>
class Q_AUTOTEST_EXPORT QWasmWindowStack class Q_AUTOTEST_EXPORT QWasmWindowStack
{ {
private:
QWasmWindowStack(const QWasmWindowStack &) = delete;
QWasmWindowStack(QWasmWindowStack &&) = delete;
QWasmWindowStack &operator=(const QWasmWindowStack &) = delete;
QWasmWindowStack &&operator=(QWasmWindowStack &&) = delete;
public: 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 { enum class PositionPreference {
StayOnBottom, StayOnBottom,
Regular, Regular,
StayOnTop, 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); explicit QWasmWindowStack(WindowOrderChangedCallbackType topWindowChangedCallback);
~QWasmWindowStack(); ~QWasmWindowStack();
void pushWindow(QWasmWindow *window, PositionPreference position); void pushWindow(Window *window, PositionPreference position, bool insertAtRegionBegin = false,
void removeWindow(QWasmWindow *window); bool callCallbacks = true);
void raise(QWasmWindow *window); void removeWindow(Window *window, bool callCallbacks = true);
void lower(QWasmWindow *window); void raise(Window *window);
void windowPositionPreferenceChanged(QWasmWindow *window, PositionPreference position); void lower(Window *window);
void windowPositionPreferenceChanged(Window *window, PositionPreference position);
// Iterates top-to-bottom // Iterates top-to-bottom
iterator begin(); iterator begin();
@ -59,17 +78,25 @@ public:
bool empty() const; bool empty() const;
size_t size() const; size_t size() const;
QWasmWindow *topWindow() const; Window *topWindow() const;
PositionPreference getWindowPositionPreference(typename StorageType::const_iterator windowIt,
bool testStayAbove = true) const;
private: 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; WindowOrderChangedCallbackType m_windowOrderChangedCallback;
QList<QWasmWindow *> m_windowStack;
StorageType::iterator m_regularWindowsBegin; StorageType m_windowStack;
StorageType::iterator m_alwaysOnTopWindowsBegin; typename StorageType::iterator m_regularWindowsBegin;
typename StorageType::iterator m_alwaysOnTopWindowsBegin;
}; };
#include "qwasmwindowstack.inc"
QT_END_NAMESPACE QT_END_NAMESPACE
#endif // QWASMWINDOWSTACK_H #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 "qwasmwindowtreenode.h"
#include "qwasmwindow.h" uint64_t QWasmWindowTreeNodeBase::s_nextActiveIndex = 0;
#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();
}

View File

@ -6,6 +6,8 @@
#include "qwasmwindowstack.h" #include "qwasmwindowstack.h"
#include <QVariant>
namespace emscripten { namespace emscripten {
class val; class val;
} }
@ -17,7 +19,14 @@ enum class QWasmWindowTreeNodeChangeType {
NodeRemoval, NodeRemoval,
}; };
class QWasmWindowTreeNode class QWasmWindowTreeNodeBase
{
protected:
static uint64_t s_nextActiveIndex;
};
template<class Window = QWasmWindow>
class QWasmWindowTreeNode : public QWasmWindowTreeNodeBase
{ {
public: public:
QWasmWindowTreeNode(); QWasmWindowTreeNode();
@ -28,20 +37,20 @@ public:
protected: protected:
virtual void onParentChanged(QWasmWindowTreeNode *previous, QWasmWindowTreeNode *current, virtual void onParentChanged(QWasmWindowTreeNode *previous, QWasmWindowTreeNode *current,
QWasmWindowStack::PositionPreference positionPreference); typename QWasmWindowStack<Window>::PositionPreference positionPreference);
virtual QWasmWindow *asWasmWindow(); virtual Window *asWasmWindow();
virtual void onSubtreeChanged(QWasmWindowTreeNodeChangeType changeType, virtual void onSubtreeChanged(QWasmWindowTreeNodeChangeType changeType,
QWasmWindowTreeNode *parent, QWasmWindow *child); QWasmWindowTreeNode *parent, Window *child);
virtual void setWindowZOrder(QWasmWindow *window, int z); virtual void setWindowZOrder(Window *window, int z);
void onPositionPreferenceChanged(QWasmWindowStack::PositionPreference positionPreference); void onPositionPreferenceChanged(typename QWasmWindowStack<Window>::PositionPreference positionPreference);
void setAsActiveNode(); void setAsActiveNode();
void bringToTop(); void bringToTop();
void sendToBottom(); void sendToBottom();
void shutdown();
const QWasmWindowStack &childStack() const { return m_childStack; } const QWasmWindowStack<Window> &childStack() const { return m_childStack; }
QWasmWindow *activeChild() const { return m_activeChild; } QWasmWindowStack<Window> &childStack() { return m_childStack; }
Window *activeChild() const { return m_activeChild; }
uint64_t getActiveIndex() const { uint64_t getActiveIndex() const {
return m_activeIndex; return m_activeIndex;
@ -49,13 +58,13 @@ protected:
private: private:
void onTopWindowChanged(); void onTopWindowChanged();
void setActiveChildNode(QWasmWindow *activeChild); void setActiveChildNode(Window *activeChild);
uint64_t m_activeIndex = 0; uint64_t m_activeIndex = 0;
static uint64_t s_nextActiveIndex;
QWasmWindowStack m_childStack; QWasmWindowStack<Window> m_childStack;
QWasmWindow *m_activeChild = nullptr; 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 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "../../../../src/plugins/platforms/wasm/qwasmwindowstack.h" #include "../../../../src/plugins/platforms/wasm/qwasmwindowstack.h"
#include <QtGui/QWindow> #include <QtGui/QWindow>
#include <QTest> #include <QTest>
#include <emscripten/val.h> #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 { 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) std::vector<QWasmWindow *> getWindowsFrontToBack(const QWasmWindowStack *stack)
{ {
return std::vector<QWasmWindow *>(stack->begin(), stack->end()); return std::vector<QWasmWindow *>(stack->begin(), stack->end());
@ -42,6 +102,11 @@ private slots:
void removingWithAlwaysOnTop(); void removingWithAlwaysOnTop();
void positionPreferenceChanges(); void positionPreferenceChanges();
void clearing(); void clearing();
void stayAboveParentOnBottom1();
void stayAboveParentOnBottom2();
void stayAboveParentOnBottom3();
void stayAboveParentRegular();
void stayAboveParentOnTop();
private: private:
void onTopWindowChanged() void onTopWindowChanged()
@ -97,7 +162,6 @@ void tst_QWasmWindowStack::insertion()
void tst_QWasmWindowStack::raising() void tst_QWasmWindowStack::raising()
{ {
QWasmWindowStack stack(m_mockCallback); QWasmWindowStack stack(m_mockCallback);
stack.pushWindow(&m_root, QWasmWindowStack::PositionPreference::StayOnBottom); stack.pushWindow(&m_root, QWasmWindowStack::PositionPreference::StayOnBottom);
stack.pushWindow(&m_window1, QWasmWindowStack::PositionPreference::Regular); stack.pushWindow(&m_window1, QWasmWindowStack::PositionPreference::Regular);
stack.pushWindow(&m_window2, 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); stack.pushWindow(&m_window5, QWasmWindowStack::PositionPreference::Regular);
clearCallbackCounter(); clearCallbackCounter();
QCOMPARE(&m_window5, stack.topWindow()); QCOMPARE(&m_window5, stack.topWindow());
m_onTopLevelChangedAction = [this, &stack]() { QVERIFY(stack.topWindow() == &m_window1); }; m_onTopLevelChangedAction = [this, &stack]() { QVERIFY(stack.topWindow() == &m_window1); };
@ -710,5 +773,254 @@ void tst_QWasmWindowStack::clearing()
QCOMPARE(0u, stack.size()); 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) QTEST_MAIN(tst_QWasmWindowStack)
#include "tst_qwasmwindowstack.moc" #include "tst_qwasmwindowstack.moc"

View File

@ -6,28 +6,33 @@
#include <QTest> #include <QTest>
#include <emscripten/val.h> #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( using OnSubtreeChangedCallback = std::function<void(
QWasmWindowTreeNodeChangeType changeType, QWasmWindowTreeNode *parent, QWasmWindow *child)>; QWasmWindowTreeNodeChangeType changeType, QWasmWindowTreeNode<TestWindowTreeNode> *parent, TestWindowTreeNode *child)>;
using SetWindowZOrderCallback = std::function<void(QWasmWindow *window, int z)>; using SetWindowZOrderCallback = std::function<void(TestWindowTreeNode *window, int z)>;
struct OnSubtreeChangedCallData struct OnSubtreeChangedCallData
{ {
QWasmWindowTreeNodeChangeType changeType; QWasmWindowTreeNodeChangeType changeType;
QWasmWindowTreeNode *parent; QWasmWindowTreeNode<TestWindowTreeNode> *parent;
QWasmWindow *child; TestWindowTreeNode *child;
}; };
struct SetWindowZOrderCallData struct SetWindowZOrderCallData
{ {
QWasmWindow *window; TestWindowTreeNode *window;
int z; int z;
}; };
class TestWindowTreeNode final : public QWasmWindowTreeNode, public QWasmWindow class TestWindowTreeNode final : public QWasmWindowTreeNode<TestWindowTreeNode>
{ {
public: public:
TestWindowTreeNode(OnSubtreeChangedCallback onSubtreeChangedCallback, TestWindowTreeNode(OnSubtreeChangedCallback onSubtreeChangedCallback,
@ -42,7 +47,7 @@ public:
{ {
auto *previous = m_parent; auto *previous = m_parent;
m_parent = 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; } void setContainerElement(emscripten::val container) { m_containerElement = container; }
@ -51,30 +56,42 @@ public:
void sendToBottom() { QWasmWindowTreeNode::sendToBottom(); } void sendToBottom() { QWasmWindowTreeNode::sendToBottom(); }
const QWasmWindowStack &childStack() { return QWasmWindowTreeNode::childStack(); } const QWasmWindowStack<TestWindowTreeNode> &childStack() { return QWasmWindowTreeNode::childStack(); }
emscripten::val containerElement() final { return m_containerElement; } emscripten::val containerElement() final { return m_containerElement; }
QWasmWindowTreeNode *parentNode() final { return m_parent; } 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: protected:
void onSubtreeChanged(QWasmWindowTreeNodeChangeType changeType, QWasmWindowTreeNode *parent, void onSubtreeChanged(QWasmWindowTreeNodeChangeType changeType, QWasmWindowTreeNode<TestWindowTreeNode> *parent,
QWasmWindow *child) final TestWindowTreeNode *child) final
{ {
m_onSubtreeChangedCallback(changeType, parent, child); 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; TestWindowTreeNode *m_parent = nullptr;
emscripten::val m_containerElement = emscripten::val::undefined(); emscripten::val m_containerElement = emscripten::val::undefined();
OnSubtreeChangedCallback m_onSubtreeChangedCallback; OnSubtreeChangedCallback m_onSubtreeChangedCallback;
SetWindowZOrderCallback m_setWindowZOrderCallback; SetWindowZOrderCallback m_setWindowZOrderCallback;
TestQWindow m_qWindow;
}; };
#define QWasmWindowTreeNode QWasmWindowTreeNode<TestWindowTreeNode>
#define QWasmWindow TestWindowTreeNode
class tst_QWasmWindowTreeNode : public QObject class tst_QWasmWindowTreeNode : public QObject
{ {
Q_OBJECT Q_OBJECT