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>
This commit is contained in:
parent
eb92d52dc7
commit
fc4fca6d9d
@ -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
|
||||
|
@ -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)
|
||||
void QWasmCompositor::onWindowTreeChanged(QWasmWindowTreeNodeChangeType changeType,
|
||||
QWasmWindow *window)
|
||||
{
|
||||
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);
|
||||
m_windowStack.removeWindow(window);
|
||||
if (m_windowStack.topWindow()) {
|
||||
m_windowStack.topWindow()->requestActivateWindow();
|
||||
setActive(m_windowStack.topWindow());
|
||||
}
|
||||
|
||||
updateEnabledState();
|
||||
}
|
||||
|
||||
void QWasmCompositor::setActive(QWasmWindow *window)
|
||||
void QWasmCompositor::setEnabled(bool enabled)
|
||||
{
|
||||
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
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
QWindow *QWasmCompositor::keyWindow() const
|
||||
{
|
||||
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()
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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,7 +174,6 @@ void QWasmWindow::onCloseClicked()
|
||||
|
||||
void QWasmWindow::onNonClientAreaInteraction()
|
||||
{
|
||||
if (!isActive())
|
||||
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());
|
||||
|
||||
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;
|
||||
// In viewport
|
||||
auto containerGeometryInViewport =
|
||||
QRectF::fromDOMRect(parentNode()->containerElement().call<emscripten::val>(
|
||||
"getBoundingClientRect"))
|
||||
.toRect();
|
||||
|
||||
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);
|
||||
}
|
||||
setAsActiveNode();
|
||||
|
||||
if (!QWasmIntegration::get()->inputContext())
|
||||
m_canvas.call<void>("focus");
|
||||
@ -651,9 +671,41 @@ void QWasmWindow::setMask(const QRegion ®ion)
|
||||
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
|
||||
|
@ -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 ®ion) 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;
|
||||
|
||||
|
@ -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)
|
||||
|
104
src/plugins/platforms/wasm/qwasmwindowtreenode.cpp
Normal file
104
src/plugins/platforms/wasm/qwasmwindowtreenode.cpp
Normal file
@ -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();
|
||||
}
|
53
src/plugins/platforms/wasm/qwasmwindowtreenode.h
Normal file
53
src/plugins/platforms/wasm/qwasmwindowtreenode.h
Normal file
@ -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
|
@ -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
|
||||
|
257
tests/auto/wasm/tst_qwasmwindowtreenode.cpp
Normal file
257
tests/auto/wasm/tst_qwasmwindowtreenode.cpp
Normal file
@ -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"
|
@ -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,25 +447,71 @@ 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(
|
||||
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')
|
||||
#print(information)
|
||||
return next(filter(lambda e: e['title'] == self.title, information))
|
||||
|
||||
@property
|
||||
@ -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()
|
||||
|
@ -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)
|
||||
{
|
||||
QScreen *parentScreen = nullptr;
|
||||
QWindow *parentWindow = nullptr;
|
||||
if (parentType == "screen") {
|
||||
auto screens = qGuiApp->screens();
|
||||
auto screen_it = std::find_if(screens.begin(), screens.end(), [&screenId](QScreen *screen) {
|
||||
return screen->name() == QString::fromLatin1(screenId);
|
||||
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: " << screenId;
|
||||
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)
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user