[server] Add a method SurfaceInterface::surfaceAt(const QPointF&) -> SurfaceInterface*

Summary:
The new method returns the (child) surface at a given surface position
taking care of stacking order, whether surfaces are mapped, etc.

Reviewers: #plasma

Subscribers: plasma-devel

Projects: #plasma

Differential Revision: https://phabricator.kde.org/D1319
This commit is contained in:
Martin Gräßlin 2016-04-05 14:26:53 +02:00
parent a2a888bc76
commit 37851e2e08
4 changed files with 158 additions and 0 deletions

View file

@ -57,6 +57,7 @@ private Q_SLOTS:
void testMainSurfaceFromTree();
void testRemoveSurface();
void testMappingOfSurfaceTree();
void testSurfaceAt();
private:
KWayland::Server::Display *m_display;
@ -920,5 +921,84 @@ void TestSubSurface::testMappingOfSurfaceTree()
QVERIFY(!child3->surface()->isMapped());
}
void TestSubSurface::testSurfaceAt()
{
// this test verifies that the correct surface is picked in a sub-surface tree
using namespace KWayland::Client;
using namespace KWayland::Server;
// first create a parent surface and map it
QSignalSpy serverSurfaceCreated(m_compositorInterface, &CompositorInterface::surfaceCreated);
QVERIFY(serverSurfaceCreated.isValid());
QScopedPointer<Surface> parent(m_compositor->createSurface());
QImage image(QSize(100, 100), QImage::Format_ARGB32_Premultiplied);
image.fill(Qt::red);
parent->attachBuffer(m_shm->createBuffer(image));
parent->damage(QRect(0, 0, 100, 100));
parent->commit(Surface::CommitFlag::None);
QVERIFY(serverSurfaceCreated.wait());
SurfaceInterface *parentServerSurface = serverSurfaceCreated.last().first().value<KWayland::Server::SurfaceInterface*>();
// create two child sub surfaces, those won't be mapped, just added to the parent
// this is to simulate the behavior of QtWayland
QScopedPointer<Surface> directChild1(m_compositor->createSurface());
QVERIFY(serverSurfaceCreated.wait());
SurfaceInterface *directChild1ServerSurface = serverSurfaceCreated.last().first().value<KWayland::Server::SurfaceInterface*>();
QScopedPointer<Surface> directChild2(m_compositor->createSurface());
QVERIFY(serverSurfaceCreated.wait());
SurfaceInterface *directChild2ServerSurface = serverSurfaceCreated.last().first().value<KWayland::Server::SurfaceInterface*>();
// create the sub surfaces for them
QScopedPointer<SubSurface> directChild1SubSurface(m_subCompositor->createSubSurface(directChild1.data(), parent.data()));
directChild1SubSurface->setMode(SubSurface::Mode::Desynchronized);
QScopedPointer<SubSurface> directChild2SubSurface(m_subCompositor->createSubSurface(directChild2.data(), parent.data()));
directChild2SubSurface->setMode(SubSurface::Mode::Desynchronized);
// each of the children gets a child
QScopedPointer<Surface> childFor1(m_compositor->createSurface());
QVERIFY(serverSurfaceCreated.wait());
SurfaceInterface *childFor1ServerSurface = serverSurfaceCreated.last().first().value<KWayland::Server::SurfaceInterface*>();
QScopedPointer<Surface> childFor2(m_compositor->createSurface());
QVERIFY(serverSurfaceCreated.wait());
SurfaceInterface *childFor2ServerSurface = serverSurfaceCreated.last().first().value<KWayland::Server::SurfaceInterface*>();
// create sub surfaces for them
QScopedPointer<SubSurface> childFor1SubSurface(m_subCompositor->createSubSurface(childFor1.data(), directChild1.data()));
childFor1SubSurface->setMode(SubSurface::Mode::Desynchronized);
QScopedPointer<SubSurface> childFor2SubSurface(m_subCompositor->createSubSurface(childFor2.data(), directChild2.data()));
childFor2SubSurface->setMode(SubSurface::Mode::Desynchronized);
// both get a quarter of the grand-parent surface
childFor2SubSurface->setPosition(QPoint(50, 50));
childFor2->commit(Surface::CommitFlag::None);
directChild2->commit(Surface::CommitFlag::None);
parent->commit(Surface::CommitFlag::None);
// now let's render both grand children
QImage partImage(QSize(50, 50), QImage::Format_ARGB32_Premultiplied);
partImage.fill(Qt::green);
childFor1->attachBuffer(m_shm->createBuffer(partImage));
childFor1->damage(QRect(0, 0, 50, 50));
childFor1->commit(Surface::CommitFlag::None);
partImage.fill(Qt::blue);
childFor2->attachBuffer(m_shm->createBuffer(partImage));
childFor2->damage(QRect(0, 0, 50, 50));
childFor2->commit(Surface::CommitFlag::None);
QSignalSpy treeChangedSpy(parentServerSurface, &SurfaceInterface::subSurfaceTreeChanged);
QVERIFY(treeChangedSpy.isValid());
QVERIFY(treeChangedSpy.wait());
// now let's test a few positions
QCOMPARE(parentServerSurface->surfaceAt(QPointF(0, 0)), childFor1ServerSurface);
QCOMPARE(parentServerSurface->surfaceAt(QPointF(49, 49)), childFor1ServerSurface);
QCOMPARE(parentServerSurface->surfaceAt(QPointF(50, 50)), childFor2ServerSurface);
QCOMPARE(parentServerSurface->surfaceAt(QPointF(100, 100)), childFor2ServerSurface);
QCOMPARE(parentServerSurface->surfaceAt(QPointF(25, 75)), parentServerSurface);
QCOMPARE(parentServerSurface->surfaceAt(QPointF(75, 25)), parentServerSurface);
// outside the geometries should be no surface
QVERIFY(!parentServerSurface->surfaceAt(QPointF(-1, -1)));
QVERIFY(!parentServerSurface->surfaceAt(QPointF(101, 101)));
}
QTEST_GUILESS_MAIN(TestSubSurface)
#include "test_wayland_subsurface.moc"

