1/*
2 * SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
3 *
4 * SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6
7#include "shadowedrectangle.h"
8
9#include <QQuickWindow>
10#include <QSGRectangleNode>
11#include <QSGRendererInterface>
12
13#include "scenegraph/shadernode.h"
14#include "scenegraph/softwarerectanglenode.h"
15
16using namespace Qt::StringLiterals;
17
18inline QVector2D calculateAspect(const QRectF &rect)
19{
20 auto aspect = QVector2D{1.0, 1.0};
21 if (rect.width() >= rect.height()) {
22 aspect.setX(rect.width() / rect.height());
23 } else {
24 aspect.setY(rect.height() / rect.width());
25 }
26 return aspect;
27}
28
29inline QRectF adjustRectForShadow(const QRectF &rect, float shadowSize, const QVector2D &offsets, const QVector2D &aspect)
30{
31 auto adjusted = rect.adjusted(xp1: -shadowSize * aspect.x(), yp1: -shadowSize * aspect.y(), xp2: shadowSize * aspect.x(), yp2: shadowSize * aspect.y());
32 auto offsetLength = offsets.length();
33 adjusted.adjust(xp1: -offsetLength * aspect.x(), yp1: -offsetLength * aspect.y(), xp2: offsetLength * aspect.x(), yp2: offsetLength * aspect.y());
34 return adjusted;
35}
36
37BorderGroup::BorderGroup(QObject *parent)
38 : QObject(parent)
39{
40}
41
42qreal BorderGroup::width() const
43{
44 return m_width;
45}
46
47void BorderGroup::setWidth(qreal newWidth)
48{
49 if (newWidth == m_width) {
50 return;
51 }
52
53 m_width = newWidth;
54 Q_EMIT changed();
55}
56
57QColor BorderGroup::color() const
58{
59 return m_color;
60}
61
62void BorderGroup::setColor(const QColor &newColor)
63{
64 if (newColor == m_color) {
65 return;
66 }
67
68 m_color = newColor;
69 Q_EMIT changed();
70}
71
72ShadowGroup::ShadowGroup(QObject *parent)
73 : QObject(parent)
74{
75}
76
77qreal ShadowGroup::size() const
78{
79 return m_size;
80}
81
82void ShadowGroup::setSize(qreal newSize)
83{
84 if (newSize == m_size) {
85 return;
86 }
87
88 m_size = newSize;
89 Q_EMIT changed();
90}
91
92qreal ShadowGroup::xOffset() const
93{
94 return m_xOffset;
95}
96
97void ShadowGroup::setXOffset(qreal newXOffset)
98{
99 if (newXOffset == m_xOffset) {
100 return;
101 }
102
103 m_xOffset = newXOffset;
104 Q_EMIT changed();
105}
106
107qreal ShadowGroup::yOffset() const
108{
109 return m_yOffset;
110}
111
112void ShadowGroup::setYOffset(qreal newYOffset)
113{
114 if (newYOffset == m_yOffset) {
115 return;
116 }
117
118 m_yOffset = newYOffset;
119 Q_EMIT changed();
120}
121
122QColor ShadowGroup::color() const
123{
124 return m_color;
125}
126
127void ShadowGroup::setColor(const QColor &newColor)
128{
129 if (newColor == m_color) {
130 return;
131 }
132
133 m_color = newColor;
134 Q_EMIT changed();
135}
136
137CornersGroup::CornersGroup(QObject *parent)
138 : QObject(parent)
139{
140}
141
142qreal CornersGroup::topLeft() const
143{
144 return m_topLeft;
145}
146
147void CornersGroup::setTopLeft(qreal newTopLeft)
148{
149 if (newTopLeft == m_topLeft) {
150 return;
151 }
152
153 m_topLeft = newTopLeft;
154 Q_EMIT changed();
155}
156
157qreal CornersGroup::topRight() const
158{
159 return m_topRight;
160}
161
162void CornersGroup::setTopRight(qreal newTopRight)
163{
164 if (newTopRight == m_topRight) {
165 return;
166 }
167
168 m_topRight = newTopRight;
169 Q_EMIT changed();
170}
171
172qreal CornersGroup::bottomLeft() const
173{
174 return m_bottomLeft;
175}
176
177void CornersGroup::setBottomLeft(qreal newBottomLeft)
178{
179 if (newBottomLeft == m_bottomLeft) {
180 return;
181 }
182
183 m_bottomLeft = newBottomLeft;
184 Q_EMIT changed();
185}
186
187qreal CornersGroup::bottomRight() const
188{
189 return m_bottomRight;
190}
191
192void CornersGroup::setBottomRight(qreal newBottomRight)
193{
194 if (newBottomRight == m_bottomRight) {
195 return;
196 }
197
198 m_bottomRight = newBottomRight;
199 Q_EMIT changed();
200}
201
202QVector4D CornersGroup::toVector4D(float all) const
203{
204 return QVector4D{m_bottomRight < 0.0 ? all : m_bottomRight,
205 m_topRight < 0.0 ? all : m_topRight,
206 m_bottomLeft < 0.0 ? all : m_bottomLeft,
207 m_topLeft < 0.0 ? all : m_topLeft};
208}
209
210ShadowedRectangle::ShadowedRectangle(QQuickItem *parentItem)
211 : QQuickItem(parentItem)
212 , m_border(std::make_unique<BorderGroup>())
213 , m_shadow(std::make_unique<ShadowGroup>())
214 , m_corners(std::make_unique<CornersGroup>())
215{
216 setFlag(flag: QQuickItem::ItemHasContents, enabled: true);
217
218 connect(sender: m_border.get(), signal: &BorderGroup::changed, context: this, slot: &ShadowedRectangle::update);
219 connect(sender: m_shadow.get(), signal: &ShadowGroup::changed, context: this, slot: &ShadowedRectangle::update);
220 connect(sender: m_corners.get(), signal: &CornersGroup::changed, context: this, slot: &ShadowedRectangle::update);
221}
222
223ShadowedRectangle::~ShadowedRectangle()
224{
225}
226
227BorderGroup *ShadowedRectangle::border() const
228{
229 return m_border.get();
230}
231
232ShadowGroup *ShadowedRectangle::shadow() const
233{
234 return m_shadow.get();
235}
236
237CornersGroup *ShadowedRectangle::corners() const
238{
239 return m_corners.get();
240}
241
242qreal ShadowedRectangle::radius() const
243{
244 return m_radius;
245}
246
247void ShadowedRectangle::setRadius(qreal newRadius)
248{
249 if (newRadius == m_radius) {
250 return;
251 }
252
253 m_radius = newRadius;
254 update();
255 Q_EMIT radiusChanged();
256}
257
258QColor ShadowedRectangle::color() const
259{
260 return m_color;
261}
262
263void ShadowedRectangle::setColor(const QColor &newColor)
264{
265 if (newColor == m_color) {
266 return;
267 }
268
269 m_color = newColor;
270 update();
271 Q_EMIT colorChanged();
272}
273
274ShadowedRectangle::RenderType ShadowedRectangle::renderType() const
275{
276 return m_renderType;
277}
278
279void ShadowedRectangle::setRenderType(RenderType renderType)
280{
281 if (renderType == m_renderType) {
282 return;
283 }
284 m_renderType = renderType;
285 update();
286 Q_EMIT renderTypeChanged();
287}
288
289void ShadowedRectangle::componentComplete()
290{
291 QQuickItem::componentComplete();
292}
293
294bool ShadowedRectangle::isSoftwareRendering() const
295{
296 return (window() && window()->rendererInterface()->graphicsApi() == QSGRendererInterface::Software) || m_renderType == RenderType::Software;
297}
298
299bool ShadowedRectangle::isLowPowerRendering() const
300{
301 static bool lowPower = QByteArrayList{"1", "true"}.contains(t: qgetenv(varName: "KIRIGAMI_LOWPOWER_HARDWARE").toLower());
302 return (m_renderType == ShadowedRectangle::RenderType::Auto && lowPower) || m_renderType == ShadowedRectangle::RenderType::LowQuality;
303}
304
305QSGNode *ShadowedRectangle::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *data)
306{
307 Q_UNUSED(data);
308
309 if (boundingRect().isEmpty()) {
310 delete node;
311 return nullptr;
312 }
313
314 if (isSoftwareRendering()) {
315 auto rectangleNode = static_cast<SoftwareRectangleNode *>(node);
316 if (!rectangleNode) {
317 rectangleNode = new SoftwareRectangleNode{};
318 }
319
320 rectangleNode->setRect(boundingRect());
321 rectangleNode->setWindow(window());
322 rectangleNode->setColor(m_color);
323 rectangleNode->setRadius(m_radius);
324 rectangleNode->setBorderWidth(m_border->width());
325 rectangleNode->setBorderColor(m_border->color());
326 return rectangleNode;
327 }
328
329 auto shaderNode = static_cast<ShaderNode *>(node);
330 if (!shaderNode) {
331 shaderNode = new ShaderNode{};
332 }
333
334 QString shader;
335 if (m_border->isEnabled()) {
336 shader = u"shadowed_border_rectangle"_s;
337 } else {
338 shader = u"shadowed_rectangle"_s;
339 }
340
341 if (isLowPowerRendering()) {
342 shader += u"_lowpower"_s;
343 }
344
345 shaderNode->setShader(shader);
346 shaderNode->setUniformBufferSize(sizeof(float) * 40);
347
348 updateShaderNode(shaderNode);
349
350 shaderNode->update();
351
352 return shaderNode;
353}
354
355void ShadowedRectangle::updateShaderNode(ShaderNode *shaderNode)
356{
357 auto rect = boundingRect();
358 auto aspect = calculateAspect(rect);
359 auto minDimension = float(std::min(a: rect.width(), b: rect.height()));
360 auto shadowSize = m_shadow->size();
361 auto offset = QVector2D{float(m_shadow->xOffset()), float(m_shadow->yOffset())};
362
363 if (isLowPowerRendering()) {
364 shaderNode->setRect(rect);
365 } else {
366 shaderNode->setRect(adjustRectForShadow(rect, shadowSize, offsets: offset, aspect));
367 }
368
369 UniformDataStream stream(shaderNode->uniformData());
370 stream.skipMatrixOpacity();
371 stream << float(shadowSize / minDimension) * 2.0f // size
372 << float(m_border->width()) / minDimension // border_width
373 << aspect // aspect
374 << offset / minDimension // offset
375 << m_corners->toVector4D(all: m_radius) / minDimension // radius
376 << ShaderNode::toPremultiplied(value: m_color) // color
377 << ShaderNode::toPremultiplied(value: m_shadow->color()) // shadow_color
378 << ShaderNode::toPremultiplied(value: m_border->color()); // border_color
379 shaderNode->markDirty(bits: QSGNode::DirtyMaterial);
380}
381
382#include "moc_shadowedrectangle.cpp"
383

source code of kirigami/src/primitives/shadowedrectangle.cpp