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/paintedrectangleitem.h" |
14 | #include "scenegraph/shadowedrectanglenode.h" |
15 | |
16 | BorderGroup::BorderGroup(QObject *parent) |
17 | : QObject(parent) |
18 | { |
19 | } |
20 | |
21 | qreal BorderGroup::width() const |
22 | { |
23 | return m_width; |
24 | } |
25 | |
26 | void BorderGroup::setWidth(qreal newWidth) |
27 | { |
28 | if (newWidth == m_width) { |
29 | return; |
30 | } |
31 | |
32 | m_width = newWidth; |
33 | Q_EMIT changed(); |
34 | } |
35 | |
36 | QColor BorderGroup::color() const |
37 | { |
38 | return m_color; |
39 | } |
40 | |
41 | void BorderGroup::setColor(const QColor &newColor) |
42 | { |
43 | if (newColor == m_color) { |
44 | return; |
45 | } |
46 | |
47 | m_color = newColor; |
48 | Q_EMIT changed(); |
49 | } |
50 | |
51 | ShadowGroup::ShadowGroup(QObject *parent) |
52 | : QObject(parent) |
53 | { |
54 | } |
55 | |
56 | qreal ShadowGroup::size() const |
57 | { |
58 | return m_size; |
59 | } |
60 | |
61 | void ShadowGroup::setSize(qreal newSize) |
62 | { |
63 | if (newSize == m_size) { |
64 | return; |
65 | } |
66 | |
67 | m_size = newSize; |
68 | Q_EMIT changed(); |
69 | } |
70 | |
71 | qreal ShadowGroup::xOffset() const |
72 | { |
73 | return m_xOffset; |
74 | } |
75 | |
76 | void ShadowGroup::setXOffset(qreal newXOffset) |
77 | { |
78 | if (newXOffset == m_xOffset) { |
79 | return; |
80 | } |
81 | |
82 | m_xOffset = newXOffset; |
83 | Q_EMIT changed(); |
84 | } |
85 | |
86 | qreal ShadowGroup::yOffset() const |
87 | { |
88 | return m_yOffset; |
89 | } |
90 | |
91 | void ShadowGroup::setYOffset(qreal newYOffset) |
92 | { |
93 | if (newYOffset == m_yOffset) { |
94 | return; |
95 | } |
96 | |
97 | m_yOffset = newYOffset; |
98 | Q_EMIT changed(); |
99 | } |
100 | |
101 | QColor ShadowGroup::color() const |
102 | { |
103 | return m_color; |
104 | } |
105 | |
106 | void ShadowGroup::setColor(const QColor &newColor) |
107 | { |
108 | if (newColor == m_color) { |
109 | return; |
110 | } |
111 | |
112 | m_color = newColor; |
113 | Q_EMIT changed(); |
114 | } |
115 | |
116 | CornersGroup::CornersGroup(QObject *parent) |
117 | : QObject(parent) |
118 | { |
119 | } |
120 | |
121 | qreal CornersGroup::topLeft() const |
122 | { |
123 | return m_topLeft; |
124 | } |
125 | |
126 | void CornersGroup::setTopLeft(qreal newTopLeft) |
127 | { |
128 | if (newTopLeft == m_topLeft) { |
129 | return; |
130 | } |
131 | |
132 | m_topLeft = newTopLeft; |
133 | Q_EMIT changed(); |
134 | } |
135 | |
136 | qreal CornersGroup::topRight() const |
137 | { |
138 | return m_topRight; |
139 | } |
140 | |
141 | void CornersGroup::setTopRight(qreal newTopRight) |
142 | { |
143 | if (newTopRight == m_topRight) { |
144 | return; |
145 | } |
146 | |
147 | m_topRight = newTopRight; |
148 | Q_EMIT changed(); |
149 | } |
150 | |
151 | qreal CornersGroup::bottomLeft() const |
152 | { |
153 | return m_bottomLeft; |
154 | } |
155 | |
156 | void CornersGroup::setBottomLeft(qreal newBottomLeft) |
157 | { |
158 | if (newBottomLeft == m_bottomLeft) { |
159 | return; |
160 | } |
161 | |
162 | m_bottomLeft = newBottomLeft; |
163 | Q_EMIT changed(); |
164 | } |
165 | |
166 | qreal CornersGroup::bottomRight() const |
167 | { |
168 | return m_bottomRight; |
169 | } |
170 | |
171 | void CornersGroup::setBottomRight(qreal newBottomRight) |
172 | { |
173 | if (newBottomRight == m_bottomRight) { |
174 | return; |
175 | } |
176 | |
177 | m_bottomRight = newBottomRight; |
178 | Q_EMIT changed(); |
179 | } |
180 | |
181 | QVector4D CornersGroup::toVector4D(float all) const |
182 | { |
183 | return QVector4D{m_bottomRight < 0.0 ? all : m_bottomRight, |
184 | m_topRight < 0.0 ? all : m_topRight, |
185 | m_bottomLeft < 0.0 ? all : m_bottomLeft, |
186 | m_topLeft < 0.0 ? all : m_topLeft}; |
187 | } |
188 | |
189 | ShadowedRectangle::ShadowedRectangle(QQuickItem *parentItem) |
190 | : QQuickItem(parentItem) |
191 | , m_border(std::make_unique<BorderGroup>()) |
192 | , m_shadow(std::make_unique<ShadowGroup>()) |
193 | , m_corners(std::make_unique<CornersGroup>()) |
194 | { |
195 | setFlag(flag: QQuickItem::ItemHasContents, enabled: true); |
196 | |
197 | connect(sender: m_border.get(), signal: &BorderGroup::changed, context: this, slot: &ShadowedRectangle::update); |
198 | connect(sender: m_shadow.get(), signal: &ShadowGroup::changed, context: this, slot: &ShadowedRectangle::update); |
199 | connect(sender: m_corners.get(), signal: &CornersGroup::changed, context: this, slot: &ShadowedRectangle::update); |
200 | } |
201 | |
202 | ShadowedRectangle::~ShadowedRectangle() |
203 | { |
204 | } |
205 | |
206 | BorderGroup *ShadowedRectangle::border() const |
207 | { |
208 | return m_border.get(); |
209 | } |
210 | |
211 | ShadowGroup *ShadowedRectangle::shadow() const |
212 | { |
213 | return m_shadow.get(); |
214 | } |
215 | |
216 | CornersGroup *ShadowedRectangle::corners() const |
217 | { |
218 | return m_corners.get(); |
219 | } |
220 | |
221 | qreal ShadowedRectangle::radius() const |
222 | { |
223 | return m_radius; |
224 | } |
225 | |
226 | void ShadowedRectangle::setRadius(qreal newRadius) |
227 | { |
228 | if (newRadius == m_radius) { |
229 | return; |
230 | } |
231 | |
232 | m_radius = newRadius; |
233 | if (!isSoftwareRendering()) { |
234 | update(); |
235 | } |
236 | Q_EMIT radiusChanged(); |
237 | } |
238 | |
239 | QColor ShadowedRectangle::color() const |
240 | { |
241 | return m_color; |
242 | } |
243 | |
244 | void ShadowedRectangle::setColor(const QColor &newColor) |
245 | { |
246 | if (newColor == m_color) { |
247 | return; |
248 | } |
249 | |
250 | m_color = newColor; |
251 | if (!isSoftwareRendering()) { |
252 | update(); |
253 | } |
254 | Q_EMIT colorChanged(); |
255 | } |
256 | |
257 | ShadowedRectangle::RenderType ShadowedRectangle::renderType() const |
258 | { |
259 | return m_renderType; |
260 | } |
261 | |
262 | void ShadowedRectangle::setRenderType(RenderType renderType) |
263 | { |
264 | if (renderType == m_renderType) { |
265 | return; |
266 | } |
267 | m_renderType = renderType; |
268 | update(); |
269 | Q_EMIT renderTypeChanged(); |
270 | } |
271 | |
272 | void ShadowedRectangle::componentComplete() |
273 | { |
274 | QQuickItem::componentComplete(); |
275 | |
276 | checkSoftwareItem(); |
277 | } |
278 | |
279 | bool ShadowedRectangle::isSoftwareRendering() const |
280 | { |
281 | return (window() && window()->rendererInterface()->graphicsApi() == QSGRendererInterface::Software) || m_renderType == RenderType::Software; |
282 | } |
283 | |
284 | PaintedRectangleItem *ShadowedRectangle::softwareItem() const |
285 | { |
286 | return m_softwareItem; |
287 | } |
288 | |
289 | void ShadowedRectangle::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) |
290 | { |
291 | if (change == QQuickItem::ItemSceneChange && value.window) { |
292 | checkSoftwareItem(); |
293 | // TODO: only conditionally emit? |
294 | Q_EMIT softwareRenderingChanged(); |
295 | } |
296 | |
297 | QQuickItem::itemChange(change, value); |
298 | } |
299 | |
300 | QSGNode *ShadowedRectangle::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *data) |
301 | { |
302 | Q_UNUSED(data); |
303 | |
304 | if (boundingRect().isEmpty()) { |
305 | delete node; |
306 | return nullptr; |
307 | } |
308 | |
309 | auto shadowNode = static_cast<ShadowedRectangleNode *>(node); |
310 | |
311 | if (!shadowNode) { |
312 | shadowNode = new ShadowedRectangleNode{}; |
313 | |
314 | // Cache lowPower state so we only execute the full check once. |
315 | static bool lowPower = QByteArrayList{"1" , "true" }.contains(t: qgetenv(varName: "KIRIGAMI_LOWPOWER_HARDWARE" ).toLower()); |
316 | if (m_renderType == RenderType::LowQuality || (m_renderType == RenderType::Auto && lowPower)) { |
317 | shadowNode->setShaderType(ShadowedRectangleMaterial::ShaderType::LowPower); |
318 | } |
319 | } |
320 | |
321 | shadowNode->setBorderEnabled(m_border->isEnabled()); |
322 | shadowNode->setRect(boundingRect()); |
323 | shadowNode->setSize(m_shadow->size()); |
324 | shadowNode->setRadius(m_corners->toVector4D(all: m_radius)); |
325 | shadowNode->setOffset(QVector2D{float(m_shadow->xOffset()), float(m_shadow->yOffset())}); |
326 | shadowNode->setColor(m_color); |
327 | shadowNode->setShadowColor(m_shadow->color()); |
328 | shadowNode->setBorderWidth(m_border->width()); |
329 | shadowNode->setBorderColor(m_border->color()); |
330 | shadowNode->updateGeometry(); |
331 | return shadowNode; |
332 | } |
333 | |
334 | void ShadowedRectangle::checkSoftwareItem() |
335 | { |
336 | if (!m_softwareItem && isSoftwareRendering()) { |
337 | m_softwareItem = new PaintedRectangleItem{this}; |
338 | // The software item is added as a "normal" child item, this means it |
339 | // will be part of the normal item sort order. Since there is no way to |
340 | // control the ordering of children, just make sure to have a very low Z |
341 | // value for the child, to force it to be the lowest item. |
342 | m_softwareItem->setZ(-99.0); |
343 | |
344 | auto updateItem = [this]() { |
345 | auto borderWidth = m_border->width(); |
346 | auto rect = boundingRect(); |
347 | m_softwareItem->setSize(rect.size()); |
348 | m_softwareItem->setColor(m_color); |
349 | m_softwareItem->setRadius(m_radius); |
350 | m_softwareItem->setBorderWidth(borderWidth); |
351 | m_softwareItem->setBorderColor(m_border->color()); |
352 | }; |
353 | |
354 | updateItem(); |
355 | |
356 | connect(sender: this, signal: &ShadowedRectangle::widthChanged, context: m_softwareItem, slot&: updateItem); |
357 | connect(sender: this, signal: &ShadowedRectangle::heightChanged, context: m_softwareItem, slot&: updateItem); |
358 | connect(sender: this, signal: &ShadowedRectangle::colorChanged, context: m_softwareItem, slot&: updateItem); |
359 | connect(sender: this, signal: &ShadowedRectangle::radiusChanged, context: m_softwareItem, slot&: updateItem); |
360 | connect(sender: m_border.get(), signal: &BorderGroup::changed, context: m_softwareItem, slot&: updateItem); |
361 | setFlag(flag: QQuickItem::ItemHasContents, enabled: false); |
362 | } |
363 | } |
364 | |
365 | #include "moc_shadowedrectangle.cpp" |
366 | |