File indexing completed on 2024-04-21 03:56:01

0001 /*
0002  *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
0003  *
0004  *  SPDX-License-Identifier: LGPL-2.0-or-later
0005  */
0006 
0007 #include "shadowedrectangle.h"
0008 
0009 #include <QQuickWindow>
0010 #include <QSGRectangleNode>
0011 #include <QSGRendererInterface>
0012 
0013 #include "scenegraph/paintedrectangleitem.h"
0014 #include "scenegraph/shadowedrectanglenode.h"
0015 
0016 BorderGroup::BorderGroup(QObject *parent)
0017     : QObject(parent)
0018 {
0019 }
0020 
0021 qreal BorderGroup::width() const
0022 {
0023     return m_width;
0024 }
0025 
0026 void BorderGroup::setWidth(qreal newWidth)
0027 {
0028     if (newWidth == m_width) {
0029         return;
0030     }
0031 
0032     m_width = newWidth;
0033     Q_EMIT changed();
0034 }
0035 
0036 QColor BorderGroup::color() const
0037 {
0038     return m_color;
0039 }
0040 
0041 void BorderGroup::setColor(const QColor &newColor)
0042 {
0043     if (newColor == m_color) {
0044         return;
0045     }
0046 
0047     m_color = newColor;
0048     Q_EMIT changed();
0049 }
0050 
0051 ShadowGroup::ShadowGroup(QObject *parent)
0052     : QObject(parent)
0053 {
0054 }
0055 
0056 qreal ShadowGroup::size() const
0057 {
0058     return m_size;
0059 }
0060 
0061 void ShadowGroup::setSize(qreal newSize)
0062 {
0063     if (newSize == m_size) {
0064         return;
0065     }
0066 
0067     m_size = newSize;
0068     Q_EMIT changed();
0069 }
0070 
0071 qreal ShadowGroup::xOffset() const
0072 {
0073     return m_xOffset;
0074 }
0075 
0076 void ShadowGroup::setXOffset(qreal newXOffset)
0077 {
0078     if (newXOffset == m_xOffset) {
0079         return;
0080     }
0081 
0082     m_xOffset = newXOffset;
0083     Q_EMIT changed();
0084 }
0085 
0086 qreal ShadowGroup::yOffset() const
0087 {
0088     return m_yOffset;
0089 }
0090 
0091 void ShadowGroup::setYOffset(qreal newYOffset)
0092 {
0093     if (newYOffset == m_yOffset) {
0094         return;
0095     }
0096 
0097     m_yOffset = newYOffset;
0098     Q_EMIT changed();
0099 }
0100 
0101 QColor ShadowGroup::color() const
0102 {
0103     return m_color;
0104 }
0105 
0106 void ShadowGroup::setColor(const QColor &newColor)
0107 {
0108     if (newColor == m_color) {
0109         return;
0110     }
0111 
0112     m_color = newColor;
0113     Q_EMIT changed();
0114 }
0115 
0116 CornersGroup::CornersGroup(QObject *parent)
0117     : QObject(parent)
0118 {
0119 }
0120 
0121 qreal CornersGroup::topLeft() const
0122 {
0123     return m_topLeft;
0124 }
0125 
0126 void CornersGroup::setTopLeft(qreal newTopLeft)
0127 {
0128     if (newTopLeft == m_topLeft) {
0129         return;
0130     }
0131 
0132     m_topLeft = newTopLeft;
0133     Q_EMIT changed();
0134 }
0135 
0136 qreal CornersGroup::topRight() const
0137 {
0138     return m_topRight;
0139 }
0140 
0141 void CornersGroup::setTopRight(qreal newTopRight)
0142 {
0143     if (newTopRight == m_topRight) {
0144         return;
0145     }
0146 
0147     m_topRight = newTopRight;
0148     Q_EMIT changed();
0149 }
0150 
0151 qreal CornersGroup::bottomLeft() const
0152 {
0153     return m_bottomLeft;
0154 }
0155 
0156 void CornersGroup::setBottomLeft(qreal newBottomLeft)
0157 {
0158     if (newBottomLeft == m_bottomLeft) {
0159         return;
0160     }
0161 
0162     m_bottomLeft = newBottomLeft;
0163     Q_EMIT changed();
0164 }
0165 
0166 qreal CornersGroup::bottomRight() const
0167 {
0168     return m_bottomRight;
0169 }
0170 
0171 void CornersGroup::setBottomRight(qreal newBottomRight)
0172 {
0173     if (newBottomRight == m_bottomRight) {
0174         return;
0175     }
0176 
0177     m_bottomRight = newBottomRight;
0178     Q_EMIT changed();
0179 }
0180 
0181 QVector4D CornersGroup::toVector4D(float all) const
0182 {
0183     return QVector4D{m_bottomRight < 0.0 ? all : m_bottomRight,
0184                      m_topRight < 0.0 ? all : m_topRight,
0185                      m_bottomLeft < 0.0 ? all : m_bottomLeft,
0186                      m_topLeft < 0.0 ? all : m_topLeft};
0187 }
0188 
0189 ShadowedRectangle::ShadowedRectangle(QQuickItem *parentItem)
0190     : QQuickItem(parentItem)
0191     , m_border(std::make_unique<BorderGroup>())
0192     , m_shadow(std::make_unique<ShadowGroup>())
0193     , m_corners(std::make_unique<CornersGroup>())
0194 {
0195     setFlag(QQuickItem::ItemHasContents, true);
0196 
0197     connect(m_border.get(), &BorderGroup::changed, this, &ShadowedRectangle::update);
0198     connect(m_shadow.get(), &ShadowGroup::changed, this, &ShadowedRectangle::update);
0199     connect(m_corners.get(), &CornersGroup::changed, this, &ShadowedRectangle::update);
0200 }
0201 
0202 ShadowedRectangle::~ShadowedRectangle()
0203 {
0204 }
0205 
0206 BorderGroup *ShadowedRectangle::border() const
0207 {
0208     return m_border.get();
0209 }
0210 
0211 ShadowGroup *ShadowedRectangle::shadow() const
0212 {
0213     return m_shadow.get();
0214 }
0215 
0216 CornersGroup *ShadowedRectangle::corners() const
0217 {
0218     return m_corners.get();
0219 }
0220 
0221 qreal ShadowedRectangle::radius() const
0222 {
0223     return m_radius;
0224 }
0225 
0226 void ShadowedRectangle::setRadius(qreal newRadius)
0227 {
0228     if (newRadius == m_radius) {
0229         return;
0230     }
0231 
0232     m_radius = newRadius;
0233     if (!isSoftwareRendering()) {
0234         update();
0235     }
0236     Q_EMIT radiusChanged();
0237 }
0238 
0239 QColor ShadowedRectangle::color() const
0240 {
0241     return m_color;
0242 }
0243 
0244 void ShadowedRectangle::setColor(const QColor &newColor)
0245 {
0246     if (newColor == m_color) {
0247         return;
0248     }
0249 
0250     m_color = newColor;
0251     if (!isSoftwareRendering()) {
0252         update();
0253     }
0254     Q_EMIT colorChanged();
0255 }
0256 
0257 ShadowedRectangle::RenderType ShadowedRectangle::renderType() const
0258 {
0259     return m_renderType;
0260 }
0261 
0262 void ShadowedRectangle::setRenderType(RenderType renderType)
0263 {
0264     if (renderType == m_renderType) {
0265         return;
0266     }
0267     m_renderType = renderType;
0268     update();
0269     Q_EMIT renderTypeChanged();
0270 }
0271 
0272 void ShadowedRectangle::componentComplete()
0273 {
0274     QQuickItem::componentComplete();
0275 
0276     checkSoftwareItem();
0277 }
0278 
0279 bool ShadowedRectangle::isSoftwareRendering() const
0280 {
0281     return (window() && window()->rendererInterface()->graphicsApi() == QSGRendererInterface::Software) || m_renderType == RenderType::Software;
0282 }
0283 
0284 PaintedRectangleItem *ShadowedRectangle::softwareItem() const
0285 {
0286     return m_softwareItem;
0287 }
0288 
0289 void ShadowedRectangle::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
0290 {
0291     if (change == QQuickItem::ItemSceneChange && value.window) {
0292         checkSoftwareItem();
0293         // TODO: only conditionally emit?
0294         Q_EMIT softwareRenderingChanged();
0295     }
0296 
0297     QQuickItem::itemChange(change, value);
0298 }
0299 
0300 QSGNode *ShadowedRectangle::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *data)
0301 {
0302     Q_UNUSED(data);
0303 
0304     if (boundingRect().isEmpty()) {
0305         delete node;
0306         return nullptr;
0307     }
0308 
0309     auto shadowNode = static_cast<ShadowedRectangleNode *>(node);
0310 
0311     if (!shadowNode) {
0312         shadowNode = new ShadowedRectangleNode{};
0313 
0314         // Cache lowPower state so we only execute the full check once.
0315         static bool lowPower = QByteArrayList{"1", "true"}.contains(qgetenv("KIRIGAMI_LOWPOWER_HARDWARE").toLower());
0316         if (m_renderType == RenderType::LowQuality || (m_renderType == RenderType::Auto && lowPower)) {
0317             shadowNode->setShaderType(ShadowedRectangleMaterial::ShaderType::LowPower);
0318         }
0319     }
0320 
0321     shadowNode->setBorderEnabled(m_border->isEnabled());
0322     shadowNode->setRect(boundingRect());
0323     shadowNode->setSize(m_shadow->size());
0324     shadowNode->setRadius(m_corners->toVector4D(m_radius));
0325     shadowNode->setOffset(QVector2D{float(m_shadow->xOffset()), float(m_shadow->yOffset())});
0326     shadowNode->setColor(m_color);
0327     shadowNode->setShadowColor(m_shadow->color());
0328     shadowNode->setBorderWidth(m_border->width());
0329     shadowNode->setBorderColor(m_border->color());
0330     shadowNode->updateGeometry();
0331     return shadowNode;
0332 }
0333 
0334 void ShadowedRectangle::checkSoftwareItem()
0335 {
0336     if (!m_softwareItem && isSoftwareRendering()) {
0337         m_softwareItem = new PaintedRectangleItem{this};
0338         // The software item is added as a "normal" child item, this means it
0339         // will be part of the normal item sort order. Since there is no way to
0340         // control the ordering of children, just make sure to have a very low Z
0341         // value for the child, to force it to be the lowest item.
0342         m_softwareItem->setZ(-99.0);
0343 
0344         auto updateItem = [this]() {
0345             auto borderWidth = m_border->width();
0346             auto rect = boundingRect();
0347             m_softwareItem->setSize(rect.size());
0348             m_softwareItem->setColor(m_color);
0349             m_softwareItem->setRadius(m_radius);
0350             m_softwareItem->setBorderWidth(borderWidth);
0351             m_softwareItem->setBorderColor(m_border->color());
0352         };
0353 
0354         updateItem();
0355 
0356         connect(this, &ShadowedRectangle::widthChanged, m_softwareItem, updateItem);
0357         connect(this, &ShadowedRectangle::heightChanged, m_softwareItem, updateItem);
0358         connect(this, &ShadowedRectangle::colorChanged, m_softwareItem, updateItem);
0359         connect(this, &ShadowedRectangle::radiusChanged, m_softwareItem, updateItem);
0360         connect(m_border.get(), &BorderGroup::changed, m_softwareItem, updateItem);
0361         setFlag(QQuickItem::ItemHasContents, false);
0362     }
0363 }
0364 
0365 #include "moc_shadowedrectangle.cpp"