[scenes/qpainter] Draw decoration shadows
Summary: QPainter doesn't render decoration shadows. It renders only shadows provided through ShadowInterface. With this change, painting of shadows is done in similar way OpenGL backend is currently doing. Before {F5734867, layout=center, size=full} After {F5734870, layout=center, size=full} Depends on D10811 (dummy decoration with shadows in autotests) Test Plan: * start kwin with QPainter backend enabled: ``` KWIN_COMPOSE=Q kwin_wayland --xwayland --windowed ``` * open konsole and kate: ``` DISPLAY=:1 konsole DISPLAY=:1 kate ``` Reviewers: #kwin, graesslin, davidedmundson Reviewed By: davidedmundson Subscribers: abetts, kwin Tags: #kwin Differential Revision: https://phabricator.kde.org/D10943
This commit is contained in:
parent
7637cfc22b
commit
2d01ba6450
6 changed files with 1058 additions and 60 deletions
|
@ -72,6 +72,7 @@ if (XCB_ICCCM_FOUND)
|
|||
integrationTest(NAME testQuickTiling SRCS quick_tiling_test.cpp LIBS XCB::ICCCM)
|
||||
integrationTest(NAME testGlobalShortcuts SRCS globalshortcuts_test.cpp LIBS XCB::ICCCM)
|
||||
integrationTest(NAME testSceneQPainter SRCS scene_qpainter_test.cpp LIBS XCB::ICCCM)
|
||||
integrationTest(NAME testSceneQPainterShadow SRCS scene_qpainter_shadow_test.cpp LIBS XCB::ICCCM)
|
||||
|
||||
if (KWIN_BUILD_ACTIVITIES)
|
||||
integrationTest(NAME testActivities SRCS activities_test.cpp LIBS XCB::ICCCM)
|
||||
|
|
|
@ -38,6 +38,7 @@ class PlasmaWindowManagement;
|
|||
class PointerConstraints;
|
||||
class Seat;
|
||||
class ServerSideDecorationManager;
|
||||
class ShadowManager;
|
||||
class Shell;
|
||||
class ShellSurface;
|
||||
class ShmPool;
|
||||
|
@ -85,7 +86,8 @@ enum class AdditionalWaylandInterface {
|
|||
WindowManagement = 1 << 3,
|
||||
PointerConstraints = 1 << 4,
|
||||
IdleInhibition = 1 << 5,
|
||||
AppMenu = 1 << 6
|
||||
AppMenu = 1 << 6,
|
||||
ShadowManager = 1 << 7
|
||||
};
|
||||
Q_DECLARE_FLAGS(AdditionalWaylandInterfaces, AdditionalWaylandInterface)
|
||||
/**
|
||||
|
@ -106,6 +108,7 @@ void destroyWaylandConnection();
|
|||
|
||||
KWayland::Client::ConnectionThread *waylandConnection();
|
||||
KWayland::Client::Compositor *waylandCompositor();
|
||||
KWayland::Client::ShadowManager *waylandShadowManager();
|
||||
KWayland::Client::Shell *waylandShell();
|
||||
KWayland::Client::ShmPool *waylandShmPool();
|
||||
KWayland::Client::Seat *waylandSeat();
|
||||
|
|
782
autotests/integration/scene_qpainter_shadow_test.cpp
Normal file
782
autotests/integration/scene_qpainter_shadow_test.cpp
Normal file
|
@ -0,0 +1,782 @@
|
|||
/********************************************************************
|
||||
KWin - the KDE window manager
|
||||
This file is part of the KDE project.
|
||||
|
||||
Copyright (C) 2018 Vlad Zagorodniy <vladzzag@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************/
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QDir>
|
||||
#include <QImage>
|
||||
#include <QMarginsF>
|
||||
#include <QObject>
|
||||
#include <QPainter>
|
||||
#include <QPair>
|
||||
#include <QVector>
|
||||
|
||||
#include <KDecoration2/Decoration>
|
||||
#include <KDecoration2/DecorationShadow>
|
||||
|
||||
#include <KWayland/Client/server_decoration.h>
|
||||
#include <KWayland/Client/shadow.h>
|
||||
#include <KWayland/Client/shell.h>
|
||||
#include <KWayland/Client/shm_pool.h>
|
||||
#include <KWayland/Client/surface.h>
|
||||
#include <KWayland/Server/shadow_interface.h>
|
||||
#include <KWayland/Server/surface_interface.h>
|
||||
|
||||
#include "kwin_wayland_test.h"
|
||||
|
||||
#include "composite.h"
|
||||
#include "effect_builtins.h"
|
||||
#include "effectloader.h"
|
||||
#include "effects.h"
|
||||
#include "platform.h"
|
||||
#include "plugins/scenes/qpainter/scene_qpainter.h"
|
||||
#include "shadow.h"
|
||||
#include "shell_client.h"
|
||||
#include "wayland_server.h"
|
||||
#include "workspace.h"
|
||||
|
||||
Q_DECLARE_METATYPE(KWin::WindowQuadList)
|
||||
|
||||
using namespace KWin;
|
||||
using namespace KWayland::Client;
|
||||
|
||||
static const QString s_socketName = QStringLiteral("wayland_test_kwin_scene_qpainter_shadow-0");
|
||||
|
||||
class SceneQPainterShadowTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SceneQPainterShadowTest() {}
|
||||
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
void cleanup();
|
||||
|
||||
void testShadowTileOverlaps_data();
|
||||
void testShadowTileOverlaps();
|
||||
void testShadowTextureReconstruction();
|
||||
|
||||
};
|
||||
|
||||
inline bool isClose(double a, double b, double eps = 1e-5)
|
||||
{
|
||||
if (a == b) {
|
||||
return true;
|
||||
}
|
||||
const double diff = std::fabs(a - b);
|
||||
if (a == 0 || b == 0) {
|
||||
return diff < eps;
|
||||
}
|
||||
return diff / std::max(a, b) < eps;
|
||||
}
|
||||
|
||||
inline bool compareQuads(const WindowQuad &a, const WindowQuad &b)
|
||||
{
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (!isClose(a[i].x(), b[i].x())
|
||||
|| !isClose(a[i].y(), b[i].y())
|
||||
|| !isClose(a[i].textureX(), b[i].textureX())
|
||||
|| !isClose(a[i].textureY(), b[i].textureY())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
inline WindowQuad makeShadowQuad(const QRectF &geo, qreal tx1, qreal ty1, qreal tx2, qreal ty2)
|
||||
{
|
||||
WindowQuad quad(WindowQuadShadow);
|
||||
quad[0] = WindowVertex(geo.left(), geo.top(), tx1, ty1);
|
||||
quad[1] = WindowVertex(geo.right(), geo.top(), tx2, ty1);
|
||||
quad[2] = WindowVertex(geo.right(), geo.bottom(), tx2, ty2);
|
||||
quad[3] = WindowVertex(geo.left(), geo.bottom(), tx1, ty2);
|
||||
return quad;
|
||||
}
|
||||
|
||||
void SceneQPainterShadowTest::initTestCase()
|
||||
{
|
||||
// Copied from scene_qpainter_test.cpp
|
||||
|
||||
qRegisterMetaType<KWin::ShellClient*>();
|
||||
qRegisterMetaType<KWin::AbstractClient*>();
|
||||
QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated);
|
||||
QVERIFY(workspaceCreatedSpy.isValid());
|
||||
kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024));
|
||||
QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit()));
|
||||
|
||||
// disable all effects - we don't want to have it interact with the rendering
|
||||
auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig);
|
||||
KConfigGroup plugins(config, QStringLiteral("Plugins"));
|
||||
ScriptedEffectLoader loader;
|
||||
const auto builtinNames = BuiltInEffects::availableEffectNames() << loader.listOfKnownEffects();
|
||||
for (QString name : builtinNames) {
|
||||
plugins.writeEntry(name + QStringLiteral("Enabled"), false);
|
||||
}
|
||||
|
||||
config->sync();
|
||||
kwinApp()->setConfig(config);
|
||||
|
||||
if (!QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("icons/DMZ-White/index.theme")).isEmpty()) {
|
||||
qputenv("XCURSOR_THEME", QByteArrayLiteral("DMZ-White"));
|
||||
} else {
|
||||
// might be vanilla-dmz (e.g. Arch, FreeBSD)
|
||||
qputenv("XCURSOR_THEME", QByteArrayLiteral("Vanilla-DMZ"));
|
||||
}
|
||||
qputenv("XCURSOR_SIZE", QByteArrayLiteral("24"));
|
||||
qputenv("KWIN_COMPOSE", QByteArrayLiteral("Q"));
|
||||
|
||||
kwinApp()->start();
|
||||
QVERIFY(workspaceCreatedSpy.wait());
|
||||
QVERIFY(KWin::Compositor::self());
|
||||
|
||||
// Add directory with fake decorations to the plugin search path.
|
||||
QCoreApplication::addLibraryPath(
|
||||
QDir(QCoreApplication::applicationDirPath()).absoluteFilePath("fakes")
|
||||
);
|
||||
|
||||
// Change decoration theme.
|
||||
KConfigGroup group = kwinApp()->config()->group("org.kde.kdecoration2");
|
||||
group.writeEntry("library", "org.kde.test.fakedecowithshadows");
|
||||
group.sync();
|
||||
Workspace::self()->slotReconfigure();
|
||||
}
|
||||
|
||||
void SceneQPainterShadowTest::cleanup()
|
||||
{
|
||||
Test::destroyWaylandConnection();
|
||||
}
|
||||
|
||||
namespace {
|
||||
const int SHADOW_SIZE = 128;
|
||||
|
||||
const int SHADOW_OFFSET_TOP = 64;
|
||||
const int SHADOW_OFFSET_LEFT = 48;
|
||||
|
||||
// NOTE: We assume deco shadows are generated with blur so that's
|
||||
// why there is 4, 1 is the size of the inner shadow rect.
|
||||
const int SHADOW_TEXTURE_WIDTH = 4 * SHADOW_SIZE + 1;
|
||||
const int SHADOW_TEXTURE_HEIGHT = 4 * SHADOW_SIZE + 1;
|
||||
|
||||
const int SHADOW_PADDING_TOP = SHADOW_SIZE - SHADOW_OFFSET_TOP;
|
||||
const int SHADOW_PADDING_RIGHT = SHADOW_SIZE + SHADOW_OFFSET_LEFT;
|
||||
const int SHADOW_PADDING_BOTTOM = SHADOW_SIZE + SHADOW_OFFSET_TOP;
|
||||
const int SHADOW_PADDING_LEFT = SHADOW_SIZE - SHADOW_OFFSET_LEFT;
|
||||
|
||||
const QRectF SHADOW_INNER_RECT(2 * SHADOW_SIZE, 2 * SHADOW_SIZE, 1, 1);
|
||||
}
|
||||
|
||||
void SceneQPainterShadowTest::testShadowTileOverlaps_data()
|
||||
{
|
||||
QTest::addColumn<QSize>("windowSize");
|
||||
QTest::addColumn<WindowQuadList>("expectedQuads");
|
||||
|
||||
// Precompute shadow tile geometries(in texture's space).
|
||||
const QRectF topLeftTile(
|
||||
0,
|
||||
0,
|
||||
SHADOW_INNER_RECT.x(),
|
||||
SHADOW_INNER_RECT.y());
|
||||
const QRectF topRightTile(
|
||||
SHADOW_INNER_RECT.right(),
|
||||
0,
|
||||
SHADOW_TEXTURE_WIDTH - SHADOW_INNER_RECT.right(),
|
||||
SHADOW_INNER_RECT.y());
|
||||
const QRectF topTile(topLeftTile.topRight(), topRightTile.bottomLeft());
|
||||
|
||||
const QRectF bottomLeftTile(
|
||||
0,
|
||||
SHADOW_INNER_RECT.bottom(),
|
||||
SHADOW_INNER_RECT.x(),
|
||||
SHADOW_TEXTURE_HEIGHT - SHADOW_INNER_RECT.bottom());
|
||||
const QRectF bottomRightTile(
|
||||
SHADOW_INNER_RECT.right(),
|
||||
SHADOW_INNER_RECT.bottom(),
|
||||
SHADOW_TEXTURE_WIDTH - SHADOW_INNER_RECT.right(),
|
||||
SHADOW_TEXTURE_HEIGHT - SHADOW_INNER_RECT.bottom());
|
||||
const QRectF bottomTile(bottomLeftTile.topRight(), bottomRightTile.bottomLeft());
|
||||
|
||||
const QRectF leftTile(topLeftTile.bottomLeft(), bottomLeftTile.topRight());
|
||||
const QRectF rightTile(topRightTile.bottomLeft(), bottomRightTile.topRight());
|
||||
|
||||
qreal tx1 = 0;
|
||||
qreal ty1 = 0;
|
||||
qreal tx2 = 0;
|
||||
qreal ty2 = 0;
|
||||
|
||||
// Explanation behind numbers: (256+1 x 256+1) is the minimum window size
|
||||
// which doesn't cause overlapping of shadow tiles. For example, if a window
|
||||
// has (256 x 256+1) size, top-left and top-right or bottom-left and
|
||||
// bottom-right shadow tiles overlap.
|
||||
|
||||
// No overlaps: In this case corner tiles are rendered as they are,
|
||||
// and top/right/bottom/left tiles are stretched.
|
||||
{
|
||||
const QSize windowSize(256 + 1, 256 + 1);
|
||||
WindowQuadList shadowQuads;
|
||||
|
||||
const QRectF outerRect(
|
||||
-SHADOW_PADDING_LEFT,
|
||||
-SHADOW_PADDING_TOP,
|
||||
windowSize.width() + SHADOW_PADDING_LEFT + SHADOW_PADDING_RIGHT,
|
||||
windowSize.height() + SHADOW_PADDING_TOP + SHADOW_PADDING_BOTTOM);
|
||||
|
||||
const QRectF topLeft(
|
||||
outerRect.left(),
|
||||
outerRect.top(),
|
||||
topLeftTile.width(),
|
||||
topLeftTile.height());
|
||||
tx1 = topLeftTile.left();
|
||||
ty1 = topLeftTile.top();
|
||||
tx2 = topLeftTile.right();
|
||||
ty2 = topLeftTile.bottom();
|
||||
shadowQuads << makeShadowQuad(topLeft, tx1, ty1, tx2, ty2);
|
||||
|
||||
const QRectF topRight(
|
||||
outerRect.right() - topRightTile.width(),
|
||||
outerRect.top(),
|
||||
topRightTile.width(),
|
||||
topRightTile.height());
|
||||
tx1 = topRightTile.left();
|
||||
ty1 = topRightTile.top();
|
||||
tx2 = topRightTile.right();
|
||||
ty2 = topRightTile.bottom();
|
||||
shadowQuads << makeShadowQuad(topRight, tx1, ty1, tx2, ty2);
|
||||
|
||||
const QRectF top(topLeft.topRight(), topRight.bottomLeft());
|
||||
tx1 = topTile.left();
|
||||
ty1 = topTile.top();
|
||||
tx2 = topTile.right();
|
||||
ty2 = topTile.bottom();
|
||||
shadowQuads << makeShadowQuad(top, tx1, ty1, tx2, ty2);
|
||||
|
||||
const QRectF bottomLeft(
|
||||
outerRect.left(),
|
||||
outerRect.bottom() - bottomLeftTile.height(),
|
||||
bottomLeftTile.width(),
|
||||
bottomLeftTile.height());
|
||||
tx1 = bottomLeftTile.left();
|
||||
ty1 = bottomLeftTile.top();
|
||||
tx2 = bottomLeftTile.right();
|
||||
ty2 = bottomLeftTile.bottom();
|
||||
shadowQuads << makeShadowQuad(bottomLeft, tx1, ty1, tx2, ty2);
|
||||
|
||||
const QRectF bottomRight(
|
||||
outerRect.right() - bottomRightTile.width(),
|
||||
outerRect.bottom() - bottomRightTile.height(),
|
||||
bottomRightTile.width(),
|
||||
bottomRightTile.height());
|
||||
tx1 = bottomRightTile.left();
|
||||
ty1 = bottomRightTile.top();
|
||||
tx2 = bottomRightTile.right();
|
||||
ty2 = bottomRightTile.bottom();
|
||||
shadowQuads << makeShadowQuad(bottomRight, tx1, ty1, tx2, ty2);
|
||||
|
||||
const QRectF bottom(bottomLeft.topRight(), bottomRight.bottomLeft());
|
||||
tx1 = bottomTile.left();
|
||||
ty1 = bottomTile.top();
|
||||
tx2 = bottomTile.right();
|
||||
ty2 = bottomTile.bottom();
|
||||
shadowQuads << makeShadowQuad(bottom, tx1, ty1, tx2, ty2);
|
||||
|
||||
const QRectF left(topLeft.bottomLeft(), bottomLeft.topRight());
|
||||
tx1 = leftTile.left();
|
||||
ty1 = leftTile.top();
|
||||
tx2 = leftTile.right();
|
||||
ty2 = leftTile.bottom();
|
||||
shadowQuads << makeShadowQuad(left, tx1, ty1, tx2, ty2);
|
||||
|
||||
const QRectF right(topRight.bottomLeft(), bottomRight.topRight());
|
||||
tx1 = rightTile.left();
|
||||
ty1 = rightTile.top();
|
||||
tx2 = rightTile.right();
|
||||
ty2 = rightTile.bottom();
|
||||
shadowQuads << makeShadowQuad(right, tx1, ty1, tx2, ty2);
|
||||
|
||||
QTest::newRow("no overlaps") << windowSize << shadowQuads;
|
||||
}
|
||||
|
||||
// Top-Left & Bottom-Left/Top-Right & Bottom-Right overlap:
|
||||
// In this case overlapping parts are clipped and left/right
|
||||
// tiles aren't rendered.
|
||||
const QVector<QPair<QByteArray, QSize>> verticalOverlapTestTable {
|
||||
QPair<QByteArray, QSize> {
|
||||
QByteArray("top-left & bottom-left/top-right & bottom-right overlap"),
|
||||
QSize(256 + 1, 256)
|
||||
},
|
||||
QPair<QByteArray, QSize> {
|
||||
QByteArray("top-left & bottom-left/top-right & bottom-right overlap :: pre"),
|
||||
QSize(256 + 1, 256 - 1)
|
||||
}
|
||||
// No need to test the case when window size is QSize(256 + 1, 256 + 1).
|
||||
// It has been tested already (no overlaps test case).
|
||||
};
|
||||
|
||||
for (auto const &tt : verticalOverlapTestTable) {
|
||||
const char *testName = tt.first.constData();
|
||||
const QSize windowSize = tt.second;
|
||||
|
||||
WindowQuadList shadowQuads;
|
||||
qreal halfOverlap = 0.0;
|
||||
|
||||
const QRectF outerRect(
|
||||
-SHADOW_PADDING_LEFT,
|
||||
-SHADOW_PADDING_TOP,
|
||||
windowSize.width() + SHADOW_PADDING_LEFT + SHADOW_PADDING_RIGHT,
|
||||
windowSize.height() + SHADOW_PADDING_TOP + SHADOW_PADDING_BOTTOM);
|
||||
|
||||
QRectF topLeft(
|
||||
outerRect.left(),
|
||||
outerRect.top(),
|
||||
topLeftTile.width(),
|
||||
topLeftTile.height());
|
||||
|
||||
QRectF bottomLeft(
|
||||
outerRect.left(),
|
||||
outerRect.bottom() - bottomLeftTile.height(),
|
||||
bottomLeftTile.width(),
|
||||
bottomLeftTile.height());
|
||||
|
||||
halfOverlap = qAbs(topLeft.bottom() - bottomLeft.top()) / 2;
|
||||
topLeft.setBottom(topLeft.bottom() - std::floor(halfOverlap));
|
||||
bottomLeft.setTop(bottomLeft.top() + std::ceil(halfOverlap));
|
||||
|
||||
tx1 = topLeftTile.left();
|
||||
ty1 = topLeftTile.top();
|
||||
tx2 = topLeftTile.right();
|
||||
ty2 = topLeft.height();
|
||||
shadowQuads << makeShadowQuad(topLeft, tx1, ty1, tx2, ty2);
|
||||
|
||||
tx1 = bottomLeftTile.left();
|
||||
ty1 = SHADOW_TEXTURE_HEIGHT - bottomLeft.height();
|
||||
tx2 = bottomLeftTile.right();
|
||||
ty2 = bottomLeftTile.bottom();
|
||||
shadowQuads << makeShadowQuad(bottomLeft, tx1, ty1, tx2, ty2);
|
||||
|
||||
QRectF topRight(
|
||||
outerRect.right() - topRightTile.width(),
|
||||
outerRect.top(),
|
||||
topRightTile.width(),
|
||||
topRightTile.height());
|
||||
|
||||
QRectF bottomRight(
|
||||
outerRect.right() - bottomRightTile.width(),
|
||||
outerRect.bottom() - bottomRightTile.height(),
|
||||
bottomRightTile.width(),
|
||||
bottomRightTile.height());
|
||||
|
||||
halfOverlap = qAbs(topRight.bottom() - bottomRight.top()) / 2;
|
||||
topRight.setBottom(topRight.bottom() - std::floor(halfOverlap));
|
||||
bottomRight.setTop(bottomRight.top() + std::ceil(halfOverlap));
|
||||
|
||||
tx1 = topRightTile.left();
|
||||
ty1 = topRightTile.top();
|
||||
tx2 = topRightTile.right();
|
||||
ty2 = topRight.height();
|
||||
shadowQuads << makeShadowQuad(topRight, tx1, ty1, tx2, ty2);
|
||||
|
||||
tx1 = bottomRightTile.left();
|
||||
ty1 = SHADOW_TEXTURE_HEIGHT - bottomRight.height();
|
||||
tx2 = bottomRightTile.right();
|
||||
ty2 = bottomRightTile.bottom();
|
||||
shadowQuads << makeShadowQuad(bottomRight, tx1, ty1, tx2, ty2);
|
||||
|
||||
const QRectF top(topLeft.topRight(), topRight.bottomLeft());
|
||||
tx1 = topTile.left();
|
||||
ty1 = topTile.top();
|
||||
tx2 = topTile.right();
|
||||
ty2 = top.height();
|
||||
shadowQuads << makeShadowQuad(top, tx1, ty1, tx2, ty2);
|
||||
|
||||
const QRectF bottom(bottomLeft.topRight(), bottomRight.bottomLeft());
|
||||
tx1 = bottomTile.left();
|
||||
ty1 = SHADOW_TEXTURE_HEIGHT - bottom.height();
|
||||
tx2 = bottomTile.right();
|
||||
ty2 = bottomTile.bottom();
|
||||
shadowQuads << makeShadowQuad(bottom, tx1, ty1, tx2, ty2);
|
||||
|
||||
QTest::newRow(testName) << windowSize << shadowQuads;
|
||||
}
|
||||
|
||||
// Top-Left & Top-Right/Bottom-Left & Bottom-Right overlap:
|
||||
// In this case overlapping parts are clipped and top/bottom
|
||||
// tiles aren't rendered.
|
||||
const QVector<QPair<QByteArray, QSize>> horizontalOverlapTestTable {
|
||||
QPair<QByteArray, QSize> {
|
||||
QByteArray("top-left & top-right/bottom-left & bottom-right overlap"),
|
||||
QSize(256, 256 + 1)
|
||||
},
|
||||
QPair<QByteArray, QSize> {
|
||||
QByteArray("top-left & top-right/bottom-left & bottom-right overlap :: pre"),
|
||||
QSize(256 - 1, 256 + 1)
|
||||
}
|
||||
// No need to test the case when window size is QSize(256 + 1, 256 + 1).
|
||||
// It has been tested already (no overlaps test case).
|
||||
};
|
||||
|
||||
for (auto const &tt : horizontalOverlapTestTable) {
|
||||
const char *testName = tt.first.constData();
|
||||
const QSize windowSize = tt.second;
|
||||
|
||||
WindowQuadList shadowQuads;
|
||||
qreal halfOverlap = 0.0;
|
||||
|
||||
const QRectF outerRect(
|
||||
-SHADOW_PADDING_LEFT,
|
||||
-SHADOW_PADDING_TOP,
|
||||
windowSize.width() + SHADOW_PADDING_LEFT + SHADOW_PADDING_RIGHT,
|
||||
windowSize.height() + SHADOW_PADDING_TOP + SHADOW_PADDING_BOTTOM);
|
||||
|
||||
QRectF topLeft(
|
||||
outerRect.left(),
|
||||
outerRect.top(),
|
||||
topLeftTile.width(),
|
||||
topLeftTile.height());
|
||||
|
||||
QRectF topRight(
|
||||
outerRect.right() - topRightTile.width(),
|
||||
outerRect.top(),
|
||||
topRightTile.width(),
|
||||
topRightTile.height());
|
||||
|
||||
halfOverlap = qAbs(topLeft.right() - topRight.left()) / 2;
|
||||
topLeft.setRight(topLeft.right() - std::floor(halfOverlap));
|
||||
topRight.setLeft(topRight.left() + std::ceil(halfOverlap));
|
||||
|
||||
tx1 = topLeftTile.left();
|
||||
ty1 = topLeftTile.top();
|
||||
tx2 = topLeft.width();
|
||||
ty2 = topLeftTile.bottom();
|
||||
shadowQuads << makeShadowQuad(topLeft, tx1, ty1, tx2, ty2);
|
||||
|
||||
tx1 = SHADOW_TEXTURE_WIDTH - topRight.width();
|
||||
ty1 = topRightTile.top();
|
||||
tx2 = topRightTile.right();
|
||||
ty2 = topRightTile.bottom();
|
||||
shadowQuads << makeShadowQuad(topRight, tx1, ty1, tx2, ty2);
|
||||
|
||||
QRectF bottomLeft(
|
||||
outerRect.left(),
|
||||
outerRect.bottom() - bottomLeftTile.height(),
|
||||
bottomLeftTile.width(),
|
||||
bottomLeftTile.height());
|
||||
|
||||
QRectF bottomRight(
|
||||
outerRect.right() - bottomRightTile.width(),
|
||||
outerRect.bottom() - bottomRightTile.height(),
|
||||
bottomRightTile.width(),
|
||||
bottomRightTile.height());
|
||||
|
||||
halfOverlap = qAbs(bottomLeft.right() - bottomRight.left()) / 2;
|
||||
bottomLeft.setRight(bottomLeft.right() - std::floor(halfOverlap));
|
||||
bottomRight.setLeft(bottomRight.left() + std::ceil(halfOverlap));
|
||||
|
||||
tx1 = bottomLeftTile.left();
|
||||
ty1 = bottomLeftTile.top();
|
||||
tx2 = bottomLeft.width();
|
||||
ty2 = bottomLeftTile.bottom();
|
||||
shadowQuads << makeShadowQuad(bottomLeft, tx1, ty1, tx2, ty2);
|
||||
|
||||
tx1 = SHADOW_TEXTURE_WIDTH - bottomRight.width();
|
||||
ty1 = bottomRightTile.top();
|
||||
tx2 = bottomRightTile.right();
|
||||
ty2 = bottomRightTile.bottom();
|
||||
shadowQuads << makeShadowQuad(bottomRight, tx1, ty1, tx2, ty2);
|
||||
|
||||
const QRectF left(topLeft.bottomLeft(), bottomLeft.topRight());
|
||||
tx1 = leftTile.left();
|
||||
ty1 = leftTile.top();
|
||||
tx2 = left.width();
|
||||
ty2 = leftTile.bottom();
|
||||
shadowQuads << makeShadowQuad(left, tx1, ty1, tx2, ty2);
|
||||
|
||||
const QRectF right(topRight.bottomLeft(), bottomRight.topRight());
|
||||
tx1 = SHADOW_TEXTURE_WIDTH - right.width();
|
||||
ty1 = rightTile.top();
|
||||
tx2 = rightTile.right();
|
||||
ty2 = rightTile.bottom();
|
||||
shadowQuads << makeShadowQuad(right, tx1, ty1, tx2, ty2);
|
||||
|
||||
QTest::newRow(testName) << windowSize << shadowQuads;
|
||||
}
|
||||
|
||||
// All shadow tiles overlap: In this case all overlapping parts
|
||||
// are clippend and top/right/bottom/left tiles aren't rendered.
|
||||
const QVector<QPair<QByteArray, QSize>> allOverlapTestTable {
|
||||
QPair<QByteArray, QSize> {
|
||||
QByteArray("all corner tiles overlap"),
|
||||
QSize(256, 256)
|
||||
},
|
||||
QPair<QByteArray, QSize> {
|
||||
QByteArray("all corner tiles overlap :: pre"),
|
||||
QSize(256 - 1, 256 - 1)
|
||||
}
|
||||
// No need to test the case when window size is QSize(256 + 1, 256 + 1).
|
||||
// It has been tested already (no overlaps test case).
|
||||
};
|
||||
|
||||
for (auto const &tt : allOverlapTestTable) {
|
||||
const char *testName = tt.first.constData();
|
||||
const QSize windowSize = tt.second;
|
||||
|
||||
WindowQuadList shadowQuads;
|
||||
qreal halfOverlap = 0.0;
|
||||
|
||||
const QRectF outerRect(
|
||||
-SHADOW_PADDING_LEFT,
|
||||
-SHADOW_PADDING_TOP,
|
||||
windowSize.width() + SHADOW_PADDING_LEFT + SHADOW_PADDING_RIGHT,
|
||||
windowSize.height() + SHADOW_PADDING_TOP + SHADOW_PADDING_BOTTOM);
|
||||
|
||||
QRectF topLeft(
|
||||
outerRect.left(),
|
||||
outerRect.top(),
|
||||
topLeftTile.width(),
|
||||
topLeftTile.height());
|
||||
|
||||
QRectF topRight(
|
||||
outerRect.right() - topRightTile.width(),
|
||||
outerRect.top(),
|
||||
topRightTile.width(),
|
||||
topRightTile.height());
|
||||
|
||||
QRectF bottomLeft(
|
||||
outerRect.left(),
|
||||
outerRect.bottom() - bottomLeftTile.height(),
|
||||
bottomLeftTile.width(),
|
||||
bottomLeftTile.height());
|
||||
|
||||
QRectF bottomRight(
|
||||
outerRect.right() - bottomRightTile.width(),
|
||||
outerRect.bottom() - bottomRightTile.height(),
|
||||
bottomRightTile.width(),
|
||||
bottomRightTile.height());
|
||||
|
||||
halfOverlap = qAbs(topLeft.right() - topRight.left()) / 2;
|
||||
topLeft.setRight(topLeft.right() - std::floor(halfOverlap));
|
||||
topRight.setLeft(topRight.left() + std::ceil(halfOverlap));
|
||||
|
||||
halfOverlap = qAbs(bottomLeft.right() - bottomRight.left()) / 2;
|
||||
bottomLeft.setRight(bottomLeft.right() - std::floor(halfOverlap));
|
||||
bottomRight.setLeft(bottomRight.left() + std::ceil(halfOverlap));
|
||||
|
||||
halfOverlap = qAbs(topLeft.bottom() - bottomLeft.top()) / 2;
|
||||
topLeft.setBottom(topLeft.bottom() - std::floor(halfOverlap));
|
||||
bottomLeft.setTop(bottomLeft.top() + std::ceil(halfOverlap));
|
||||
|
||||
halfOverlap = qAbs(topRight.bottom() - bottomRight.top()) / 2;
|
||||
topRight.setBottom(topRight.bottom() - std::floor(halfOverlap));
|
||||
bottomRight.setTop(bottomRight.top() + std::ceil(halfOverlap));
|
||||
|
||||
tx1 = topLeftTile.left();
|
||||
ty1 = topLeftTile.top();
|
||||
tx2 = topLeft.width();
|
||||
ty2 = topLeft.height();
|
||||
shadowQuads << makeShadowQuad(topLeft, tx1, ty1, tx2, ty2);
|
||||
|
||||
tx1 = SHADOW_TEXTURE_WIDTH - topRight.width();
|
||||
ty1 = topRightTile.top();
|
||||
tx2 = topRightTile.right();
|
||||
ty2 = topRight.height();
|
||||
shadowQuads << makeShadowQuad(topRight, tx1, ty1, tx2, ty2);
|
||||
|
||||
tx1 = bottomLeftTile.left();
|
||||
ty1 = SHADOW_TEXTURE_HEIGHT - bottomLeft.height();
|
||||
tx2 = bottomLeft.width();
|
||||
ty2 = bottomLeftTile.bottom();
|
||||
shadowQuads << makeShadowQuad(bottomLeft, tx1, ty1, tx2, ty2);
|
||||
|
||||
tx1 = SHADOW_TEXTURE_WIDTH - bottomRight.width();
|
||||
ty1 = SHADOW_TEXTURE_HEIGHT - bottomRight.height();
|
||||
tx2 = bottomRightTile.right();
|
||||
ty2 = bottomRightTile.bottom();
|
||||
shadowQuads << makeShadowQuad(bottomRight, tx1, ty1, tx2, ty2);
|
||||
|
||||
QTest::newRow(testName) << windowSize << shadowQuads;
|
||||
}
|
||||
|
||||
// Window is too small: do not render any shadow tiles.
|
||||
{
|
||||
const QSize windowSize(1, 1);
|
||||
const WindowQuadList shadowQuads;
|
||||
|
||||
QTest::newRow("window is too small") << windowSize << shadowQuads;
|
||||
}
|
||||
}
|
||||
|
||||
void SceneQPainterShadowTest::testShadowTileOverlaps()
|
||||
{
|
||||
QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Decoration));
|
||||
|
||||
QFETCH(QSize, windowSize);
|
||||
QFETCH(WindowQuadList, expectedQuads);
|
||||
|
||||
// Create a decorated client.
|
||||
QScopedPointer<Surface> surface(Test::createSurface());
|
||||
QScopedPointer<ShellSurface> shellSurface(Test::createShellSurface(surface.data()));
|
||||
QScopedPointer<ServerSideDecoration> ssd(Test::waylandServerSideDecoration()->create(surface.data()));
|
||||
|
||||
auto *client = Test::renderAndWaitForShown(surface.data(), windowSize, Qt::blue);
|
||||
|
||||
QSignalSpy sizeChangedSpy(shellSurface.data(), &ShellSurface::sizeChanged);
|
||||
QVERIFY(sizeChangedSpy.isValid());
|
||||
|
||||
// Check the client is decorated.
|
||||
QVERIFY(client);
|
||||
QVERIFY(client->isDecorated());
|
||||
auto *decoration = client->decoration();
|
||||
QVERIFY(decoration);
|
||||
|
||||
// If speciefied decoration theme is not found, KWin loads a default one
|
||||
// so we have to check whether a client has right decoration.
|
||||
auto decoShadow = decoration->shadow();
|
||||
QCOMPARE(decoShadow->shadow().size(), QSize(SHADOW_TEXTURE_WIDTH, SHADOW_TEXTURE_HEIGHT));
|
||||
QCOMPARE(decoShadow->paddingTop(), SHADOW_PADDING_TOP);
|
||||
QCOMPARE(decoShadow->paddingRight(), SHADOW_PADDING_RIGHT);
|
||||
QCOMPARE(decoShadow->paddingBottom(), SHADOW_PADDING_BOTTOM);
|
||||
QCOMPARE(decoShadow->paddingLeft(), SHADOW_PADDING_LEFT);
|
||||
|
||||
// Get shadow.
|
||||
QVERIFY(client->effectWindow());
|
||||
QVERIFY(client->effectWindow()->sceneWindow());
|
||||
QVERIFY(client->effectWindow()->sceneWindow()->shadow());
|
||||
auto *shadow = client->effectWindow()->sceneWindow()->shadow();
|
||||
|
||||
// Validate shadow quads.
|
||||
const WindowQuadList &quads = shadow->shadowQuads();
|
||||
QCOMPARE(quads.size(), expectedQuads.size());
|
||||
|
||||
QVector<bool> mask(expectedQuads.size(), false);
|
||||
for (const auto &q : quads) {
|
||||
for (int i = 0; i < expectedQuads.size(); i++) {
|
||||
if (!compareQuads(q, expectedQuads[i])) {
|
||||
continue;
|
||||
}
|
||||
if (!mask[i]) {
|
||||
mask[i] = true;
|
||||
break;
|
||||
} else {
|
||||
QFAIL("got a duplicate shadow quad");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &v : qAsConst(mask)) {
|
||||
if (!v) {
|
||||
QFAIL("missed a shadow quad");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SceneQPainterShadowTest::testShadowTextureReconstruction()
|
||||
{
|
||||
QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::ShadowManager));
|
||||
|
||||
// Create a surface.
|
||||
QScopedPointer<Surface> surface(Test::createSurface());
|
||||
QScopedPointer<ShellSurface> shellSurface(Test::createShellSurface(surface.data()));
|
||||
auto *client = Test::renderAndWaitForShown(surface.data(), QSize(512, 512), Qt::blue);
|
||||
QVERIFY(client);
|
||||
QVERIFY(!client->isDecorated());
|
||||
|
||||
// Render reference shadow texture with the following params:
|
||||
// - shadow size: 128
|
||||
// - inner rect size: 1
|
||||
// - padding: 128
|
||||
QImage referenceShadowTexture(QSize(256 + 1, 256 + 1), QImage::Format_ARGB32_Premultiplied);
|
||||
referenceShadowTexture.fill(Qt::transparent);
|
||||
|
||||
QPainter painter(&referenceShadowTexture);
|
||||
painter.fillRect(QRect(10, 10, 192, 200), QColor(255, 0, 0, 128));
|
||||
painter.fillRect(QRect(128, 30, 10, 180), QColor(0, 0, 0, 30));
|
||||
painter.fillRect(QRect(20, 140, 160, 10), QColor(0, 255, 0, 128));
|
||||
|
||||
painter.setCompositionMode(QPainter::CompositionMode_DestinationOut);
|
||||
painter.fillRect(QRect(128, 128, 1, 1), Qt::black);
|
||||
painter.end();
|
||||
|
||||
// Create shadow.
|
||||
QScopedPointer<KWayland::Client::Shadow> clientShadow(Test::waylandShadowManager()->createShadow(surface.data()));
|
||||
QVERIFY(clientShadow->isValid());
|
||||
|
||||
auto *shmPool = Test::waylandShmPool();
|
||||
|
||||
Buffer::Ptr bufferTopLeft = shmPool->createBuffer(
|
||||
referenceShadowTexture.copy(QRect(0, 0, 128, 128)));
|
||||
clientShadow->attachTopLeft(bufferTopLeft);
|
||||
|
||||
Buffer::Ptr bufferTop = shmPool->createBuffer(
|
||||
referenceShadowTexture.copy(QRect(128, 0, 1, 128)));
|
||||
clientShadow->attachTop(bufferTop);
|
||||
|
||||
Buffer::Ptr bufferTopRight = shmPool->createBuffer(
|
||||
referenceShadowTexture.copy(QRect(128 + 1, 0, 128, 128)));
|
||||
clientShadow->attachTopRight(bufferTopRight);
|
||||
|
||||
Buffer::Ptr bufferRight = shmPool->createBuffer(
|
||||
referenceShadowTexture.copy(QRect(128 + 1, 128, 128, 1)));
|
||||
clientShadow->attachRight(bufferRight);
|
||||
|
||||
Buffer::Ptr bufferBottomRight = shmPool->createBuffer(
|
||||
referenceShadowTexture.copy(QRect(128 + 1, 128 + 1, 128, 128)));
|
||||
clientShadow->attachBottomRight(bufferBottomRight);
|
||||
|
||||
Buffer::Ptr bufferBottom = shmPool->createBuffer(
|
||||
referenceShadowTexture.copy(QRect(128, 128 + 1, 1, 128)));
|
||||
clientShadow->attachBottom(bufferBottom);
|
||||
|
||||
Buffer::Ptr bufferBottomLeft = shmPool->createBuffer(
|
||||
referenceShadowTexture.copy(QRect(0, 128 + 1, 128, 128)));
|
||||
clientShadow->attachBottomLeft(bufferBottomLeft);
|
||||
|
||||
Buffer::Ptr bufferLeft = shmPool->createBuffer(
|
||||
referenceShadowTexture.copy(QRect(0, 128, 128, 1)));
|
||||
clientShadow->attachLeft(bufferLeft);
|
||||
|
||||
clientShadow->setOffsets(QMarginsF(128, 128, 128, 128));
|
||||
|
||||
// Commit shadow.
|
||||
QSignalSpy shadowChangedSpy(client->surface(), &KWayland::Server::SurfaceInterface::shadowChanged);
|
||||
QVERIFY(shadowChangedSpy.isValid());
|
||||
clientShadow->commit();
|
||||
surface->commit(Surface::CommitFlag::None);
|
||||
QVERIFY(shadowChangedSpy.wait());
|
||||
|
||||
// Check whether we've got right shadow.
|
||||
auto shadowIface = client->surface()->shadow();
|
||||
QVERIFY(!shadowIface.isNull());
|
||||
QCOMPARE(shadowIface->offset().left(), 128);
|
||||
QCOMPARE(shadowIface->offset().top(), 128);
|
||||
QCOMPARE(shadowIface->offset().right(), 128);
|
||||
QCOMPARE(shadowIface->offset().bottom(), 128);
|
||||
|
||||
// Get SceneQPainterShadow's texture.
|
||||
QVERIFY(client->effectWindow());
|
||||
QVERIFY(client->effectWindow()->sceneWindow());
|
||||
QVERIFY(client->effectWindow()->sceneWindow()->shadow());
|
||||
auto &shadowTexture = static_cast<SceneQPainterShadow *>(client->effectWindow()->sceneWindow()->shadow())->shadowTexture();
|
||||
|
||||
QCOMPARE(shadowTexture, referenceShadowTexture);
|
||||
}
|
||||
|
||||
WAYLANDTEST_MAIN(SceneQPainterShadowTest)
|
||||
#include "scene_qpainter_shadow_test.moc"
|
|
@ -32,6 +32,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
#include <KWayland/Client/pointerconstraints.h>
|
||||
#include <KWayland/Client/seat.h>
|
||||
#include <KWayland/Client/server_decoration.h>
|
||||
#include <KWayland/Client/shadow.h>
|
||||
#include <KWayland/Client/shell.h>
|
||||
#include <KWayland/Client/shm_pool.h>
|
||||
#include <KWayland/Client/output.h>
|
||||
|
@ -62,6 +63,7 @@ static struct {
|
|||
EventQueue *queue = nullptr;
|
||||
Compositor *compositor = nullptr;
|
||||
ServerSideDecorationManager *decoration = nullptr;
|
||||
ShadowManager *shadowManager = nullptr;
|
||||
Shell *shell = nullptr;
|
||||
XdgShell *xdgShellV5 = nullptr;
|
||||
XdgShell *xdgShellV6 = nullptr;
|
||||
|
@ -163,6 +165,13 @@ bool setupWaylandConnection(AdditionalWaylandInterfaces flags)
|
|||
return false;
|
||||
}
|
||||
}
|
||||
if (flags.testFlag(AdditionalWaylandInterface::ShadowManager)) {
|
||||
s_waylandConnection.shadowManager = registry->createShadowManager(registry->interface(Registry::Interface::Shadow).name,
|
||||
registry->interface(Registry::Interface::Shadow).version);
|
||||
if (!s_waylandConnection.shadowManager->isValid()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (flags.testFlag(AdditionalWaylandInterface::Decoration)) {
|
||||
s_waylandConnection.decoration = registry->createServerSideDecorationManager(registry->interface(Registry::Interface::ServerSideDecorationManager).name,
|
||||
registry->interface(Registry::Interface::ServerSideDecorationManager).version);
|
||||
|
@ -230,6 +239,8 @@ void destroyWaylandConnection()
|
|||
s_waylandConnection.xdgShellV6 = nullptr;
|
||||
delete s_waylandConnection.shell;
|
||||
s_waylandConnection.shell = nullptr;
|
||||
delete s_waylandConnection.shadowManager;
|
||||
s_waylandConnection.shadowManager = nullptr;
|
||||
delete s_waylandConnection.idleInhibit;
|
||||
s_waylandConnection.idleInhibit = nullptr;
|
||||
delete s_waylandConnection.shm;
|
||||
|
@ -264,6 +275,11 @@ Compositor *waylandCompositor()
|
|||
return s_waylandConnection.compositor;
|
||||
}
|
||||
|
||||
ShadowManager *waylandShadowManager()
|
||||
{
|
||||
return s_waylandConnection.shadowManager;
|
||||
}
|
||||
|
||||
Shell *waylandShell()
|
||||
{
|
||||
return s_waylandConnection.shell;
|
||||
|
|
|
@ -38,6 +38,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
#include <QPainter>
|
||||
#include <KDecoration2/Decoration>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace KWin
|
||||
{
|
||||
|
||||
|
@ -309,51 +311,21 @@ void SceneQPainter::Window::renderShadow(QPainter* painter)
|
|||
return;
|
||||
}
|
||||
SceneQPainterShadow *shadow = static_cast<SceneQPainterShadow *>(toplevel->shadow());
|
||||
const QPixmap &topLeft = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementTopLeft);
|
||||
const QPixmap &top = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementTop);
|
||||
const QPixmap &topRight = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementTopRight);
|
||||
const QPixmap &bottomLeft = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementBottomLeft);
|
||||
const QPixmap &bottom = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementBottom);
|
||||
const QPixmap &bottomRight = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementBottomRight);
|
||||
const QPixmap &left = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementLeft);
|
||||
const QPixmap &right = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementRight);
|
||||
|
||||
const int leftOffset = shadow->leftOffset();
|
||||
const int topOffset = shadow->topOffset();
|
||||
const int rightOffset = shadow->rightOffset();
|
||||
const int bottomOffset = shadow->bottomOffset();
|
||||
const QImage &shadowTexture = shadow->shadowTexture();
|
||||
const WindowQuadList &shadowQuads = shadow->shadowQuads();
|
||||
|
||||
// top left
|
||||
painter->drawPixmap(-leftOffset, -topOffset, topLeft);
|
||||
// top right
|
||||
painter->drawPixmap(toplevel->width() - topRight.width() + rightOffset, -topOffset, topRight);
|
||||
// bottom left
|
||||
painter->drawPixmap(-leftOffset, toplevel->height() - bottomLeft.height() + bottomOffset, bottomLeft);
|
||||
// bottom right
|
||||
painter->drawPixmap(toplevel->width() - bottomRight.width() + rightOffset,
|
||||
toplevel->height() - bottomRight.height() + bottomOffset,
|
||||
bottomRight);
|
||||
// top
|
||||
painter->drawPixmap(topLeft.width() - leftOffset, -topOffset,
|
||||
toplevel->width() - topLeft.width() - topRight.width() + leftOffset + rightOffset,
|
||||
top.height(),
|
||||
top);
|
||||
// left
|
||||
painter->drawPixmap(-leftOffset, topLeft.height() - topOffset, left.width(),
|
||||
toplevel->height() - topLeft.height() - bottomLeft.height() + topOffset + bottomOffset,
|
||||
left);
|
||||
// right
|
||||
painter->drawPixmap(toplevel->width() - right.width() + rightOffset,
|
||||
topRight.height() - topOffset,
|
||||
right.width(),
|
||||
toplevel->height() - topRight.height() - bottomRight.height() + topOffset + bottomOffset,
|
||||
right);
|
||||
// bottom
|
||||
painter->drawPixmap(bottomLeft.width() - leftOffset,
|
||||
toplevel->height() - bottom.height() + bottomOffset,
|
||||
toplevel->width() - bottomLeft.width() - bottomRight.width() + leftOffset + rightOffset,
|
||||
bottom.height(),
|
||||
bottom);
|
||||
for (const auto &q : shadowQuads) {
|
||||
auto topLeft = q[0];
|
||||
auto bottomRight = q[2];
|
||||
QRectF target(topLeft.x(), topLeft.y(),
|
||||
bottomRight.x() - topLeft.x(),
|
||||
bottomRight.y() - topLeft.y());
|
||||
QRectF source(topLeft.textureX(), topLeft.textureY(),
|
||||
bottomRight.textureX() - topLeft.textureX(),
|
||||
bottomRight.textureY() - topLeft.textureY());
|
||||
painter->drawImage(target, shadowTexture, source);
|
||||
}
|
||||
}
|
||||
|
||||
void SceneQPainter::Window::renderWindowDecorations(QPainter *painter)
|
||||
|
@ -557,12 +529,242 @@ SceneQPainterShadow::~SceneQPainterShadow()
|
|||
{
|
||||
}
|
||||
|
||||
void SceneQPainterShadow::buildQuads()
|
||||
{
|
||||
// Do not draw shadows if window width or window height is less than
|
||||
// 5 px. 5 is an arbitrary choice.
|
||||
if (topLevel()->width() < 5 || topLevel()->height() < 5) {
|
||||
m_shadowQuads.clear();
|
||||
setShadowRegion(QRegion());
|
||||
return;
|
||||
}
|
||||
|
||||
const QSizeF top(elementSize(ShadowElementTop));
|
||||
const QSizeF topRight(elementSize(ShadowElementTopRight));
|
||||
const QSizeF right(elementSize(ShadowElementRight));
|
||||
const QSizeF bottomRight(elementSize(ShadowElementBottomRight));
|
||||
const QSizeF bottom(elementSize(ShadowElementBottom));
|
||||
const QSizeF bottomLeft(elementSize(ShadowElementBottomLeft));
|
||||
const QSizeF left(elementSize(ShadowElementLeft));
|
||||
const QSizeF topLeft(elementSize(ShadowElementTopLeft));
|
||||
|
||||
const QRectF outerRect(QPointF(-leftOffset(), -topOffset()),
|
||||
QPointF(topLevel()->width() + rightOffset(),
|
||||
topLevel()->height() + bottomOffset()));
|
||||
|
||||
const int width = std::max({topLeft.width(), left.width(), bottomLeft.width()})
|
||||
+ std::max(top.width(), bottom.width())
|
||||
+ std::max({topRight.width(), right.width(), bottomRight.width()});
|
||||
const int height = std::max({topLeft.height(), top.height(), topRight.height()})
|
||||
+ std::max(left.height(), right.height())
|
||||
+ std::max({bottomLeft.height(), bottom.height(), bottomRight.height()});
|
||||
|
||||
QRectF topLeftRect(outerRect.topLeft(), topLeft);
|
||||
QRectF topRightRect(outerRect.topRight() - QPointF(topRight.width(), 0), topRight);
|
||||
QRectF bottomRightRect(
|
||||
outerRect.bottomRight() - QPointF(bottomRight.width(), bottomRight.height()),
|
||||
bottomRight);
|
||||
QRectF bottomLeftRect(outerRect.bottomLeft() - QPointF(0, bottomLeft.height()), bottomLeft);
|
||||
|
||||
// Re-distribute the corner tiles so no one of them is overlapping with others.
|
||||
// By doing this, we assume that shadow's corner tiles are symmetric
|
||||
// and it is OK to not draw top/right/bottom/left tile between corners.
|
||||
// For example, let's say top-left and top-right tiles are overlapping.
|
||||
// In that case, the right side of the top-left tile will be shifted to left,
|
||||
// the left side of the top-right tile will shifted to right, and the top
|
||||
// tile won't be rendered.
|
||||
bool drawTop = true;
|
||||
if (topLeftRect.right() >= topRightRect.left()) {
|
||||
const float halfOverlap = qAbs(topLeftRect.right() - topRightRect.left()) / 2;
|
||||
topLeftRect.setRight(topLeftRect.right() - std::floor(halfOverlap));
|
||||
topRightRect.setLeft(topRightRect.left() + std::ceil(halfOverlap));
|
||||
drawTop = false;
|
||||
}
|
||||
|
||||
bool drawRight = true;
|
||||
if (topRightRect.bottom() >= bottomRightRect.top()) {
|
||||
const float halfOverlap = qAbs(topRightRect.bottom() - bottomRightRect.top()) / 2;
|
||||
topRightRect.setBottom(topRightRect.bottom() - std::floor(halfOverlap));
|
||||
bottomRightRect.setTop(bottomRightRect.top() + std::ceil(halfOverlap));
|
||||
drawRight = false;
|
||||
}
|
||||
|
||||
bool drawBottom = true;
|
||||
if (bottomLeftRect.right() >= bottomRightRect.left()) {
|
||||
const float halfOverlap = qAbs(bottomLeftRect.right() - bottomRightRect.left()) / 2;
|
||||
bottomLeftRect.setRight(bottomLeftRect.right() - std::floor(halfOverlap));
|
||||
bottomRightRect.setLeft(bottomRightRect.left() + std::ceil(halfOverlap));
|
||||
drawBottom = false;
|
||||
}
|
||||
|
||||
bool drawLeft = true;
|
||||
if (topLeftRect.bottom() >= bottomLeftRect.top()) {
|
||||
const float halfOverlap = qAbs(topLeftRect.bottom() - bottomLeftRect.top()) / 2;
|
||||
topLeftRect.setBottom(topLeftRect.bottom() - std::floor(halfOverlap));
|
||||
bottomLeftRect.setTop(bottomLeftRect.top() + std::ceil(halfOverlap));
|
||||
drawLeft = false;
|
||||
}
|
||||
|
||||
qreal tx1 = 0.0,
|
||||
tx2 = 0.0,
|
||||
ty1 = 0.0,
|
||||
ty2 = 0.0;
|
||||
|
||||
m_shadowQuads.clear();
|
||||
|
||||
tx1 = 0.0;
|
||||
ty1 = 0.0;
|
||||
tx2 = topLeftRect.width();
|
||||
ty2 = topLeftRect.height();
|
||||
WindowQuad topLeftQuad(WindowQuadShadow);
|
||||
topLeftQuad[0] = WindowVertex(topLeftRect.left(), topLeftRect.top(), tx1, ty1);
|
||||
topLeftQuad[1] = WindowVertex(topLeftRect.right(), topLeftRect.top(), tx2, ty1);
|
||||
topLeftQuad[2] = WindowVertex(topLeftRect.right(), topLeftRect.bottom(), tx2, ty2);
|
||||
topLeftQuad[3] = WindowVertex(topLeftRect.left(), topLeftRect.bottom(), tx1, ty2);
|
||||
m_shadowQuads.append(topLeftQuad);
|
||||
|
||||
tx1 = width - topRightRect.width();
|
||||
ty1 = 0.0;
|
||||
tx2 = width;
|
||||
ty2 = topRightRect.height();
|
||||
WindowQuad topRightQuad(WindowQuadShadow);
|
||||
topRightQuad[0] = WindowVertex(topRightRect.left(), topRightRect.top(), tx1, ty1);
|
||||
topRightQuad[1] = WindowVertex(topRightRect.right(), topRightRect.top(), tx2, ty1);
|
||||
topRightQuad[2] = WindowVertex(topRightRect.right(), topRightRect.bottom(), tx2, ty2);
|
||||
topRightQuad[3] = WindowVertex(topRightRect.left(), topRightRect.bottom(), tx1, ty2);
|
||||
m_shadowQuads.append(topRightQuad);
|
||||
|
||||
tx1 = width - bottomRightRect.width();
|
||||
tx2 = width;
|
||||
ty1 = height - bottomRightRect.height();
|
||||
ty2 = height;
|
||||
WindowQuad bottomRightQuad(WindowQuadShadow);
|
||||
bottomRightQuad[0] = WindowVertex(bottomRightRect.left(), bottomRightRect.top(), tx1, ty1);
|
||||
bottomRightQuad[1] = WindowVertex(bottomRightRect.right(), bottomRightRect.top(), tx2, ty1);
|
||||
bottomRightQuad[2] = WindowVertex(bottomRightRect.right(), bottomRightRect.bottom(), tx2, ty2);
|
||||
bottomRightQuad[3] = WindowVertex(bottomRightRect.left(), bottomRightRect.bottom(), tx1, ty2);
|
||||
m_shadowQuads.append(bottomRightQuad);
|
||||
|
||||
tx1 = 0.0;
|
||||
tx2 = bottomLeftRect.width();
|
||||
ty1 = height - bottomLeftRect.height();
|
||||
ty2 = height;
|
||||
WindowQuad bottomLeftQuad(WindowQuadShadow);
|
||||
bottomLeftQuad[0] = WindowVertex(bottomLeftRect.left(), bottomLeftRect.top(), tx1, ty1);
|
||||
bottomLeftQuad[1] = WindowVertex(bottomLeftRect.right(), bottomLeftRect.top(), tx2, ty1);
|
||||
bottomLeftQuad[2] = WindowVertex(bottomLeftRect.right(), bottomLeftRect.bottom(), tx2, ty2);
|
||||
bottomLeftQuad[3] = WindowVertex(bottomLeftRect.left(), bottomLeftRect.bottom(), tx1, ty2);
|
||||
m_shadowQuads.append(bottomLeftQuad);
|
||||
|
||||
if (drawTop) {
|
||||
QRectF topRect(
|
||||
topLeftRect.topRight(),
|
||||
topRightRect.bottomLeft());
|
||||
tx1 = topLeft.width();
|
||||
ty1 = 0.0;
|
||||
tx2 = width - topRight.width();
|
||||
ty2 = topRect.height();
|
||||
WindowQuad topQuad(WindowQuadShadow);
|
||||
topQuad[0] = WindowVertex(topRect.left(), topRect.top(), tx1, ty1);
|
||||
topQuad[1] = WindowVertex(topRect.right(), topRect.top(), tx2, ty1);
|
||||
topQuad[2] = WindowVertex(topRect.right(), topRect.bottom(), tx2, ty2);
|
||||
topQuad[3] = WindowVertex(topRect.left(), topRect.bottom(), tx1, ty2);
|
||||
m_shadowQuads.append(topQuad);
|
||||
}
|
||||
|
||||
if (drawRight) {
|
||||
QRectF rightRect(
|
||||
topRightRect.bottomLeft(),
|
||||
bottomRightRect.topRight());
|
||||
tx1 = width - rightRect.width();
|
||||
ty1 = topRight.height();
|
||||
tx2 = width;
|
||||
ty2 = height - bottomRight.height();
|
||||
WindowQuad rightQuad(WindowQuadShadow);
|
||||
rightQuad[0] = WindowVertex(rightRect.left(), rightRect.top(), tx1, ty1);
|
||||
rightQuad[1] = WindowVertex(rightRect.right(), rightRect.top(), tx2, ty1);
|
||||
rightQuad[2] = WindowVertex(rightRect.right(), rightRect.bottom(), tx2, ty2);
|
||||
rightQuad[3] = WindowVertex(rightRect.left(), rightRect.bottom(), tx1, ty2);
|
||||
m_shadowQuads.append(rightQuad);
|
||||
}
|
||||
|
||||
if (drawBottom) {
|
||||
QRectF bottomRect(
|
||||
bottomLeftRect.topRight(),
|
||||
bottomRightRect.bottomLeft());
|
||||
tx1 = bottomLeft.width();
|
||||
ty1 = height - bottomRect.height();
|
||||
tx2 = width - bottomRight.width();
|
||||
ty2 = height;
|
||||
WindowQuad bottomQuad(WindowQuadShadow);
|
||||
bottomQuad[0] = WindowVertex(bottomRect.left(), bottomRect.top(), tx1, ty1);
|
||||
bottomQuad[1] = WindowVertex(bottomRect.right(), bottomRect.top(), tx2, ty1);
|
||||
bottomQuad[2] = WindowVertex(bottomRect.right(), bottomRect.bottom(), tx2, ty2);
|
||||
bottomQuad[3] = WindowVertex(bottomRect.left(), bottomRect.bottom(), tx1, ty2);
|
||||
m_shadowQuads.append(bottomQuad);
|
||||
}
|
||||
|
||||
if (drawLeft) {
|
||||
QRectF leftRect(
|
||||
topLeftRect.bottomLeft(),
|
||||
bottomLeftRect.topRight());
|
||||
tx1 = 0.0;
|
||||
ty1 = topLeft.height();
|
||||
tx2 = leftRect.width();
|
||||
ty2 = height - bottomRight.height();
|
||||
WindowQuad leftQuad(WindowQuadShadow);
|
||||
leftQuad[0] = WindowVertex(leftRect.left(), leftRect.top(), tx1, ty1);
|
||||
leftQuad[1] = WindowVertex(leftRect.right(), leftRect.top(), tx2, ty1);
|
||||
leftQuad[2] = WindowVertex(leftRect.right(), leftRect.bottom(), tx2, ty2);
|
||||
leftQuad[3] = WindowVertex(leftRect.left(), leftRect.bottom(), tx1, ty2);
|
||||
m_shadowQuads.append(leftQuad);
|
||||
}
|
||||
}
|
||||
|
||||
bool SceneQPainterShadow::prepareBackend()
|
||||
{
|
||||
if (hasDecorationShadow()) {
|
||||
// TODO: implement for QPainter
|
||||
m_texture = decorationShadowImage();
|
||||
return true;
|
||||
}
|
||||
|
||||
const QPixmap &topLeft = shadowPixmap(ShadowElementTopLeft);
|
||||
const QPixmap &top = shadowPixmap(ShadowElementTop);
|
||||
const QPixmap &topRight = shadowPixmap(ShadowElementTopRight);
|
||||
const QPixmap &bottomLeft = shadowPixmap(ShadowElementBottomLeft);
|
||||
const QPixmap &bottom = shadowPixmap(ShadowElementBottom);
|
||||
const QPixmap &bottomRight = shadowPixmap(ShadowElementBottomRight);
|
||||
const QPixmap &left = shadowPixmap(ShadowElementLeft);
|
||||
const QPixmap &right = shadowPixmap(ShadowElementRight);
|
||||
|
||||
const int width = std::max({topLeft.width(), left.width(), bottomLeft.width()})
|
||||
+ std::max(top.width(), bottom.width())
|
||||
+ std::max({topRight.width(), right.width(), bottomRight.width()});
|
||||
const int height = std::max({topLeft.height(), top.height(), topRight.height()})
|
||||
+ std::max(left.height(), right.height())
|
||||
+ std::max({bottomLeft.height(), bottom.height(), bottomRight.height()});
|
||||
|
||||
if (width == 0 || height == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QImage image(width, height, QImage::Format_ARGB32_Premultiplied);
|
||||
image.fill(Qt::transparent);
|
||||
|
||||
QPainter painter;
|
||||
painter.begin(&image);
|
||||
painter.drawPixmap(0, 0, topLeft);
|
||||
painter.drawPixmap(topLeft.width(), 0, top);
|
||||
painter.drawPixmap(width - topRight.width(), 0, topRight);
|
||||
painter.drawPixmap(0, height - bottomLeft.height(), bottomLeft);
|
||||
painter.drawPixmap(bottomLeft.width(), height - bottom.height(), bottom);
|
||||
painter.drawPixmap(width - bottomRight.width(), height - bottomRight.height(), bottomRight);
|
||||
painter.drawPixmap(0, topLeft.height(), left);
|
||||
painter.drawPixmap(width - right.width(), topRight.height(), right);
|
||||
painter.end();
|
||||
|
||||
m_texture = image;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -123,23 +123,17 @@ class SceneQPainterShadow : public Shadow
|
|||
public:
|
||||
SceneQPainterShadow(Toplevel* toplevel);
|
||||
virtual ~SceneQPainterShadow();
|
||||
using Shadow::ShadowElements;
|
||||
using Shadow::ShadowElementTop;
|
||||
using Shadow::ShadowElementTopRight;
|
||||
using Shadow::ShadowElementRight;
|
||||
using Shadow::ShadowElementBottomRight;
|
||||
using Shadow::ShadowElementBottom;
|
||||
using Shadow::ShadowElementBottomLeft;
|
||||
using Shadow::ShadowElementLeft;
|
||||
using Shadow::ShadowElementTopLeft;
|
||||
using Shadow::ShadowElementsCount;
|
||||
using Shadow::shadowPixmap;
|
||||
using Shadow::topOffset;
|
||||
using Shadow::leftOffset;
|
||||
using Shadow::rightOffset;
|
||||
using Shadow::bottomOffset;
|
||||
|
||||
QImage &shadowTexture() {
|
||||
return m_texture;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void buildQuads() override;
|
||||
virtual bool prepareBackend() override;
|
||||
|
||||
private:
|
||||
QImage m_texture;
|
||||
};
|
||||
|
||||
class SceneQPainterDecorationRenderer : public Decoration::Renderer
|
||||
|
|
Loading…
Reference in a new issue