1/*
2 * SPDX-FileCopyrightText: 2025 Arjen Hiemstra <ahiemstra@heimr.nl>
3 *
4 * SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6
7#include "softwarerectanglenode.h"
8
9#include <QPainter>
10#include <QSGImageNode>
11#include <QSGRendererInterface>
12
13#include "texturecache.h"
14
15SoftwareRectangleNode::SoftwareRectangleNode()
16{
17 setFlag(QSGNode::UsePreprocess);
18}
19
20void SoftwareRectangleNode::setWindow(QQuickWindow *window)
21{
22 m_window = window;
23}
24
25QRectF SoftwareRectangleNode::rect() const
26{
27 return m_rect;
28}
29
30void SoftwareRectangleNode::setRect(const QRectF &rect)
31{
32 if (rect == m_rect) {
33 return;
34 }
35
36 m_rect = rect;
37 markDirty(bits: QSGNode::DirtyGeometry);
38}
39
40void SoftwareRectangleNode::setColor(const QColor &color)
41{
42 if (color == m_color) {
43 return;
44 }
45
46 m_color = color;
47 markDirty(bits: QSGNode::DirtyMaterial);
48}
49
50void SoftwareRectangleNode::setImage(const QImage &image)
51{
52 if (!m_window) {
53 return;
54 }
55
56 if (m_imageNode) {
57 cleanupImageNode();
58 }
59
60 m_textureInfo = ShaderNode::TextureInfo{
61 .channel = 0,
62 .options = {},
63 .texture = TextureCache::loadTexture(window: m_window, image),
64 .provider = nullptr,
65 .providerConnection = {},
66 };
67
68 if (!m_textureInfo.texture) {
69 return;
70 }
71
72 m_imageNode = m_window->createImageNode();
73 if (m_imageNode) {
74 m_imageNode->setTexture(m_textureInfo.texture.get());
75 m_imageNode->setFiltering(QSGTexture::Filtering::Linear);
76 appendChildNode(node: m_imageNode);
77 }
78}
79
80void SoftwareRectangleNode::setTextureProvider(QSGTextureProvider *provider)
81{
82 if (!m_window) {
83 return;
84 }
85
86 if (m_imageNode) {
87 cleanupImageNode();
88 }
89
90 m_textureInfo = ShaderNode::TextureInfo{
91 .channel = 0,
92 .options = {},
93 .texture = nullptr,
94 .provider = provider,
95 .providerConnection = {},
96 };
97
98 // The render node will be created in preprocess().
99}
100
101void SoftwareRectangleNode::setRadius(qreal radius)
102{
103 if (qFuzzyCompare(p1: radius, p2: m_radius)) {
104 return;
105 }
106
107 m_radius = radius;
108 markDirty(bits: QSGNode::DirtyMaterial);
109}
110
111void SoftwareRectangleNode::setBorderWidth(qreal width)
112{
113 if (qFuzzyCompare(p1: width, p2: m_borderWidth)) {
114 return;
115 }
116
117 m_borderWidth = width;
118 markDirty(bits: QSGNode::DirtyMaterial);
119}
120
121void SoftwareRectangleNode::setBorderColor(const QColor &color)
122{
123 if (color == m_borderColor) {
124 return;
125 }
126
127 m_borderColor = color;
128 markDirty(bits: QSGNode::DirtyMaterial);
129}
130
131QSGRenderNode::RenderingFlags SoftwareRectangleNode::flags() const
132{
133 return BoundedRectRendering;
134}
135
136void SoftwareRectangleNode::preprocess()
137{
138 auto provider = m_textureInfo.provider;
139 if (provider) {
140 QSGTexture *texture = provider->texture();
141 if (QSGDynamicTexture *dynamic_texture = qobject_cast<QSGDynamicTexture *>(object: texture)) {
142 dynamic_texture->updateTexture();
143 }
144
145 if (texture) {
146 if (!m_imageNode) {
147 m_imageNode = m_window->createImageNode();
148 m_imageNode->setTexture(texture);
149 m_imageNode->setFiltering(QSGTexture::Filtering::Linear);
150 appendChildNode(node: m_imageNode);
151 } else {
152 m_imageNode->setTexture(texture);
153 }
154 } else if (m_imageNode) {
155 cleanupImageNode();
156 }
157 }
158}
159
160void SoftwareRectangleNode::render(const RenderState *state)
161{
162 auto painter = static_cast<QPainter *>(m_window->rendererInterface()->getResource(window: m_window, resource: QSGRendererInterface::PainterResource));
163 Q_ASSERT(painter);
164
165 const QRegion *clipRegion = state->clipRegion();
166 if (clipRegion && !clipRegion->isEmpty()) {
167 painter->setClipRegion(*clipRegion, op: Qt::ReplaceClip);
168 }
169
170 painter->setTransform(transform: matrix()->toTransform());
171 painter->setOpacity(inheritedOpacity());
172 painter->setRenderHint(hint: QPainter::Antialiasing, on: true);
173 painter->setPen(Qt::transparent);
174
175 auto radius = std::min(a: m_radius, b: std::min(a: m_rect.width(), b: m_rect.height()) / 2);
176 auto borderWidth = std::floor(x: m_borderWidth);
177
178 if (borderWidth > 0.0) {
179 painter->setBrush(m_borderColor);
180 painter->drawRoundedRect(rect: m_rect, xRadius: radius, yRadius: radius);
181 }
182
183 painter->setBrush(m_color);
184 auto adjustedRect = m_rect.adjusted(xp1: borderWidth, yp1: borderWidth, xp2: -borderWidth, yp2: -borderWidth);
185 painter->drawRoundedRect(rect: adjustedRect, xRadius: radius - borderWidth, yRadius: radius - borderWidth);
186
187 if (m_imageNode) {
188 static constexpr auto cornerAngle = 0.70710678; // sin(0.25pi)
189 auto cornerAdjustment = cornerAngle * (std::sqrt(x: std::pow(x: radius, y: 2.0) * 2.0) - radius + borderWidth);
190 auto withoutCorners = m_rect.adjusted(xp1: cornerAdjustment, yp1: cornerAdjustment, xp2: -cornerAdjustment, yp2: -cornerAdjustment);
191 m_imageNode->setRect(withoutCorners);
192 }
193}
194
195void SoftwareRectangleNode::cleanupImageNode()
196{
197 removeChildNode(node: m_imageNode);
198 delete m_imageNode;
199 m_imageNode = nullptr;
200}
201

source code of kirigami/src/primitives/scenegraph/softwarerectanglenode.cpp