summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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>