View file

@ -56,6 +56,7 @@ private Q_SLOTS:
void testDestroy();
void testUnmapOfNotMappedSurface();
void testDamageTracking();
void testSurfaceAt();
private:
KWayland::Server::Display *m_display;
@ -797,5 +798,40 @@ void TestWaylandSurface::testDamageTracking()
QCOMPARE(serverSurface->damage(), QRegion(50, 40, 20, 30));
}
void TestWaylandSurface::testSurfaceAt()
{
// this test verifies that surfaceAt(const QPointF&) works as expected for the case of no children
using namespace KWayland::Client;
using namespace KWayland::Server;
// create surface
QSignalSpy serverSurfaceCreated(m_compositorInterface, &CompositorInterface::surfaceCreated);
QVERIFY(serverSurfaceCreated.isValid());
QScopedPointer<Surface> s(m_compositor->createSurface());
QVERIFY(serverSurfaceCreated.wait());
SurfaceInterface *serverSurface = serverSurfaceCreated.first().first().value<KWayland::Server::SurfaceInterface*>();
// a newly created surface should not be mapped and not provide a surface at a position
QVERIFY(!serverSurface->isMapped());
QVERIFY(!serverSurface->surfaceAt(QPointF(0, 0)));
// let's damage this surface
QSignalSpy sizeChangedSpy(serverSurface, &SurfaceInterface::sizeChanged);
QVERIFY(sizeChangedSpy.isValid());
QImage image(QSize(100, 100), QImage::Format_ARGB32_Premultiplied);
image.fill(Qt::red);
s->attachBuffer(m_shm->createBuffer(image));
s->damage(QRect(0, 0, 100, 100));
s->commit(Surface::CommitFlag::None);
QVERIFY(sizeChangedSpy.wait());
// now the surface is mapped and surfaceAt should give the surface
QVERIFY(serverSurface->isMapped());
QCOMPARE(serverSurface->surfaceAt(QPointF(0, 0)), serverSurface);
QCOMPARE(serverSurface->surfaceAt(QPointF(100, 100)), serverSurface);
// outside the geometry it should not give a surface
QVERIFY(!serverSurface->surfaceAt(QPointF(101, 101)));
QVERIFY(!serverSurface->surfaceAt(QPointF(-1, -1)));
}
QTEST_GUILESS_MAIN(TestWaylandSurface)
#include "test_wayland_surface.moc"

View file

@ -25,6 +25,8 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>.
#include "region_interface.h"
#include "subcompositor_interface.h"
#include "subsurface_interface_p.h"
// Qt
#include <QListIterator>
// Wayland
#include <wayland-server.h>
// std
@ -692,6 +694,32 @@ void SurfaceInterface::resetTrackedDamage()
d->trackedDamage = QRegion();
}
SurfaceInterface *SurfaceInterface::surfaceAt(const QPointF &position)
{
if (!isMapped()) {
return nullptr;
}
Q_D();
// go from top to bottom. Top most child is last in list
QListIterator<QPointer<SubSurfaceInterface>> it(d->current.children);
it.toBack();
while (it.hasPrevious()) {
const auto &current = it.previous();
auto surface = current->surface();
if (surface.isNull()) {
continue;
}
if (auto s = surface->surfaceAt(position - current->position())) {
return s;
}
}
// check whether the geometry contains the pos
if (!size().isEmpty() && QRectF(QPoint(0, 0), size()).contains(position)) {
return this;
}
return nullptr;
}
SurfaceInterface::Private *SurfaceInterface::d_func() const
{
return reinterpret_cast<Private*>(d.data());

View file

@ -191,6 +191,20 @@ public:
**/
void resetTrackedDamage();
/**
* Finds the SurfaceInterface at the given @p position in surface-local coordinates.
* This can be either a descendant SurfaceInterface honoring the stacking order or
* the SurfaceInterface itself if its geometry contains the given @p position.
*
* If no such SurfaceInterface is found, e.g. because the SurfaceInterface is unmapped,
* @c nullptr is returned.
*
* @param position The position in surface-local coordinates
* @returns Child surface at the given @p position or surface itself at the position, might be @c nullptr
* @since 5.7
**/
SurfaceInterface *surfaceAt(const QPointF &position);
/**
* @returns The SurfaceInterface for the @p native resource.
**/