summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMikolaj Boc <mikolaj.boc@qt.io>2023-06-14 14:54:34 +0200
committerMikolaj Boc <mikolaj.boc@qt.io>2023-06-15 09:41:06 +0200
commitfc4fca6d9dc22839ca73898c362faff96c81214c (patch)
treea30fcc5804cf28f81631f970d54e2c6207549f9a
parenteb92d52dc7190efefae0fae89c7c6eb9e16cdd9d (diff)
Support child windows on WASM
Setting parents for WASM platform windows is now supported. This means that windows now reside in a hierarchical window tree, with the screen and individual windows being nodes (QWasmWindowTreeNode), each maintaining their own child window stack. The divs backing windows are properly reparented in response to Qt window parent changes, so that the html structure reflects what is happening in Qt. Change-Id: I55c91d90caf58714342dcd747043967ebfdf96bb Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
-rw-r--r--src/plugins/platforms/wasm/CMakeLists.txt1
-rw-r--r--src/plugins/platforms/wasm/qwasmcompositor.cpp108
-rw-r--r--src/plugins/platforms/wasm/qwasmcompositor.h21
-rw-r--r--src/plugins/platforms/wasm/qwasmcssstyle.cpp7
-rw-r--r--src/plugins/platforms/wasm/qwasmscreen.cpp45
-rw-r--r--src/plugins/platforms/wasm/qwasmscreen.h14
-rw-r--r--src/plugins/platforms/wasm/qwasmwindow.cpp114
-rw-r--r--src/plugins/platforms/wasm/qwasmwindow.h21
-rw-r--r--src/plugins/platforms/wasm/qwasmwindownonclientarea.cpp21
-rw-r--r--src/plugins/platforms/wasm/qwasmwindowtreenode.cpp104
-rw-r--r--src/plugins/platforms/wasm/qwasmwindowtreenode.h53
-rw-r--r--tests/auto/wasm/CMakeLists.txt13
-rw-r--r--tests/auto/wasm/tst_qwasmwindowtreenode.cpp257
-rw-r--r--tests/manual/wasm/qwasmwindow/qwasmwindow.py418
-rw-r--r--tests/manual/wasm/qwasmwindow/qwasmwindow_harness.cpp74
-rw-r--r--tests/manual/wasm/qwasmwindow/qwasmwindow_harness.html8
16 files changed, 1072 insertions, 207 deletions
diff --git a/src/plugins/platforms/wasm/CMakeLists.txt b/src/plugins/platforms/wasm/CMakeLists.txt
index d7c96afdaa..7cb073a552 100644
--- a/src/plugins/platforms/wasm/CMakeLists.txt
+++ b/src/plugins/platforms/wasm/CMakeLists.txt
@@ -32,6 +32,7 @@ qt_internal_add_plugin(QWasmIntegrationPlugin
qwasmtheme.cpp qwasmtheme.h
qwasmwindow.cpp qwasmwindow.h
qwasmwindowclientarea.cpp qwasmwindowclientarea.h
+ qwasmwindowtreenode.cpp qwasmwindowtreenode.h
qwasmwindownonclientarea.cpp qwasmwindownonclientarea.h
qwasminputcontext.cpp qwasminputcontext.h
qwasmwindowstack.cpp qwasmwindowstack.h
diff --git a/src/plugins/platforms/wasm/qwasmcompositor.cpp b/src/plugins/platforms/wasm/qwasmcompositor.cpp
index 92e3150738..66eaea62fa 100644
--- a/src/plugins/platforms/wasm/qwasmcompositor.cpp
+++ b/src/plugins/platforms/wasm/qwasmcompositor.cpp
@@ -8,21 +8,9 @@
#include <emscripten/html5.h>
-namespace {
+using namespace emscripten;
-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
-
-QWasmCompositor::QWasmCompositor(QWasmScreen *screen)
- : QObject(screen), m_windowStack(std::bind(&QWasmCompositor::onTopWindowChanged, this))
+QWasmCompositor::QWasmCompositor(QWasmScreen *screen) : QObject(screen)
{
QWindowSystemInterface::setSynchronousWindowSystemEvents(true);
}
@@ -37,80 +25,20 @@ QWasmCompositor::~QWasmCompositor()
m_isEnabled = false; // prevent frame() from creating a new m_context
}
-void QWasmCompositor::addWindow(QWasmWindow *window)
-{
- if (m_windowStack.empty())
- window->window()->setFlag(Qt::WindowStaysOnBottomHint);
- m_windowStack.pushWindow(window, positionPreferenceFromWindowFlags(window->window()->flags()));
- window->requestActivateWindow();
- setActive(window);
-
- updateEnabledState();
-}
-
-void QWasmCompositor::removeWindow(QWasmWindow *window)
-{
- m_requestUpdateWindows.remove(window);
- m_windowStack.removeWindow(window);
- if (m_windowStack.topWindow()) {
- m_windowStack.topWindow()->requestActivateWindow();
- setActive(m_windowStack.topWindow());
- }
-
- updateEnabledState();
-}
-
-void QWasmCompositor::setActive(QWasmWindow *window)
-{
- m_activeWindow = window;
-
- auto it = m_windowStack.begin();
- if (it == m_windowStack.end()) {
- return;
- }
- for (; it != m_windowStack.end(); ++it) {
- (*it)->onActivationChanged(*it == m_activeWindow);
- }
-}
-
-void QWasmCompositor::updateEnabledState()
-{
- m_isEnabled = std::any_of(m_windowStack.begin(), m_windowStack.end(), [](QWasmWindow *window) {
- return !window->context2d().isUndefined();
- });
-}
-
-void QWasmCompositor::raise(QWasmWindow *window)
-{
- m_windowStack.raise(window);
-}
-
-void QWasmCompositor::lower(QWasmWindow *window)
-{
- m_windowStack.lower(window);
-}
-
-void QWasmCompositor::windowPositionPreferenceChanged(QWasmWindow *window, Qt::WindowFlags flags)
-{
- m_windowStack.windowPositionPreferenceChanged(window, positionPreferenceFromWindowFlags(flags));
-}
-
-QWindow *QWasmCompositor::windowAt(QPoint targetPointInScreenCoords, int padding) const
+void QWasmCompositor::onWindowTreeChanged(QWasmWindowTreeNodeChangeType changeType,
+ QWasmWindow *window)
{
- const auto found = std::find_if(
- m_windowStack.begin(), m_windowStack.end(),
- [padding, &targetPointInScreenCoords](const QWasmWindow *window) {
- const QRect geometry = window->windowFrameGeometry().adjusted(-padding, -padding,
- padding, padding);
-
- return window->isVisible() && geometry.contains(targetPointInScreenCoords);
- });
- return found != m_windowStack.end() ? (*found)->window() : nullptr;
+ auto allWindows = screen()->allWindows();
+ setEnabled(std::any_of(allWindows.begin(), allWindows.end(), [](QWasmWindow *element) {
+ return !element->context2d().isUndefined();
+ }));
+ if (changeType == QWasmWindowTreeNodeChangeType::NodeRemoval)
+ m_requestUpdateWindows.remove(window);
}
-QWindow *QWasmCompositor::keyWindow() const
+void QWasmCompositor::setEnabled(bool enabled)
{
- return m_activeWindow ? m_activeWindow->window() : nullptr;
+ m_isEnabled = enabled;
}
void QWasmCompositor::requestUpdateWindow(QWasmWindow *window, UpdateRequestDeliveryType updateType)
@@ -159,7 +87,6 @@ void QWasmCompositor::deliverUpdateRequests()
// update type: QWindow subclasses expect that requested and delivered updateRequests matches
// exactly.
m_inDeliverUpdateRequest = true;
-
for (auto it = requestUpdateWindows.constBegin(); it != requestUpdateWindows.constEnd(); ++it) {
auto *window = it.key();
UpdateRequestDeliveryType updateType = it.value();
@@ -200,15 +127,8 @@ void QWasmCompositor::frame(const QList<QWasmWindow *> &windows)
if (!m_isEnabled || !screen())
return;
- std::for_each(windows.begin(), windows.end(), [](QWasmWindow *window) { window->paint(); });
-}
-
-void QWasmCompositor::onTopWindowChanged()
-{
- constexpr int zOrderForElementInFrontOfScreen = 3;
- int z = zOrderForElementInFrontOfScreen;
- std::for_each(m_windowStack.rbegin(), m_windowStack.rend(),
- [&z](QWasmWindow *window) { window->setZOrder(z++); });
+ for (QWasmWindow *window : windows)
+ window->paint();
}
QWasmScreen *QWasmCompositor::screen()
diff --git a/src/plugins/platforms/wasm/qwasmcompositor.h b/src/plugins/platforms/wasm/qwasmcompositor.h
index 7d3ea52b39..182ea2b167 100644
--- a/src/plugins/platforms/wasm/qwasmcompositor.h
+++ b/src/plugins/platforms/wasm/qwasmcompositor.h
@@ -15,6 +15,8 @@ QT_BEGIN_NAMESPACE
class QWasmWindow;
class QWasmScreen;
+enum class QWasmWindowTreeNodeChangeType;
+
class QWasmCompositor final : public QObject
{
Q_OBJECT
@@ -22,42 +24,29 @@ public:
QWasmCompositor(QWasmScreen *screen);
~QWasmCompositor() final;
- void addWindow(QWasmWindow *window);
- void removeWindow(QWasmWindow *window);
-
void setVisible(QWasmWindow *window, bool visible);
- void setActive(QWasmWindow *window);
- void raise(QWasmWindow *window);
- void lower(QWasmWindow *window);
- void windowPositionPreferenceChanged(QWasmWindow *window, Qt::WindowFlags flags);
- QWindow *windowAt(QPoint globalPoint, int padding = 0) const;
- QWindow *keyWindow() const;
+ void onScreenDeleting();
QWasmScreen *screen();
+ void setEnabled(bool enabled);
enum UpdateRequestDeliveryType { ExposeEventDelivery, UpdateRequestDelivery };
void requestUpdateWindow(QWasmWindow *window, UpdateRequestDeliveryType updateType = ExposeEventDelivery);
void handleBackingStoreFlush(QWindow *window);
+ void onWindowTreeChanged(QWasmWindowTreeNodeChangeType changeType, QWasmWindow *window);
private:
void frame(const QList<QWasmWindow *> &windows);
- void onTopWindowChanged();
-
void deregisterEventHandlers();
void requestUpdate();
void deliverUpdateRequests();
void deliverUpdateRequest(QWasmWindow *window, UpdateRequestDeliveryType updateType);
- void updateEnabledState();
-
- QWasmWindowStack m_windowStack;
- QWasmWindow *m_activeWindow = nullptr;
-
bool m_isEnabled = true;
QMap<QWasmWindow *, UpdateRequestDeliveryType> m_requestUpdateWindows;
int m_requestAnimationFrameId = -1;
diff --git a/src/plugins/platforms/wasm/qwasmcssstyle.cpp b/src/plugins/platforms/wasm/qwasmcssstyle.cpp
index 6ac4c8d884..e0e1a99f48 100644
--- a/src/plugins/platforms/wasm/qwasmcssstyle.cpp
+++ b/src/plugins/platforms/wasm/qwasmcssstyle.cpp
@@ -35,6 +35,11 @@ const char *Style = R"css(
background-color: lightgray;
}
+.qt-window-contents {
+ overflow: hidden;
+ position: relative;
+}
+
.qt-window.transparent-for-input {
pointer-events: none;
}
@@ -135,7 +140,7 @@ const char *Style = R"css(
padding-bottom: 4px;
}
-.qt-window.has-border .title-bar {
+.qt-window.has-border > .title-bar {
display: flex;
}
diff --git a/src/plugins/platforms/wasm/qwasmscreen.cpp b/src/plugins/platforms/wasm/qwasmscreen.cpp
index eac6ce0332..f338a7541e 100644
--- a/src/plugins/platforms/wasm/qwasmscreen.cpp
+++ b/src/plugins/platforms/wasm/qwasmscreen.cpp
@@ -199,12 +199,18 @@ void QWasmScreen::resizeMaximizedWindows()
QWindow *QWasmScreen::topWindow() const
{
- return m_compositor->keyWindow();
+ return activeChild() ? activeChild()->window() : nullptr;
}
QWindow *QWasmScreen::topLevelAt(const QPoint &p) const
{
- return m_compositor->windowAt(p);
+ const auto found =
+ std::find_if(childStack().begin(), childStack().end(), [&p](const QWasmWindow *window) {
+ const QRect geometry = window->windowFrameGeometry();
+
+ return window->isVisible() && geometry.contains(p);
+ });
+ return found != childStack().end() ? (*found)->window() : nullptr;
}
QPointF QWasmScreen::mapFromLocal(const QPointF &p) const
@@ -232,6 +238,18 @@ void QWasmScreen::setGeometry(const QRect &rect)
resizeMaximizedWindows();
}
+void QWasmScreen::onSubtreeChanged(QWasmWindowTreeNodeChangeType changeType,
+ QWasmWindowTreeNode *parent, QWasmWindow *child)
+{
+ Q_UNUSED(parent);
+ if (changeType == QWasmWindowTreeNodeChangeType::NodeInsertion && parent == this
+ && childStack().size() == 1) {
+ child->window()->setFlag(Qt::WindowStaysOnBottomHint);
+ }
+ QWasmWindowTreeNode::onSubtreeChanged(changeType, parent, child);
+ m_compositor->onWindowTreeChanged(changeType, child);
+}
+
void QWasmScreen::updateQScreenAndCanvasRenderSize()
{
// The HTML canvas has two sizes: the CSS size and the canvas render size.
@@ -305,4 +323,27 @@ void QWasmScreen::installCanvasResizeObserver()
resizeObserver.call<void>("observe", m_shadowContainer);
}
+emscripten::val QWasmScreen::containerElement()
+{
+ return m_shadowContainer;
+}
+
+QWasmWindowTreeNode *QWasmScreen::parentNode()
+{
+ return nullptr;
+}
+
+QList<QWasmWindow *> QWasmScreen::allWindows()
+{
+ QList<QWasmWindow *> windows;
+ for (auto *child : childStack()) {
+ QWindowList list = child->window()->findChildren<QWindow *>(Qt::FindChildrenRecursively);
+ std::transform(
+ list.begin(), list.end(), std::back_inserter(windows),
+ [](const QWindow *window) { return static_cast<QWasmWindow *>(window->handle()); });
+ windows.push_back(child);
+ }
+ return windows;
+}
+
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmscreen.h b/src/plugins/platforms/wasm/qwasmscreen.h
index 633cf853f7..47ce11495f 100644
--- a/src/plugins/platforms/wasm/qwasmscreen.h
+++ b/src/plugins/platforms/wasm/qwasmscreen.h
@@ -6,6 +6,8 @@
#include "qwasmcursor.h"
+#include "qwasmwindowtreenode.h"
+
#include <qpa/qplatformscreen.h>
#include <QtCore/qscopedpointer.h>
@@ -23,7 +25,7 @@ class QWasmCompositor;
class QWasmDeadKeySupport;
class QOpenGLContext;
-class QWasmScreen : public QObject, public QPlatformScreen
+class QWasmScreen : public QObject, public QPlatformScreen, public QWasmWindowTreeNode
{
Q_OBJECT
public:
@@ -41,6 +43,8 @@ public:
QWasmCompositor *compositor();
QWasmDeadKeySupport *deadKeySupport() { return m_deadKeySupport.get(); }
+ QList<QWasmWindow *> allWindows();
+
QRect geometry() const override;
int depth() const override;
QImage::Format format() const override;
@@ -53,6 +57,10 @@ public:
QWindow *topWindow() const;
QWindow *topLevelAt(const QPoint &p) const override;
+ // QWasmWindowTreeNode:
+ emscripten::val containerElement() final;
+ QWasmWindowTreeNode *parentNode() final;
+
QPointF mapFromLocal(const QPointF &p) const;
QPointF clipPoint(const QPointF &p) const;
@@ -65,6 +73,10 @@ public slots:
void setGeometry(const QRect &rect);
private:
+ // QWasmWindowTreeNode:
+ void onSubtreeChanged(QWasmWindowTreeNodeChangeType changeType, QWasmWindowTreeNode *parent,
+ QWasmWindow *child) final;
+
emscripten::val m_container;
emscripten::val m_shadowContainer;
std::unique_ptr<QWasmCompositor> m_compositor;
diff --git a/src/plugins/platforms/wasm/qwasmwindow.cpp b/src/plugins/platforms/wasm/qwasmwindow.cpp
index 755a9ac930..466b713b1c 100644
--- a/src/plugins/platforms/wasm/qwasmwindow.cpp
+++ b/src/plugins/platforms/wasm/qwasmwindow.cpp
@@ -32,6 +32,17 @@
QT_BEGIN_NAMESPACE
+namespace {
+QWasmWindowStack::PositionPreference positionPreferenceFromWindowFlags(Qt::WindowFlags flags)
+{
+ if (flags.testFlag(Qt::WindowStaysOnTopHint))
+ return QWasmWindowStack::PositionPreference::StayOnTop;
+ if (flags.testFlag(Qt::WindowStaysOnBottomHint))
+ return QWasmWindowStack::PositionPreference::StayOnBottom;
+ return QWasmWindowStack::PositionPreference::Regular;
+}
+} // namespace
+
Q_GUI_EXPORT int qt_defaultDpiX();
QWasmWindow::QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport,
@@ -56,6 +67,7 @@ QWasmWindow::QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport,
m_clientArea = std::make_unique<ClientArea>(this, compositor->screen(), m_windowContents);
+ m_windowContents.set("className", "qt-window-contents");
m_qtWindow.call<void>("appendChild", m_windowContents);
m_canvas["classList"].call<void>("add", emscripten::val("qt-window-content"));
@@ -82,8 +94,6 @@ QWasmWindow::QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport,
m_canvasContainer.call<void>("appendChild", m_a11yContainer);
m_a11yContainer["classList"].call<void>("add", emscripten::val("qt-window-a11y-container"));
- compositor->screen()->element().call<void>("appendChild", m_qtWindow);
-
const bool rendersTo2dContext = w->surfaceType() != QSurface::OpenGLSurface;
if (rendersTo2dContext)
m_context2d = m_canvas.call<emscripten::val>("getContext", emscripten::val("2d"));
@@ -92,7 +102,6 @@ QWasmWindow::QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport,
m_qtWindow.set("id", "qt-window-" + std::to_string(m_winId));
emscripten::val::module_property("specialHTMLTargets").set(canvasSelector(), m_canvas);
- m_compositor->addWindow(this);
m_flags = window()->flags();
const auto pointerCallback = std::function([this](emscripten::val event) {
@@ -125,13 +134,16 @@ QWasmWindow::QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport,
m_keyDownCallback =
std::make_unique<qstdweb::EventCallback>(m_qtWindow, "keydown", keyCallback);
m_keyUpCallback = std::make_unique<qstdweb::EventCallback>(m_qtWindow, "keyup", keyCallback);
+
+ setParent(parent());
}
QWasmWindow::~QWasmWindow()
{
emscripten::val::module_property("specialHTMLTargets").delete_(canvasSelector());
- destroy();
- m_compositor->removeWindow(this);
+ m_canvasContainer.call<void>("removeChild", m_canvas);
+ m_context2d = emscripten::val::undefined();
+ commitParent(nullptr);
if (m_requestAnimationFrameId > -1)
emscripten_cancel_animation_frame(m_requestAnimationFrameId);
#if QT_CONFIG(accessibility)
@@ -162,8 +174,7 @@ void QWasmWindow::onCloseClicked()
void QWasmWindow::onNonClientAreaInteraction()
{
- if (!isActive())
- requestActivateWindow();
+ requestActivateWindow();
QGuiApplicationPrivate::instance()->closeAllPopups();
}
@@ -178,14 +189,6 @@ bool QWasmWindow::onNonClientEvent(const PointerEvent &event)
event.modifiers);
}
-void QWasmWindow::destroy()
-{
- m_qtWindow["parentElement"].call<emscripten::val>("removeChild", m_qtWindow);
-
- m_canvasContainer.call<void>("removeChild", m_canvas);
- m_context2d = emscripten::val::undefined();
-}
-
void QWasmWindow::initialize()
{
QRect rect = windowGeometry();
@@ -258,21 +261,31 @@ void QWasmWindow::setGeometry(const QRect &rect)
if (m_state.testFlag(Qt::WindowMaximized))
return platformScreen()->availableGeometry().marginsRemoved(frameMargins());
- const auto screenGeometry = screen()->geometry();
+ auto offset = rect.topLeft() - (!parent() ? screen()->geometry().topLeft() : QPoint());
+
+ // In viewport
+ auto containerGeometryInViewport =
+ QRectF::fromDOMRect(parentNode()->containerElement().call<emscripten::val>(
+ "getBoundingClientRect"))
+ .toRect();
- QRect result(rect);
- result.moveTop(std::max(std::min(rect.y(), screenGeometry.bottom()),
- screenGeometry.y() + margins.top()));
- result.setSize(
- result.size().expandedTo(windowMinimumSize()).boundedTo(windowMaximumSize()));
- return result;
+ auto rectInViewport = QRect(containerGeometryInViewport.topLeft() + offset, rect.size());
+
+ QRect cappedGeometry(rectInViewport);
+ cappedGeometry.moveTop(
+ std::max(std::min(rectInViewport.y(), containerGeometryInViewport.bottom()),
+ containerGeometryInViewport.y() + margins.top()));
+ cappedGeometry.setSize(
+ cappedGeometry.size().expandedTo(windowMinimumSize()).boundedTo(windowMaximumSize()));
+ return QRect(QPoint(rect.x(), rect.y() + cappedGeometry.y() - rectInViewport.y()),
+ rect.size());
})();
m_nonClientArea->onClientAreaWidthChange(clientAreaRect.width());
const auto frameRect =
clientAreaRect
.adjusted(-margins.left(), -margins.top(), margins.right(), margins.bottom())
- .translated(-screen()->geometry().topLeft());
+ .translated(!parent() ? -screen()->geometry().topLeft() : QPoint());
m_qtWindow["style"].set("left", std::to_string(frameRect.left()) + "px");
m_qtWindow["style"].set("top", std::to_string(frameRect.top()) + "px");
@@ -335,13 +348,13 @@ QMargins QWasmWindow::frameMargins() const
void QWasmWindow::raise()
{
- m_compositor->raise(this);
+ bringToTop();
invalidate();
}
void QWasmWindow::lower()
{
- m_compositor->lower(this);
+ sendToBottom();
invalidate();
}
@@ -378,8 +391,11 @@ void QWasmWindow::onActivationChanged(bool active)
void QWasmWindow::setWindowFlags(Qt::WindowFlags flags)
{
- if (flags.testFlag(Qt::WindowStaysOnTopHint) != m_flags.testFlag(Qt::WindowStaysOnTopHint))
- m_compositor->windowPositionPreferenceChanged(this, flags);
+ if (flags.testFlag(Qt::WindowStaysOnTopHint) != m_flags.testFlag(Qt::WindowStaysOnTopHint)
+ || flags.testFlag(Qt::WindowStaysOnBottomHint)
+ != m_flags.testFlag(Qt::WindowStaysOnBottomHint)) {
+ onPositionPreferenceChanged(positionPreferenceFromWindowFlags(flags));
+ }
m_flags = flags;
dom::syncCSSClassWith(m_qtWindow, "has-border", hasBorder());
dom::syncCSSClassWith(m_qtWindow, "has-shadow", hasShadow());
@@ -461,6 +477,12 @@ void QWasmWindow::applyWindowState()
setGeometry(newGeom);
}
+void QWasmWindow::commitParent(QWasmWindowTreeNode *parent)
+{
+ onParentChanged(m_commitedParent, parent, positionPreferenceFromWindowFlags(window()->flags()));
+ m_commitedParent = parent;
+}
+
bool QWasmWindow::processKey(const KeyEvent &event)
{
constexpr bool ProceedToNativeEvent = false;
@@ -600,10 +622,8 @@ void QWasmWindow::requestActivateWindow()
return;
}
- if (window()->isTopLevel()) {
- raise();
- m_compositor->setActive(this);
- }
+ raise();
+ setAsActiveNode();
if (!QWasmIntegration::get()->inputContext())
m_canvas.call<void>("focus");
@@ -651,9 +671,41 @@ void QWasmWindow::setMask(const QRegion &region)
m_qtWindow["style"].set("clipPath", emscripten::val(cssClipPath.str()));
}
+void QWasmWindow::setParent(const QPlatformWindow *)
+{
+ commitParent(parentNode());
+}
+
std::string QWasmWindow::canvasSelector() const
{
return "!qtwindow" + std::to_string(m_winId);
}
+emscripten::val QWasmWindow::containerElement()
+{
+ return m_windowContents;
+}
+
+QWasmWindowTreeNode *QWasmWindow::parentNode()
+{
+ if (parent())
+ return static_cast<QWasmWindow *>(parent());
+ return platformScreen();
+}
+
+QWasmWindow *QWasmWindow::asWasmWindow()
+{
+ return this;
+}
+
+void QWasmWindow::onParentChanged(QWasmWindowTreeNode *previous, QWasmWindowTreeNode *current,
+ QWasmWindowStack::PositionPreference positionPreference)
+{
+ if (previous)
+ previous->containerElement().call<void>("removeChild", m_qtWindow);
+ if (current)
+ current->containerElement().call<void>("appendChild", m_qtWindow);
+ QWasmWindowTreeNode::onParentChanged(previous, current, positionPreference);
+}
+
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmwindow.h b/src/plugins/platforms/wasm/qwasmwindow.h
index f191c90954..0c523d3d9f 100644
--- a/src/plugins/platforms/wasm/qwasmwindow.h
+++ b/src/plugins/platforms/wasm/qwasmwindow.h
@@ -12,6 +12,8 @@
#include "qwasmscreen.h"
#include "qwasmcompositor.h"
#include "qwasmwindownonclientarea.h"
+#include "qwasmwindowstack.h"
+#include "qwasmwindowtreenode.h"
#include <QtCore/private/qstdweb_p.h>
#include "QtGui/qopenglcontext.h"
@@ -38,14 +40,15 @@ struct PointerEvent;
class QWasmDeadKeySupport;
struct WheelEvent;
-class QWasmWindow final : public QPlatformWindow, public QNativeInterface::Private::QWasmWindow
+class QWasmWindow final : public QPlatformWindow,
+ public QWasmWindowTreeNode,
+ public QNativeInterface::Private::QWasmWindow
{
public:
QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport, QWasmCompositor *compositor,
QWasmBackingStore *backingStore);
~QWasmWindow() final;
- void destroy();
void paint();
void setZOrder(int order);
void setWindowCursor(QByteArray cssCursorName);
@@ -81,6 +84,7 @@ public:
bool setMouseGrabEnabled(bool grab) final;
bool windowEvent(QEvent *event) final;
void setMask(const QRegion &region) final;
+ void setParent(const QPlatformWindow *window) final;
QWasmScreen *platformScreen() const;
void setBackingStore(QWasmBackingStore *store) { m_backingStore = store; }
@@ -88,6 +92,7 @@ public:
QWindow *window() const { return m_window; }
std::string canvasSelector() const;
+
emscripten::val context2d() const { return m_context2d; }
emscripten::val a11yContainer() const { return m_a11yContainer; }
emscripten::val inputHandlerElement() const { return m_windowContents; }
@@ -96,15 +101,25 @@ public:
emscripten::val document() const override { return m_document; }
emscripten::val clientArea() const override { return m_qtWindow; }
+ // QWasmWindowTreeNode:
+ emscripten::val containerElement() final;
+ QWasmWindowTreeNode *parentNode() final;
+
private:
friend class QWasmScreen;
static constexpr auto minSizeForRegularWindows = 100;
+ // QWasmWindowTreeNode:
+ QWasmWindow *asWasmWindow() final;
+ void onParentChanged(QWasmWindowTreeNode *previous, QWasmWindowTreeNode *current,
+ QWasmWindowStack::PositionPreference positionPreference) final;
+
void invalidate();
bool hasBorder() const;
bool hasShadow() const;
bool hasMaximizeButton() const;
void applyWindowState();
+ void commitParent(QWasmWindowTreeNode *parent);
bool processKey(const KeyEvent &event);
bool processPointer(const PointerEvent &event);
@@ -128,6 +143,8 @@ private:
std::unique_ptr<NonClientArea> m_nonClientArea;
std::unique_ptr<ClientArea> m_clientArea;
+ QWasmWindowTreeNode *m_commitedParent = nullptr;
+
std::unique_ptr<qstdweb::EventCallback> m_keyDownCallback;
std::unique_ptr<qstdweb::EventCallback> m_keyUpCallback;
diff --git a/src/plugins/platforms/wasm/qwasmwindownonclientarea.cpp b/src/plugins/platforms/wasm/qwasmwindownonclientarea.cpp
index bc597069e7..00ae1aaeb3 100644
--- a/src/plugins/platforms/wasm/qwasmwindownonclientarea.cpp
+++ b/src/plugins/platforms/wasm/qwasmwindownonclientarea.cpp
@@ -187,9 +187,11 @@ ResizeConstraints Resizer::getResizeConstraints() {
const auto frameRect =
QRectF::fromDOMRect(m_windowElement.call<emscripten::val>("getBoundingClientRect"));
- const auto screenRect = QRectF::fromDOMRect(
- m_window->platformScreen()->element().call<emscripten::val>("getBoundingClientRect"));
- const int maxGrowTop = frameRect.top() - screenRect.top();
+ auto containerGeometry =
+ QRectF::fromDOMRect(m_window->parentNode()->containerElement().call<emscripten::val>(
+ "getBoundingClientRect"));
+
+ const int maxGrowTop = frameRect.top() - containerGeometry.top();
return ResizeConstraints{minShrink, maxGrow, maxGrowTop};
}
@@ -211,6 +213,7 @@ void Resizer::startResize(Qt::Edges resizeEdges, const PointerEvent &event)
const auto resizeConstraints = getResizeConstraints();
m_currentResizeData->minShrink = resizeConstraints.minShrink;
+
m_currentResizeData->maxGrow =
QPoint(resizeConstraints.maxGrow.x(),
std::min(resizeEdges & Qt::Edge::TopEdge ? resizeConstraints.maxGrowTop : INT_MAX,
@@ -415,9 +418,15 @@ bool TitleBar::onDoubleClick()
QPointF TitleBar::clipPointWithScreen(const QPointF &pointInTitleBarCoords) const
{
- auto *screen = m_window->platformScreen();
- return screen->clipPoint(screen->mapFromLocal(
- dom::mapPoint(m_element, screen->element(), pointInTitleBarCoords)));
+ auto containerRect =
+ QRectF::fromDOMRect(m_window->parentNode()->containerElement().call<emscripten::val>(
+ "getBoundingClientRect"));
+ const auto p = dom::mapPoint(m_element, m_window->parentNode()->containerElement(),
+ pointInTitleBarCoords);
+
+ auto result = QPointF(qBound(0., qreal(p.x()), containerRect.width()),
+ qBound(0., qreal(p.y()), containerRect.height()));
+ return m_window->parent() ? result : m_window->platformScreen()->mapFromLocal(result).toPoint();
}
NonClientArea::NonClientArea(QWasmWindow *window, emscripten::val qtWindowElement)
diff --git a/src/plugins/platforms/wasm/qwasmwindowtreenode.cpp b/src/plugins/platforms/wasm/qwasmwindowtreenode.cpp
new file mode 100644
index 0000000000..e16410dcde
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmwindowtreenode.cpp
@@ -0,0 +1,104 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qwasmwindowtreenode.h"
+
+#include "qwasmwindow.h"
+
+QWasmWindowTreeNode::QWasmWindowTreeNode()
+ : m_childStack(std::bind(&QWasmWindowTreeNode::onTopWindowChanged, this))
+{
+}
+
+QWasmWindowTreeNode::~QWasmWindowTreeNode() = default;
+
+void QWasmWindowTreeNode::onParentChanged(QWasmWindowTreeNode *previousParent,
+ QWasmWindowTreeNode *currentParent,
+ QWasmWindowStack::PositionPreference positionPreference)
+{
+ auto *window = asWasmWindow();
+ if (previousParent) {
+ previousParent->m_childStack.removeWindow(window);
+ previousParent->onSubtreeChanged(QWasmWindowTreeNodeChangeType::NodeRemoval, previousParent,
+ window);
+ }
+
+ if (currentParent) {
+ currentParent->m_childStack.pushWindow(window, positionPreference);
+ currentParent->onSubtreeChanged(QWasmWindowTreeNodeChangeType::NodeInsertion, currentParent,
+ window);
+ }
+}
+
+QWasmWindow *QWasmWindowTreeNode::asWasmWindow()
+{
+ return nullptr;
+}
+
+void QWasmWindowTreeNode::onSubtreeChanged(QWasmWindowTreeNodeChangeType changeType,
+ QWasmWindowTreeNode *parent, QWasmWindow *child)
+{
+ if (changeType == QWasmWindowTreeNodeChangeType::NodeInsertion && parent == this
+ && m_childStack.topWindow()) {
+ m_childStack.topWindow()->requestActivateWindow();
+ }
+
+ if (parentNode())
+ parentNode()->onSubtreeChanged(changeType, parent, child);
+}
+
+void QWasmWindowTreeNode::setWindowZOrder(QWasmWindow *window, int z)
+{
+ window->setZOrder(z);
+}
+
+void QWasmWindowTreeNode::onPositionPreferenceChanged(
+ QWasmWindowStack::PositionPreference positionPreference)
+{
+ if (parentNode()) {
+ parentNode()->m_childStack.windowPositionPreferenceChanged(asWasmWindow(),
+ positionPreference);
+ }
+}
+
+void QWasmWindowTreeNode::setAsActiveNode()
+{
+ if (parentNode())
+ parentNode()->setActiveChildNode(asWasmWindow());
+}
+
+void QWasmWindowTreeNode::bringToTop()
+{
+ if (!parentNode())
+ return;
+ parentNode()->m_childStack.raise(asWasmWindow());
+ parentNode()->bringToTop();
+}
+
+void QWasmWindowTreeNode::sendToBottom()
+{
+ if (!parentNode())
+ return;
+ m_childStack.lower(asWasmWindow());
+}
+
+void QWasmWindowTreeNode::onTopWindowChanged()
+{
+ constexpr int zOrderForElementInFrontOfScreen = 3;
+ int z = zOrderForElementInFrontOfScreen;
+ std::for_each(m_childStack.rbegin(), m_childStack.rend(),
+ [this, &z](QWasmWindow *window) { setWindowZOrder(window, z++); });
+}
+
+void QWasmWindowTreeNode::setActiveChildNode(QWasmWindow *activeChild)
+{
+ m_activeChild = activeChild;
+
+ auto it = m_childStack.begin();
+ if (it == m_childStack.end())
+ return;
+ for (; it != m_childStack.end(); ++it)
+ (*it)->onActivationChanged(*it == m_activeChild);
+
+ setAsActiveNode();
+}
diff --git a/src/plugins/platforms/wasm/qwasmwindowtreenode.h b/src/plugins/platforms/wasm/qwasmwindowtreenode.h
new file mode 100644
index 0000000000..344fdb43cb
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmwindowtreenode.h
@@ -0,0 +1,53 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QWASMWINDOWTREENODE_H
+#define QWASMWINDOWTREENODE_H
+
+#include "qwasmwindowstack.h"
+
+namespace emscripten {
+class val;
+}
+
+class QWasmWindow;
+
+enum class QWasmWindowTreeNodeChangeType {
+ NodeInsertion,
+ NodeRemoval,
+};
+
+class QWasmWindowTreeNode
+{
+public:
+ QWasmWindowTreeNode();
+ virtual ~QWasmWindowTreeNode();
+
+ virtual emscripten::val containerElement() = 0;
+ virtual QWasmWindowTreeNode *parentNode() = 0;
+
+protected:
+ virtual void onParentChanged(QWasmWindowTreeNode *previous, QWasmWindowTreeNode *current,
+ QWasmWindowStack::PositionPreference positionPreference);
+ virtual QWasmWindow *asWasmWindow();
+ virtual void onSubtreeChanged(QWasmWindowTreeNodeChangeType changeType,
+ QWasmWindowTreeNode *parent, QWasmWindow *child);
+ virtual void setWindowZOrder(QWasmWindow *window, int z);
+
+ void onPositionPreferenceChanged(QWasmWindowStack::PositionPreference positionPreference);
+ void setAsActiveNode();
+ void bringToTop();
+ void sendToBottom();
+
+ const QWasmWindowStack &childStack() const { return m_childStack; }
+ QWasmWindow *activeChild() const { return m_activeChild; }
+
+private:
+ void onTopWindowChanged();
+ void setActiveChildNode(QWasmWindow *activeChild);
+
+ QWasmWindowStack m_childStack;
+ QWasmWindow *m_activeChild = nullptr;
+};
+
+#endif // QWASMWINDOWTREENODE_H
diff --git a/tests/auto/wasm/CMakeLists.txt b/tests/auto/wasm/CMakeLists.txt
index 47031037e0..0d67ae8e79 100644
--- a/tests/auto/wasm/CMakeLists.txt
+++ b/tests/auto/wasm/CMakeLists.txt
@@ -30,6 +30,19 @@ qt_internal_add_test(tst_qwasmwindowstack
Qt::Widgets
)
+qt_internal_add_test(tst_qwasmwindowtreenode
+ SOURCES
+ tst_qwasmwindowtreenode.cpp
+ DEFINES
+ QT_NO_FOREACH
+ LIBRARIES
+ Qt::GuiPrivate
+ PUBLIC_LIBRARIES
+ Qt::Core
+ Qt::Gui
+ Qt::Widgets
+)
+
qt_internal_add_test(tst_qwasmkeytranslator
SOURCES
tst_qwasmkeytranslator.cpp
diff --git a/tests/auto/wasm/tst_qwasmwindowtreenode.cpp b/tests/auto/wasm/tst_qwasmwindowtreenode.cpp
new file mode 100644
index 0000000000..2fd2ab2e66
--- /dev/null
+++ b/tests/auto/wasm/tst_qwasmwindowtreenode.cpp
@@ -0,0 +1,257 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "../../../src/plugins/platforms/wasm/qwasmwindowtreenode.h"
+#include <QtGui/QWindow>
+#include <QTest>
+#include <emscripten/val.h>
+
+class QWasmWindow
+{
+};
+
+using OnSubtreeChangedCallback = std::function<void(
+ QWasmWindowTreeNodeChangeType changeType, QWasmWindowTreeNode *parent, QWasmWindow *child)>;
+using SetWindowZOrderCallback = std::function<void(QWasmWindow *window, int z)>;
+
+struct OnSubtreeChangedCallData
+{
+ QWasmWindowTreeNodeChangeType changeType;
+ QWasmWindowTreeNode *parent;
+ QWasmWindow *child;
+};
+
+struct SetWindowZOrderCallData
+{
+ QWasmWindow *window;
+ int z;
+};
+
+class TestWindowTreeNode final : public QWasmWindowTreeNode, public QWasmWindow
+{
+public:
+ TestWindowTreeNode(OnSubtreeChangedCallback onSubtreeChangedCallback,
+ SetWindowZOrderCallback setWindowZOrderCallback)
+ : m_onSubtreeChangedCallback(std::move(onSubtreeChangedCallback)),
+ m_setWindowZOrderCallback(std::move(setWindowZOrderCallback))
+ {
+ }
+ ~TestWindowTreeNode() final { }
+
+ void setParent(TestWindowTreeNode *parent)
+ {
+ auto *previous = m_parent;
+ m_parent = parent;
+ onParentChanged(previous, parent, QWasmWindowStack::PositionPreference::Regular);
+ }
+
+ void setContainerElement(emscripten::val container) { m_containerElement = container; }
+
+ void bringToTop() { QWasmWindowTreeNode::bringToTop(); }
+
+ void sendToBottom() { QWasmWindowTreeNode::sendToBottom(); }
+
+ const QWasmWindowStack &childStack() { return QWasmWindowTreeNode::childStack(); }
+
+ emscripten::val containerElement() final { return m_containerElement; }
+
+ QWasmWindowTreeNode *parentNode() final { return m_parent; }
+
+ QWasmWindow *asWasmWindow() final { return this; }
+
+protected:
+ void onSubtreeChanged(QWasmWindowTreeNodeChangeType changeType, QWasmWindowTreeNode *parent,
+ QWasmWindow *child) final
+ {
+ m_onSubtreeChangedCallback(changeType, parent, child);
+ }
+
+ void setWindowZOrder(QWasmWindow *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;
+};
+
+class tst_QWasmWindowTreeNode : public QObject
+{
+ Q_OBJECT
+
+public:
+ tst_QWasmWindowTreeNode() { }
+
+private slots:
+ void init();
+
+ void nestedWindowStacks();
+ void settingChildWindowZOrder();
+};
+
+void tst_QWasmWindowTreeNode::init() { }
+
+bool operator==(const OnSubtreeChangedCallData &lhs, const OnSubtreeChangedCallData &rhs)
+{
+ return lhs.changeType == rhs.changeType && lhs.parent == rhs.parent && lhs.child == rhs.child;
+}
+
+bool operator==(const SetWindowZOrderCallData &lhs, const SetWindowZOrderCallData &rhs)
+{
+ return lhs.window == rhs.window && lhs.z == rhs.z;
+}
+
+void tst_QWasmWindowTreeNode::nestedWindowStacks()
+{
+ QList<OnSubtreeChangedCallData> calls;
+ OnSubtreeChangedCallback mockOnSubtreeChanged =
+ [&calls](QWasmWindowTreeNodeChangeType changeType, QWasmWindowTreeNode *parent,
+ QWasmWindow *child) {
+ calls.push_back(OnSubtreeChangedCallData{ changeType, parent, child });
+ };
+ SetWindowZOrderCallback ignoreSetWindowZOrder = [](QWasmWindow *, int) {};
+ TestWindowTreeNode node(mockOnSubtreeChanged, ignoreSetWindowZOrder);
+ node.bringToTop();
+
+ OnSubtreeChangedCallback ignoreSubtreeChanged = [](QWasmWindowTreeNodeChangeType,
+ QWasmWindowTreeNode *, QWasmWindow *) {};
+ TestWindowTreeNode node2(ignoreSubtreeChanged, ignoreSetWindowZOrder);
+ node2.setParent(&node);
+
+ QCOMPARE(node.childStack().size(), 1u);
+ QCOMPARE(node2.childStack().size(), 0u);
+ QCOMPARE(node.childStack().topWindow(), &node2);
+ QCOMPARE(calls.size(), 1u);
+ {
+ OnSubtreeChangedCallData expected{ QWasmWindowTreeNodeChangeType::NodeInsertion, &node,
+ &node2 };
+ QCOMPARE(calls[0], expected);
+ calls.clear();
+ }
+
+ TestWindowTreeNode node3(ignoreSubtreeChanged, ignoreSetWindowZOrder);
+ node3.setParent(&node);
+
+ QCOMPARE(node.childStack().size(), 2u);
+ QCOMPARE(node2.childStack().size(), 0u);
+ QCOMPARE(node3.childStack().size(), 0u);
+ QCOMPARE(node.childStack().topWindow(), &node3);
+ {
+ OnSubtreeChangedCallData expected{ QWasmWindowTreeNodeChangeType::NodeInsertion, &node,
+ &node3 };
+ QCOMPARE(calls[0], expected);
+ calls.clear();
+ }
+
+ TestWindowTreeNode node4(ignoreSubtreeChanged, ignoreSetWindowZOrder);
+ node4.setParent(&node);
+
+ QCOMPARE(node.childStack().size(), 3u);
+ QCOMPARE(node2.childStack().size(), 0u);
+ QCOMPARE(node3.childStack().size(), 0u);
+ QCOMPARE(node4.childStack().size(), 0u);
+ QCOMPARE(node.childStack().topWindow(), &node4);
+ {
+ OnSubtreeChangedCallData expected{ QWasmWindowTreeNodeChangeType::NodeInsertion, &node,
+ &node4 };
+ QCOMPARE(calls[0], expected);
+ calls.clear();
+ }
+
+ node3.bringToTop();
+ QCOMPARE(node.childStack().topWindow(), &node3);
+
+ node4.setParent(nullptr);
+ QCOMPARE(node.childStack().size(), 2u);
+ QCOMPARE(node.childStack().topWindow(), &node3);
+ {
+ OnSubtreeChangedCallData expected{ QWasmWindowTreeNodeChangeType::NodeRemoval, &node,
+ &node4 };
+ QCOMPARE(calls[0], expected);
+ calls.clear();
+ }
+
+ node2.setParent(nullptr);
+ QCOMPARE(node.childStack().size(), 1u);
+ QCOMPARE(node.childStack().topWindow(), &node3);
+ {
+ OnSubtreeChangedCallData expected{ QWasmWindowTreeNodeChangeType::NodeRemoval, &node,
+ &node2 };
+ QCOMPARE(calls[0], expected);
+ calls.clear();
+ }
+
+ node3.setParent(nullptr);
+ QVERIFY(node.childStack().empty());
+ QCOMPARE(node.childStack().topWindow(), nullptr);
+ {
+ OnSubtreeChangedCallData expected{ QWasmWindowTreeNodeChangeType::NodeRemoval, &node,
+ &node3 };
+ QCOMPARE(calls[0], expected);
+ calls.clear();
+ }
+}
+
+void tst_QWasmWindowTreeNode::settingChildWindowZOrder()
+{
+ QList<SetWindowZOrderCallData> calls;
+ OnSubtreeChangedCallback ignoreSubtreeChanged = [](QWasmWindowTreeNodeChangeType,
+ QWasmWindowTreeNode *, QWasmWindow *) {};
+ SetWindowZOrderCallback onSetWindowZOrder = [&calls](QWasmWindow *window, int z) {
+ calls.push_back(SetWindowZOrderCallData{ window, z });
+ };
+ SetWindowZOrderCallback ignoreSetWindowZOrder = [](QWasmWindow *, int) {};
+ TestWindowTreeNode node(ignoreSubtreeChanged, onSetWindowZOrder);
+
+ TestWindowTreeNode node2(ignoreSubtreeChanged, ignoreSetWindowZOrder);
+ node2.setParent(&node);
+
+ {
+ QCOMPARE(calls.size(), 1u);
+ SetWindowZOrderCallData expected{ &node2, 3 };
+ QCOMPARE(calls[0], expected);
+ calls.clear();
+ }
+
+ TestWindowTreeNode node3(ignoreSubtreeChanged, ignoreSetWindowZOrder);
+ node3.setParent(&node);
+
+ {
+ QCOMPARE(calls.size(), 2u);
+ SetWindowZOrderCallData expected{ &node2, 3 };
+ QCOMPARE(calls[0], expected);
+ expected = SetWindowZOrderCallData{ &node3, 4 };
+ QCOMPARE(calls[1], expected);
+ calls.clear();
+ }
+
+ TestWindowTreeNode node4(ignoreSubtreeChanged, ignoreSetWindowZOrder);
+ node4.setParent(&node);
+
+ {
+ QCOMPARE(calls.size(), 3u);
+ SetWindowZOrderCallData expected{ &node2, 3 };
+ QCOMPARE(calls[0], expected);
+ expected = SetWindowZOrderCallData{ &node3, 4 };
+ QCOMPARE(calls[1], expected);
+ expected = SetWindowZOrderCallData{ &node4, 5 };
+ QCOMPARE(calls[2], expected);
+ calls.clear();
+ }
+
+ node2.bringToTop();
+
+ {
+ QCOMPARE(calls.size(), 3u);
+ SetWindowZOrderCallData expected{ &node3, 3 };
+ QCOMPARE(calls[0], expected);
+ expected = SetWindowZOrderCallData{ &node4, 4 };
+ QCOMPARE(calls[1], expected);
+ expected = SetWindowZOrderCallData{ &node2, 5 };
+ QCOMPARE(calls[2], expected);
+ calls.clear();
+ }
+}
+
+QTEST_MAIN(tst_QWasmWindowTreeNode)
+#include "tst_qwasmwindowtreenode.moc"
diff --git a/tests/manual/wasm/qwasmwindow/qwasmwindow.py b/tests/manual/wasm/qwasmwindow/qwasmwindow.py
index 04e1611220..5bcc39361b 100644
--- a/tests/manual/wasm/qwasmwindow/qwasmwindow.py
+++ b/tests/manual/wasm/qwasmwindow/qwasmwindow.py
@@ -14,6 +14,8 @@ from selenium.webdriver.common.action_chains import ActionChains
import unittest
from enum import Enum, auto
+import time
+
class WidgetTestCase(unittest.TestCase):
def setUp(self):
self._driver = Chrome()
@@ -28,8 +30,7 @@ class WidgetTestCase(unittest.TestCase):
defaultWindowMinSize = 100
screen = Screen(self._driver, ScreenPosition.FIXED,
x=0, y=0, width=600, height=600)
- window = Window(screen, x=100, y=100, width=200, height=200)
- window.set_visible(True)
+ window = Window(parent=screen, rect=Rect(x=100, y=100, width=200, height=200))
self.assertEqual(window.rect, Rect(x=100, y=100, width=200, height=200))
window.drag(Handle.TOP_LEFT, direction=UP(10) + LEFT(10))
@@ -62,8 +63,7 @@ class WidgetTestCase(unittest.TestCase):
def test_cannot_resize_over_screen_top_edge(self):
screen = Screen(self._driver, ScreenPosition.FIXED,
x=200, y=200, width=300, height=300)
- window = Window(screen, x=300, y=300, width=100, height=100)
- window.set_visible(True)
+ window = Window(parent=screen, rect=Rect(x=300, y=300, width=100, height=100))
self.assertEqual(window.rect, Rect(x=300, y=300, width=100, height=100))
frame_rect_before_resize = window.frame_rect
@@ -77,8 +77,7 @@ class WidgetTestCase(unittest.TestCase):
def test_window_move(self):
screen = Screen(self._driver, ScreenPosition.FIXED,
x=200, y=200, width=300, height=300)
- window = Window(screen, x=300, y=300, width=100, height=100)
- window.set_visible(True)
+ window = Window(parent=screen, rect=Rect(x=300, y=300, width=100, height=100))
self.assertEqual(window.rect, Rect(x=300, y=300, width=100, height=100))
window.drag(Handle.TOP_WINDOW_BAR, direction=UP(30))
@@ -93,8 +92,7 @@ class WidgetTestCase(unittest.TestCase):
def test_screen_limits_window_moves(self):
screen = Screen(self._driver, ScreenPosition.RELATIVE,
x=200, y=200, width=300, height=300)
- window = Window(screen, x=300, y=300, width=100, height=100)
- window.set_visible(True)
+ window = Window(parent=screen, rect=Rect(x=300, y=300, width=100, height=100))
self.assertEqual(window.rect, Rect(x=300, y=300, width=100, height=100))
window.drag(Handle.TOP_WINDOW_BAR, direction=LEFT(300))
@@ -105,8 +103,7 @@ class WidgetTestCase(unittest.TestCase):
x=200, y=2000, width=300, height=300,
container_width=500, container_height=7000)
screen.scroll_to()
- window = Window(screen, x=300, y=2100, width=100, height=100)
- window.set_visible(True)
+ window = Window(parent=screen, rect=Rect(x=300, y=2100, width=100, height=100))
self.assertEqual(window.rect, Rect(x=300, y=2100, width=100, height=100))
window.drag(Handle.TOP_WINDOW_BAR, direction=LEFT(300))
@@ -115,8 +112,7 @@ class WidgetTestCase(unittest.TestCase):
def test_maximize(self):
screen = Screen(self._driver, ScreenPosition.RELATIVE,
x=200, y=200, width=300, height=300)
- window = Window(screen, x=300, y=300, width=100, height=100, title='Maximize')
- window.set_visible(True)
+ window = Window(parent=screen, rect=Rect(x=300, y=300, width=100, height=100), title='Maximize')
self.assertEqual(window.rect, Rect(x=300, y=300, width=100, height=100))
window.maximize()
@@ -125,11 +121,9 @@ class WidgetTestCase(unittest.TestCase):
def test_multitouch_window_move(self):
screen = Screen(self._driver, ScreenPosition.FIXED,
x=0, y=0, width=800, height=800)
- windows = [Window(screen, x=50, y=50, width=100, height=100, title='First'),
- Window(screen, x=400, y=400, width=100, height=100, title='Second'),
- Window(screen, x=50, y=400, width=100, height=100, title='Third')]
- for window in windows:
- window.set_visible(True)
+ windows = [Window(screen, rect=Rect(x=50, y=50, width=100, height=100), title='First'),
+ Window(screen, rect=Rect(x=400, y=400, width=100, height=100), title='Second'),
+ Window(screen, rect=Rect(x=50, y=400, width=100, height=100), title='Third')]
self.assertEqual(windows[0].rect, Rect(x=50, y=50, width=100, height=100))
self.assertEqual(windows[1].rect, Rect(x=400, y=400, width=100, height=100))
@@ -146,11 +140,9 @@ class WidgetTestCase(unittest.TestCase):
def test_multitouch_window_resize(self):
screen = Screen(self._driver, ScreenPosition.FIXED,
x=0, y=0, width=800, height=800)
- windows = [Window(screen, x=50, y=50, width=150, height=150, title='First'),
- Window(screen, x=400, y=400, width=150, height=150, title='Second'),
- Window(screen, x=50, y=400, width=150, height=150, title='Third')]
- for window in windows:
- window.set_visible(True)
+ windows = [Window(screen, rect=Rect(x=50, y=50, width=150, height=150), title='First'),
+ Window(screen, rect=Rect(x=400, y=400, width=150, height=150), title='Second'),
+ Window(screen, rect=Rect(x=50, y=400, width=150, height=150), title='Third')]
self.assertEqual(windows[0].rect, Rect(x=50, y=50, width=150, height=150))
self.assertEqual(windows[1].rect, Rect(x=400, y=400, width=150, height=150))
@@ -167,8 +159,7 @@ class WidgetTestCase(unittest.TestCase):
def test_newly_created_window_gets_keyboard_focus(self):
screen = Screen(self._driver, ScreenPosition.FIXED,
x=0, y=0, width=800, height=800)
- window = Window(screen, x=0, y=0, width=800, height=800, title='root')
- window.set_visible(True)
+ window = Window(parent=screen, rect=Rect(x=0, y=0, width=800, height=800), title='root')
ActionChains(self._driver).key_down('c').key_up('c').perform()
@@ -179,29 +170,262 @@ class WidgetTestCase(unittest.TestCase):
self.assertEqual(events[-1]['type'], 'keyRelease')
self.assertEqual(events[-1]['key'], 'c')
+ def test_parent_window_limits_moves_of_children(self):
+ screen = Screen(self._driver, ScreenPosition.FIXED,
+ x=0, y=0, width=800, height=800)
+
+ w1 = Window(parent=screen, rect=Rect(x=200, y=200, width=400, height=400), title='w1')
+ w1_w1 = Window(parent=w1, rect=Rect(x=100, y=100, width=200, height=200), title='w1_w1')
+ w1_w1_w1 = Window(parent=w1_w1, rect=Rect(50, 50, 100, 100), title='w1_w1_w1')
+
+ self.assertEqual(w1.rect, Rect(200, 200, 400, 400))
+ self.assertEqual(w1_w1.rect, Rect(100, 100, 200, 200))
+ self.assertEqual(w1_w1_w1.rect, Rect(50, 50, 100, 100))
+
+ # Left - Middle window
+ w1_w1.drag(Handle.TOP_WINDOW_BAR, direction=LEFT(300))
+
+ self.assertEqual(
+ w1_w1.frame_rect.x, -w1_w1.frame_rect.width / 2)
+ w1_w1.drag(Handle.TOP_WINDOW_BAR, direction=RIGHT(w1_w1.frame_rect.width / 2 + 100))
+
+ # Right - Middle window
+ w1_w1.drag(Handle.TOP_WINDOW_BAR, direction=RIGHT(300))
+
+ self.assertEqual(
+ w1_w1.frame_rect.x, w1.rect.width - w1_w1.frame_rect.width / 2)
+ w1_w1.drag(Handle.TOP_WINDOW_BAR, direction=LEFT(w1.rect.width / 2))
+
+ # Left - Inner window
+ w1_w1_w1.drag(Handle.TOP_WINDOW_BAR, direction=LEFT(300))
+
+ self.assertEqual(
+ w1_w1_w1.frame_rect.x, -w1_w1_w1.frame_rect.width / 2)
+
+ def test_child_window_activation(self):
+ screen = Screen(self._driver, ScreenPosition.FIXED,
+ x=0, y=0, width=800, height=800)
+
+ bottom = Window(parent=screen, rect=Rect(x=0, y=0, width=800, height=800), title='root')
+ w1 = Window(parent=bottom, rect=Rect(x=100, y=100, width=600, height=600), title='w1')
+ w1_w1 = Window(parent=w1, rect=Rect(x=100, y=100, width=300, height=300), title='w1_w1')
+ w1_w1_w1 = Window(parent=w1_w1, rect=Rect(x=100, y=100, width=100, height=100), title='w1_w1_w1')
+ w1_w1_w2 = Window(parent=w1_w1, rect=Rect(x=150, y=150, width=100, height=100), title='w1_w1_w2')
+ w1_w2 = Window(parent=w1, rect=Rect(x=300, y=300, width=300, height=300), title='w1_w2')
+ w1_w2_w1 = Window(parent=w1_w2, rect=Rect(x=100, y=100, width=100, height=100), title='w1_w2_w1')
+ w2 = Window(parent=bottom, rect=Rect(x=300, y=300, width=450, height=450), title='w2')
+
+ self.assertEqual(screen.window_stack_at_point(w1_w1.bounding_box.midpoint[0], w1_w1.bounding_box.midpoint[1]),
+ [w2, w1_w1_w2, w1_w1_w1, w1_w1, w1, bottom])
+
+ self.assertEqual(screen.window_stack_at_point(w2.bounding_box.midpoint[0], w2.bounding_box.midpoint[1]),
+ [w2, w1_w2_w1, w1_w2, w1, bottom])
+
+ for w in [w1, w1_w1, w1_w1_w1, w1_w1_w2, w1_w2, w1_w2_w1]:
+ self.assertFalse(w.active)
+ self.assertTrue(w2.active)
+
+ w1.click(0, 0)
+
+ for w in [w1, w1_w2, w1_w2_w1]:
+ self.assertTrue(w.active)
+ for w in [w1_w1, w1_w1_w1, w1_w1_w2, w2]:
+ self.assertFalse(w.active)
+
+ self.assertEqual(screen.window_stack_at_point(w2.frame_rect.midpoint[0], w2.frame_rect.midpoint[1]),
+ [w1_w2_w1, w1_w2, w1, w2, bottom])
+
+ w1_w1_w1.click(0, 0)
+
+ for w in [w1, w1_w1, w1_w1_w1]:
+ self.assertTrue(w.active)
+ for w in [w1_w1_w2, w1_w2, w1_w2_w1, w2]:
+ self.assertFalse(w.active)
+
+ self.assertEqual(screen.window_stack_at_point(w1_w1_w1.bounding_box.midpoint[0], w1_w1_w1.bounding_box.midpoint[1]),
+ [w1_w1_w1, w1_w1_w2, w1_w1, w1, w2, bottom])
+
+ w1_w1_w2.click(w1_w1_w2.bounding_box.width, w1_w1_w2.bounding_box.height)
+
+ for w in [w1, w1_w1, w1_w1_w2]:
+ self.assertTrue(w.active)
+ for w in [w1_w1_w1, w1_w2, w1_w2_w1, w2]:
+ self.assertFalse(w.active)
+
+ self.assertEqual(screen.window_stack_at_point(w1_w1_w2.bounding_box.x, w1_w1_w2.bounding_box.y),
+ [w1_w1_w2, w1_w1_w1, w1_w1, w1, w2, bottom])
+
+ def test_window_reparenting(self):
+ screen = Screen(self._driver, ScreenPosition.FIXED,
+ x=0, y=0, width=800, height=800)
+
+ bottom = Window(parent=screen, rect=Rect(x=800, y=800, width=300, height=300), title='bottom')
+ w1 = Window(parent=screen, rect=Rect(x=50, y=50, width=300, height=300), title='w1')
+ w2 = Window(parent=screen, rect=Rect(x=50, y=50, width=300, height=300), title='w2')
+ w3 = Window(parent=screen, rect=Rect(x=50, y=50, width=300, height=300), title='w3')
+
+ self.assertTrue(
+ w2.element not in [*w1.element.find_elements(By.XPATH, "ancestor::div")])
+ self.assertTrue(
+ w3.element not in [*w1.element.find_elements(By.XPATH, "ancestor::div")])
+ self.assertTrue(
+ w1.element not in [*w2.element.find_elements(By.XPATH, "ancestor::div")])
+ self.assertTrue(
+ w3.element not in [*w2.element.find_elements(By.XPATH, "ancestor::div")])
+ self.assertTrue(
+ w1.element not in [*w3.element.find_elements(By.XPATH, "ancestor::div")])
+ self.assertTrue(
+ w2.element not in [*w3.element.find_elements(By.XPATH, "ancestor::div")])
+
+ w2.set_parent(w1)
+
+ self.assertTrue(
+ w2.element not in [*w1.element.find_elements(By.XPATH, "ancestor::div")])
+ self.assertTrue(
+ w3.element not in [*w1.element.find_elements(By.XPATH, "ancestor::div")])
+ self.assertTrue(
+ w1.element in [*w2.element.find_elements(By.XPATH, "ancestor::div")])
+ self.assertTrue(
+ w3.element not in [*w2.element.find_elements(By.XPATH, "ancestor::div")])
+ self.assertTrue(
+ w1.element not in [*w3.element.find_elements(By.XPATH, "ancestor::div")])
+ self.assertTrue(
+ w2.element not in [*w3.element.find_elements(By.XPATH, "ancestor::div")])
+
+ w3.set_parent(w2)
+
+ self.assertTrue(
+ w2.element not in [*w1.element.find_elements(By.XPATH, "ancestor::div")])
+ self.assertTrue(
+ w3.element not in [*w1.element.find_elements(By.XPATH, "ancestor::div")])
+ self.assertTrue(
+ w1.element in [*w2.element.find_elements(By.XPATH, "ancestor::div")])
+ self.assertTrue(
+ w3.element not in [*w2.element.find_elements(By.XPATH, "ancestor::div")])
+ self.assertTrue(
+ w1.element in [*w3.element.find_elements(By.XPATH, "ancestor::div")])
+ self.assertTrue(
+ w2.element in [*w3.element.find_elements(By.XPATH, "ancestor::div")])
+
+ w2.set_parent(screen)
+
+ self.assertTrue(
+ w2.element not in [*w1.element.find_elements(By.XPATH, "ancestor::div")])
+ self.assertTrue(
+ w3.element not in [*w1.element.find_elements(By.XPATH, "ancestor::div")])
+ self.assertTrue(
+ w1.element not in [*w2.element.find_elements(By.XPATH, "ancestor::div")])
+ self.assertTrue(
+ w3.element not in [*w2.element.find_elements(By.XPATH, "ancestor::div")])
+ self.assertTrue(
+ w1.element not in [*w3.element.find_elements(By.XPATH, "ancestor::div")])
+ self.assertTrue(
+ w2.element in [*w3.element.find_elements(By.XPATH, "ancestor::div")])
+
+ w1.set_parent(w2)
+
+ self.assertTrue(
+ w2.element in [*w1.element.find_elements(By.XPATH, "ancestor::div")])
+ self.assertTrue(
+ w3.element not in [*w1.element.find_elements(By.XPATH, "ancestor::div")])
+ self.assertTrue(
+ w1.element not in [*w2.element.find_elements(By.XPATH, "ancestor::div")])
+ self.assertTrue(
+ w3.element not in [*w2.element.find_elements(By.XPATH, "ancestor::div")])
+ self.assertTrue(
+ w1.element not in [*w3.element.find_elements(By.XPATH, "ancestor::div")])
+ self.assertTrue(
+ w2.element in [*w3.element.find_elements(By.XPATH, "ancestor::div")])
+
+ w3.set_parent(screen)
+
+ self.assertTrue(
+ w2.element in [*w1.element.find_elements(By.XPATH, "ancestor::div")])
+ self.assertTrue(
+ w3.element not in [*w1.element.find_elements(By.XPATH, "ancestor::div")])
+ self.assertTrue(
+ w1.element not in [*w2.element.find_elements(By.XPATH, "ancestor::div")])
+ self.assertTrue(
+ w3.element not in [*w2.element.find_elements(By.XPATH, "ancestor::div")])
+ self.assertTrue(
+ w1.element not in [*w3.element.find_elements(By.XPATH, "ancestor::div")])
+ self.assertTrue(
+ w2.element not in [*w3.element.find_elements(By.XPATH, "ancestor::div")])
+
+ w2.set_parent(w3)
+
+ self.assertTrue(
+ w2.element in [*w1.element.find_elements(By.XPATH, "ancestor::div")])
+ self.assertTrue(
+ w3.element in [*w1.element.find_elements(By.XPATH, "ancestor::div")])
+ self.assertTrue(
+ w1.element not in [*w2.element.find_elements(By.XPATH, "ancestor::div")])
+ self.assertTrue(
+ w3.element in [*w2.element.find_elements(By.XPATH, "ancestor::div")])
+ self.assertTrue(
+ w1.element not in [*w3.element.find_elements(By.XPATH, "ancestor::div")])
+ self.assertTrue(
+ w2.element not in [*w3.element.find_elements(By.XPATH, "ancestor::div")])
+
+ def test_window_closing(self):
+ screen = Screen(self._driver, ScreenPosition.FIXED,
+ x=0, y=0, width=800, height=800)
+
+ bottom = Window(parent=screen, rect=Rect(x=800, y=800, width=300, height=300), title='root')
+ bottom.close()
+
+ w1 = Window(parent=screen, rect=Rect(x=50, y=50, width=300, height=300), title='w1')
+ w2 = Window(parent=screen, rect=Rect(x=50, y=50, width=300, height=300), title='w2')
+ w3 = Window(parent=screen, rect=Rect(x=50, y=50, width=300, height=300), title='w3')
+
+ w3.close()
+
+ self.assertFalse(w3 in screen.query_windows())
+ self.assertTrue(w2 in screen.query_windows())
+ self.assertTrue(w1 in screen.query_windows())
+
+ w4 = Window(parent=screen, rect=Rect(x=50, y=50, width=300, height=300), title='w4')
+
+ self.assertTrue(w4 in screen.query_windows())
+ self.assertTrue(w2 in screen.query_windows())
+ self.assertTrue(w1 in screen.query_windows())
+
+ w2.close()
+ w1.close()
+
+ self.assertTrue(w4 in screen.query_windows())
+ self.assertFalse(w2 in screen.query_windows())
+ self.assertFalse(w1 in screen.query_windows())
+
+ w4.close()
+
+ self.assertFalse(w4 in screen.query_windows())
+
def tearDown(self):
self._driver.quit()
-
class ScreenPosition(Enum):
FIXED = auto()
RELATIVE = auto()
IN_SCROLL_CONTAINER = auto()
-
class Screen:
- def __init__(self, driver, positioning, x, y, width, height, container_width=0, container_height=0):
+ def __init__(self, driver, positioning=None, x=None, y=None, width=None, height=None, container_width=0, container_height=0, screen_name=None):
self.driver = driver
- self.x = x
- self.y = y
- self.width = width
- self.height = height
+ if screen_name is not None:
+ screen_information = call_instance_function(self.driver, 'screenInformation')
+ if len(screen_information) != 1:
+ raise AssertionError('Expecting exactly one screen_information!')
+ self.screen_info = screen_information[0]
+ self.element = driver.find_element(By.CSS_SELECTOR, f'#test-screen-1')
+ return
+
if positioning == ScreenPosition.FIXED:
- command = f'initializeScreenWithFixedPosition({self.x}, {self.y}, {self.width}, {self.height})'
+ command = f'initializeScreenWithFixedPosition({x}, {y}, {width}, {height})'
elif positioning == ScreenPosition.RELATIVE:
- command = f'initializeScreenWithRelativePosition({self.x}, {self.y}, {self.width}, {self.height})'
+ command = f'initializeScreenWithRelativePosition({x}, {y}, {width}, {height})'
elif positioning == ScreenPosition.IN_SCROLL_CONTAINER:
- command = f'initializeScreenInScrollContainer({container_width}, {container_height}, {self.x}, {self.y}, {self.width}, {self.height})'
+ command = f'initializeScreenInScrollContainer({container_width}, {container_height}, {x}, {y}, {width}, {height})'
self.element = self.driver.execute_script(
f'''
return testSupport.{command};
@@ -223,26 +447,72 @@ class Screen:
geo = self.screen_info['geometry']
return Rect(geo['x'], geo['y'], geo['width'], geo['height'])
+ @property
+ def name(self):
+ return self.screen_info['name']
+
def scroll_to(self):
ActionChains(self.driver).scroll_to_element(self.element).perform()
-
-class Window:
- def __init__(self, screen, x, y, width, height, title='title'):
- self.driver = screen.driver
- self.title = title
- self.driver.execute_script(
+ def hit_test_point(self, x, y):
+ return self.driver.execute_script(
f'''
- instance.createWindow({x}, {y}, {width}, {height}, '{screen.screen_info["name"]}', '{title}');
+ return testSupport.hitTestPoint({x}, {y}, '{self.element.get_attribute("id")}');
'''
)
+
+ def window_stack_at_point(self, x, y):
+ return [
+ Window(self, element=element) for element in [
+ *filter(lambda elem: (elem.get_attribute('id') if elem.get_attribute('id') is not None else '')
+ .startswith('qt-window-'), self.hit_test_point(x, y))]]
+
+ def query_windows(self):
+ return [
+ Window(self, element=element) for element in self.element.shadow_root.find_elements(
+ By.CSS_SELECTOR, f'div#{self.name} > div.qt-window')]
+
+
+class Window:
+ def __init__(self, parent=None, rect=None, title=None, element=None, visible=True):
+ self.driver = parent.driver
+ if element is not None:
+ self.element = element
+ self.title = element.find_element(
+ By.CSS_SELECTOR, f'.title-bar > .window-name').text
+ information = self.__window_information()
+ self.screen = Screen(self.driver, screen_name=information['screen']['name'])
+ pass
+ else:
+ self.title = title = title if title is not None else 'window'
+ if isinstance(parent, Window):
+ self.driver.execute_script(
+ f'''
+ instance.createWindow({rect.x}, {rect.y}, {rect.width}, {rect.height}, 'window', '{parent.title}', '{title}');
+ '''
+ )
+ self.screen = parent.screen
+ else:
+ assert(isinstance(parent, Screen))
+ self.driver.execute_script(
+ f'''
+ instance.createWindow({rect.x}, {rect.y}, {rect.width}, {rect.height}, 'screen', '{parent.name}', '{title}');
+ '''
+ )
+ self.screen = parent
self._window_id = self.__window_information()['id']
- self.element = screen.element.shadow_root.find_element(
- By.CSS_SELECTOR, f'#qt-window-{self._window_id}')
+ self.element = self.screen.element.shadow_root.find_element(
+ By.CSS_SELECTOR, f'#qt-window-{self._window_id}')
+ if visible:
+ self.set_visible(True)
+
+ def __eq__(self, other):
+ return self._window_id == other._window_id if isinstance(other, Window) else False
def __window_information(self):
information = call_instance_function(self.driver, 'windowInformation')
- return next(filter(lambda e: e['title'] == self.title, information))
+ #print(information)
+ return next(filter(lambda e: e['title'] == self.title, information))
@property
def rect(self):
@@ -308,6 +578,61 @@ class Window:
offset = (0, -height/2 + top_frame_bar_width/2)
return {'window': self, 'offset': offset}
+ @property
+ def bounding_box(self):
+ raw = self.driver.execute_script("""
+ return arguments[0].getBoundingClientRect();
+ """, self.element)
+ return Rect(raw['x'], raw['y'], raw['width'], raw['height'])
+
+ @property
+ def active(self):
+ return not self.inactive
+ # self.assertFalse('inactive' in window_element.get_attribute(
+ # 'class').split(' '), window_element.get_attribute('id'))
+
+ @property
+ def inactive(self):
+ window_chain = [
+ *self.element.find_elements(By.XPATH, "ancestor::div"), self.element]
+ return next(filter(lambda elem: 'qt-window' in elem.get_attribute('class').split(' ') and
+ 'inactive' in elem.get_attribute(
+ 'class').split(' '),
+ window_chain
+ ), None) is not None
+
+ def click(self, x, y):
+ rect = self.bounding_box
+
+ SELENIUM_IMPRECISION_COMPENSATION = 2
+ ActionChains(self.driver).move_to_element(
+ self.element).move_by_offset(-rect.width / 2 + x + SELENIUM_IMPRECISION_COMPENSATION,
+ -rect.height / 2 + y + SELENIUM_IMPRECISION_COMPENSATION).click().perform()
+
+ def set_parent(self, parent):
+ if isinstance(parent, Screen):
+ # TODO won't work with screen that is not parent.screen
+ self.screen = parent
+ self.driver.execute_script(
+ f'''
+ instance.setWindowParent('{self.title}', 'none');
+ '''
+ )
+ else:
+ assert(isinstance(parent, Window))
+ self.screen = parent.screen
+ self.driver.execute_script(
+ f'''
+ instance.setWindowParent('{self.title}', '{parent.title}');
+ '''
+ )
+
+ def close(self):
+ self.driver.execute_script(
+ f'''
+ instance.closeWindow('{self.title}');
+ '''
+ )
class TouchDragAction:
def __init__(self, origin, direction):
@@ -477,10 +802,13 @@ class Rect:
def __str__(self):
return f'(x: {self.x}, y: {self.y}, width: {self.width}, height: {self.height})'
+ @property
+ def midpoint(self):
+ return self.x + self.width / 2, self.y + self.height / 2,
+
def assert_rects_equal(geo1, geo2, msg=None):
if geo1.x != geo2.x or geo1.y != geo2.y or geo1.width != geo2.width or geo1.height != geo2.height:
raise AssertionError(f'Rectangles not equal: \n{geo1} \nvs \n{geo2}')
-
unittest.main()
diff --git a/tests/manual/wasm/qwasmwindow/qwasmwindow_harness.cpp b/tests/manual/wasm/qwasmwindow/qwasmwindow_harness.cpp
index eda557e195..04a947d0ba 100644
--- a/tests/manual/wasm/qwasmwindow/qwasmwindow_harness.cpp
+++ b/tests/manual/wasm/qwasmwindow/qwasmwindow_harness.cpp
@@ -50,6 +50,17 @@ private:
}
};
+namespace {
+DeleteOnCloseWindow *findWindowByTitle(const std::string &title)
+{
+ auto windows = qGuiApp->allWindows();
+ auto window_it = std::find_if(windows.begin(), windows.end(), [&title](QWindow *window) {
+ return window->title() == QString::fromLatin1(title);
+ });
+ return window_it == windows.end() ? nullptr : static_cast<DeleteOnCloseWindow *>(*window_it);
+}
+} // namespace
+
using namespace emscripten;
std::string toJSArray(const std::vector<std::string> &elements)
@@ -103,6 +114,7 @@ std::string windowToJSObject(const QWindow &window)
<< " id: " << std::to_string(window.winId()) << ","
<< " geometry: " << rectToJSObject(window.geometry()) << ","
<< " frameGeometry: " << rectToJSObject(window.frameGeometry()) << ","
+ << " screen: " << screenToJSObject(*window.screen()) << ","
<< " title: '" << window.title().toStdString() << "' }";
return out.str();
}
@@ -132,14 +144,34 @@ void screenInformation()
emscripten::val(toJSArray(screensAsJsObjects)));
}
-void createWindow(int x, int y, int w, int h, std::string screenId, std::string title)
+void createWindow(int x, int y, int w, int h, std::string parentType, std::string parentId,
+ std::string title)
{
- auto screens = qGuiApp->screens();
- auto screen_it = std::find_if(screens.begin(), screens.end(), [&screenId](QScreen *screen) {
- return screen->name() == QString::fromLatin1(screenId);
- });
- if (screen_it == screens.end()) {
- qWarning() << "No such screen: " << screenId;
+ QScreen *parentScreen = nullptr;
+ QWindow *parentWindow = nullptr;
+ if (parentType == "screen") {
+ auto screens = qGuiApp->screens();
+ auto screen_it = std::find_if(screens.begin(), screens.end(), [&parentId](QScreen *screen) {
+ return screen->name() == QString::fromLatin1(parentId);
+ });
+ if (screen_it == screens.end()) {
+ qWarning() << "No such screen: " << parentId;
+ return;
+ }
+ parentScreen = *screen_it;
+ } else if (parentType == "window") {
+ auto windows = qGuiApp->allWindows();
+ auto window_it = std::find_if(windows.begin(), windows.end(), [&parentId](QWindow *window) {
+ return window->title() == QString::fromLatin1(parentId);
+ });
+ if (window_it == windows.end()) {
+ qWarning() << "No such window: " << parentId;
+ return;
+ }
+ parentWindow = *window_it;
+ parentScreen = parentWindow->screen();
+ } else {
+ qWarning() << "Wrong parent type " << parentType;
return;
}
@@ -149,7 +181,8 @@ void createWindow(int x, int y, int w, int h, std::string screenId, std::string
window->setFlag(Qt::WindowMaximizeButtonHint);
window->setTitle(QString::fromLatin1(title));
window->setGeometry(x, y, w, h);
- window->setScreen(*screen_it);
+ window->setScreen(parentScreen);
+ window->setParent(parentWindow);
}
void setWindowVisible(int windowId, bool visible) {
@@ -165,12 +198,37 @@ void setWindowVisible(int windowId, bool visible) {
(*window_it)->setVisible(visible);
}
+void setWindowParent(std::string windowTitle, std::string parentTitle)
+{
+ QWindow *window = findWindowByTitle(windowTitle);
+ if (!window) {
+ qWarning() << "Window could not be found " << parentTitle;
+ return;
+ }
+ QWindow *parent = nullptr;
+ if (parentTitle != "none") {
+ if ((parent = findWindowByTitle(parentTitle)) == nullptr) {
+ qWarning() << "Parent window could not be found " << parentTitle;
+ return;
+ }
+ }
+ window->setParent(parent);
+}
+
+bool closeWindow(std::string title)
+{
+ QWindow *window = findWindowByTitle(title);
+ return window ? window->close() : false;
+}
+
EMSCRIPTEN_BINDINGS(qwasmwindow)
{
emscripten::function("screenInformation", &screenInformation);
emscripten::function("windowInformation", &windowInformation);
emscripten::function("createWindow", &createWindow);
emscripten::function("setWindowVisible", &setWindowVisible);
+ emscripten::function("setWindowParent", &setWindowParent);
+ emscripten::function("closeWindow", &closeWindow);
}
int main(int argc, char **argv)
diff --git a/tests/manual/wasm/qwasmwindow/qwasmwindow_harness.html b/tests/manual/wasm/qwasmwindow/qwasmwindow_harness.html
index 8d73160ed8..c8f9e977fe 100644
--- a/tests/manual/wasm/qwasmwindow/qwasmwindow_harness.html
+++ b/tests/manual/wasm/qwasmwindow/qwasmwindow_harness.html
@@ -9,6 +9,7 @@
const testSandbox = document.createElement('div');
testSandbox.id = 'test-sandbox';
+ let nextScreenId = 1;
document.body.appendChild(testSandbox);
const eventList = [];
@@ -21,6 +22,7 @@
screenDiv.style.width = `${width}px`;
screenDiv.style.height = `${height}px`;
screenDiv.style.backgroundColor = 'lightblue';
+ screenDiv.id = `test-screen-${nextScreenId++}`;
return screenDiv;
};
@@ -62,7 +64,11 @@
reportEvent: event => {
eventList.push(event);
},
- events: () => eventList
+ events: () => eventList,
+ hitTestPoint: (x, y, screenId) => {
+ return document
+ .querySelector(`#${screenId}`).shadowRoot.elementsFromPoint(x, y);
+ }
};
})();
</script>