1 | /* |
2 | * SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl> |
3 | * |
4 | * SPDX-License-Identifier: LGPL-2.0-or-later |
5 | */ |
6 | |
7 | #include "shadowedrectanglenode.h" |
8 | #include "shadowedborderrectanglematerial.h" |
9 | |
10 | QColor premultiply(const QColor &color) |
11 | { |
12 | return QColor::fromRgbF(r: color.redF() * color.alphaF(), // |
13 | g: color.greenF() * color.alphaF(), |
14 | b: color.blueF() * color.alphaF(), |
15 | a: color.alphaF()); |
16 | } |
17 | |
18 | ShadowedRectangleNode::ShadowedRectangleNode() |
19 | { |
20 | m_geometry = new QSGGeometry{QSGGeometry::defaultAttributes_TexturedPoint2D(), 4}; |
21 | setGeometry(m_geometry); |
22 | |
23 | setFlags(QSGNode::OwnsGeometry | QSGNode::OwnsMaterial); |
24 | } |
25 | |
26 | void ShadowedRectangleNode::setBorderEnabled(bool enabled) |
27 | { |
28 | // We can achieve more performant shaders by splitting the two into separate |
29 | // shaders. This requires separating the materials as well. So when |
30 | // borderWidth is increased to something where the border should be visible, |
31 | // switch to the with-border material. Otherwise use the no-border version. |
32 | |
33 | if (enabled) { |
34 | if (!m_material || m_material->type() == borderlessMaterialType()) { |
35 | auto newMaterial = createBorderMaterial(); |
36 | newMaterial->shaderType = m_shaderType; |
37 | setMaterial(newMaterial); |
38 | m_material = newMaterial; |
39 | m_rect = QRectF{}; |
40 | markDirty(bits: QSGNode::DirtyMaterial); |
41 | } |
42 | } else { |
43 | if (!m_material || m_material->type() == borderMaterialType()) { |
44 | auto newMaterial = createBorderlessMaterial(); |
45 | newMaterial->shaderType = m_shaderType; |
46 | setMaterial(newMaterial); |
47 | m_material = newMaterial; |
48 | m_rect = QRectF{}; |
49 | markDirty(bits: QSGNode::DirtyMaterial); |
50 | } |
51 | } |
52 | } |
53 | |
54 | void ShadowedRectangleNode::setRect(const QRectF &rect) |
55 | { |
56 | if (rect == m_rect) { |
57 | return; |
58 | } |
59 | |
60 | m_rect = rect; |
61 | |
62 | QVector2D newAspect{1.0, 1.0}; |
63 | if (m_rect.width() >= m_rect.height()) { |
64 | newAspect.setX(m_rect.width() / m_rect.height()); |
65 | } else { |
66 | newAspect.setY(m_rect.height() / m_rect.width()); |
67 | } |
68 | |
69 | if (m_material->aspect != newAspect) { |
70 | m_material->aspect = newAspect; |
71 | markDirty(bits: QSGNode::DirtyMaterial); |
72 | m_aspect = newAspect; |
73 | } |
74 | } |
75 | |
76 | void ShadowedRectangleNode::setSize(qreal size) |
77 | { |
78 | auto minDimension = std::min(a: m_rect.width(), b: m_rect.height()); |
79 | float uniformSize = (size / minDimension) * 2.0; |
80 | |
81 | if (!qFuzzyCompare(p1: m_material->size, p2: uniformSize)) { |
82 | m_material->size = uniformSize; |
83 | markDirty(bits: QSGNode::DirtyMaterial); |
84 | m_size = size; |
85 | } |
86 | } |
87 | |
88 | void ShadowedRectangleNode::setRadius(const QVector4D &radius) |
89 | { |
90 | float minDimension = std::min(a: m_rect.width(), b: m_rect.height()); |
91 | auto uniformRadius = QVector4D{std::min(a: radius.x() * 2.0f / minDimension, b: 1.0f), |
92 | std::min(a: radius.y() * 2.0f / minDimension, b: 1.0f), |
93 | std::min(a: radius.z() * 2.0f / minDimension, b: 1.0f), |
94 | std::min(a: radius.w() * 2.0f / minDimension, b: 1.0f)}; |
95 | |
96 | if (m_material->radius != uniformRadius) { |
97 | m_material->radius = uniformRadius; |
98 | markDirty(bits: QSGNode::DirtyMaterial); |
99 | m_radius = radius; |
100 | } |
101 | } |
102 | |
103 | void ShadowedRectangleNode::setColor(const QColor &color) |
104 | { |
105 | auto premultiplied = premultiply(color); |
106 | if (m_material->color != premultiplied) { |
107 | m_material->color = premultiplied; |
108 | markDirty(bits: QSGNode::DirtyMaterial); |
109 | } |
110 | } |
111 | |
112 | void ShadowedRectangleNode::setShadowColor(const QColor &color) |
113 | { |
114 | auto premultiplied = premultiply(color); |
115 | if (m_material->shadowColor != premultiplied) { |
116 | m_material->shadowColor = premultiplied; |
117 | markDirty(bits: QSGNode::DirtyMaterial); |
118 | } |
119 | } |
120 | |
121 | void ShadowedRectangleNode::setOffset(const QVector2D &offset) |
122 | { |
123 | auto minDimension = std::min(a: m_rect.width(), b: m_rect.height()); |
124 | auto uniformOffset = offset / minDimension; |
125 | |
126 | if (m_material->offset != uniformOffset) { |
127 | m_material->offset = uniformOffset; |
128 | markDirty(bits: QSGNode::DirtyMaterial); |
129 | m_offset = offset; |
130 | } |
131 | } |
132 | |
133 | void ShadowedRectangleNode::setBorderWidth(qreal width) |
134 | { |
135 | if (m_material->type() != borderMaterialType()) { |
136 | return; |
137 | } |
138 | |
139 | auto minDimension = std::min(a: m_rect.width(), b: m_rect.height()); |
140 | float uniformBorderWidth = width / minDimension; |
141 | |
142 | auto borderMaterial = static_cast<ShadowedBorderRectangleMaterial *>(m_material); |
143 | if (!qFuzzyCompare(p1: borderMaterial->borderWidth, p2: uniformBorderWidth)) { |
144 | borderMaterial->borderWidth = uniformBorderWidth; |
145 | markDirty(bits: QSGNode::DirtyMaterial); |
146 | m_borderWidth = width; |
147 | } |
148 | } |
149 | |
150 | void ShadowedRectangleNode::setBorderColor(const QColor &color) |
151 | { |
152 | if (m_material->type() != borderMaterialType()) { |
153 | return; |
154 | } |
155 | |
156 | auto borderMaterial = static_cast<ShadowedBorderRectangleMaterial *>(m_material); |
157 | auto premultiplied = premultiply(color); |
158 | if (borderMaterial->borderColor != premultiplied) { |
159 | borderMaterial->borderColor = premultiplied; |
160 | markDirty(bits: QSGNode::DirtyMaterial); |
161 | } |
162 | } |
163 | |
164 | void ShadowedRectangleNode::setShaderType(ShadowedRectangleMaterial::ShaderType type) |
165 | { |
166 | m_shaderType = type; |
167 | } |
168 | |
169 | void ShadowedRectangleNode::updateGeometry() |
170 | { |
171 | auto rect = m_rect; |
172 | if (m_shaderType == ShadowedRectangleMaterial::ShaderType::Standard) { |
173 | rect = rect.adjusted(xp1: -m_size * m_aspect.x(), // |
174 | yp1: -m_size * m_aspect.y(), |
175 | xp2: m_size * m_aspect.x(), |
176 | yp2: m_size * m_aspect.y()); |
177 | |
178 | auto offsetLength = m_offset.length(); |
179 | rect = rect.adjusted(xp1: -offsetLength * m_aspect.x(), // |
180 | yp1: -offsetLength * m_aspect.y(), |
181 | xp2: offsetLength * m_aspect.x(), |
182 | yp2: offsetLength * m_aspect.y()); |
183 | } |
184 | |
185 | QSGGeometry::updateTexturedRectGeometry(g: m_geometry, rect, sourceRect: QRectF{0.0, 0.0, 1.0, 1.0}); |
186 | markDirty(bits: QSGNode::DirtyGeometry); |
187 | } |
188 | |
189 | ShadowedRectangleMaterial *ShadowedRectangleNode::createBorderlessMaterial() |
190 | { |
191 | return new ShadowedRectangleMaterial{}; |
192 | } |
193 | |
194 | ShadowedBorderRectangleMaterial *ShadowedRectangleNode::createBorderMaterial() |
195 | { |
196 | return new ShadowedBorderRectangleMaterial{}; |
197 | } |
198 | |
199 | QSGMaterialType *ShadowedRectangleNode::borderlessMaterialType() |
200 | { |
201 | return &ShadowedRectangleMaterial::staticType; |
202 | } |
203 | |
204 | QSGMaterialType *ShadowedRectangleNode::borderMaterialType() |
205 | { |
206 | return &ShadowedBorderRectangleMaterial::staticType; |
207 | } |
208 | |