/*****************************************************************************
* Copyright (C) 2022 VLC authors and VideoLAN
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* ( at your option ) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#include "viewblockingrectangle.hpp"
#include <QQuickWindow>
#include <QPainter>
#include <QPointer>
#include <QSGRectangleNode>
#include <QSGMaterial>
#include <QSGRenderNode>
class SoftwareRenderNode : public QSGRenderNode
{
public:
void render(const RenderState *renderState) override
{
assert(m_window);
const auto painter = static_cast<QPainter *>(m_window->rendererInterface()->getResource(m_window, QSGRendererInterface::PainterResource));
assert(painter);
painter->setCompositionMode(QPainter::CompositionMode_Source);
const auto clipRegion = renderState->clipRegion();
if (clipRegion && !clipRegion->isEmpty())
painter->setClipRegion(*clipRegion, Qt::ReplaceClip);
painter->setTransform(matrix()->toTransform());
painter->setOpacity(inheritedOpacity());
painter->fillRect(rect().toRect(), m_color);
}
RenderingFlags flags() const override
{
return BoundedRectRendering | DepthAwareRendering | OpaqueRendering;
}
QRectF rect() const override
{
return m_rect;
}
void setRect(const QRectF& rect)
{
if (m_rect != rect)
{
m_rect = rect;
markDirty(DirtyGeometry);
}
}
void setWindow(QQuickWindow* const window)
{
if (Q_LIKELY(m_window != window))
{
m_window = window;
markDirty(DirtyForceUpdate);
}
}
void setColor(const QColor& color)
{
if (m_color != color)
{
m_color = color;
markDirty(DirtyMaterial);
}
}
private:
QPointer<QQuickWindow> m_window = nullptr;
QRectF m_rect;
QColor m_color {Qt::transparent};
};
ViewBlockingRectangle::ViewBlockingRectangle(QQuickItem *parent)
: QQuickItem(parent)
, m_color(Qt::transparent)
{
if (m_renderingEnabled || m_updateRenderPosition)
setFlag(QQuickItem::ItemHasContents);
connect(this, &ViewBlockingRectangle::colorChanged, this, &QQuickItem::update);
connect(this, &ViewBlockingRectangle::windowChanged, this, [this] {
if (window())
{
// The new window might use an appropriate graphics API:
setFlag(QQuickItem::ItemHasContents);
m_windowChanged = true;
}
});
}
QSGNode *ViewBlockingRectangle::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
{
QSGRectangleNode *rectangleNode = nullptr;
SoftwareRenderNode *softwareRenderNode = nullptr;
if (oldNode)
{
switch (oldNode->type())
{
case QSGNode::GeometryNodeType:
assert(dynamic_cast<QSGRectangleNode*>(oldNode));
rectangleNode = static_cast<QSGRectangleNode*>(oldNode);
break;
case QSGNode::RenderNodeType:
// Can be `SoftwareRenderNode` or `MatrixChangeObserverNode`:
softwareRenderNode = dynamic_cast<SoftwareRenderNode*>(oldNode);
break;
default:
break;
}
}
assert(window());
const bool softwareMode = (window()->rendererInterface()->graphicsApi() == QSGRendererInterface::GraphicsApi::Software);
if (m_renderingEnabled)
{
if (Q_UNLIKELY(oldNode && ((softwareMode && !softwareRenderNode)
|| (!softwareMode && !rectangleNode))))
{
delete oldNode;
oldNode = nullptr;
}
}
else
{
if (rectangleNode || softwareRenderNode || (!m_updateRenderPosition && oldNode))
{
// If `m_updateRenderPosition` is true, new node will be the observer node,
// otherwise, we early return as a scene graph node is not necessary.
// Currently we are not reparenting the observer node that could be reused
// otherwise.
delete oldNode;
oldNode = nullptr;
}
if (!m_updateRenderPosition)
return nullptr;
}
const auto createObserverNode = [this]() {
const auto node = new MatrixChangeObserverNode([p = QPointer(this)](const QMatrix4x4& matrix) {
if (Q_LIKELY(p))
{
p->m_renderPosition = {matrix.row(0)[3], // Viewport/scene X
matrix.row(1)[3]}; // Viewport/scene y
emit p->scenePositionHasChanged();
}
});
node->setFlag(QSGNode::OwnedByParent);
return node;
};
if (!oldNode)
{
MatrixChangeObserverNode *observerNode;
if (m_updateRenderPosition)
{
observerNode = createObserverNode();
// Initial position:
m_renderPosition = mapToScene(QPointF(0,0));
}
else
{
observerNode = nullptr;
}
if (m_renderingEnabled)
{
if (softwareMode)
{
softwareRenderNode = new SoftwareRenderNode;
softwareRenderNode->setWindow(window());
if (observerNode)
softwareRenderNode->appendChildNode(observerNode);
}
else
{
rectangleNode = window()->createRectangleNode();
assert(rectangleNode);
if (observerNode)
rectangleNode->appendChildNode(observerNode);
const auto material = rectangleNode->material();
if (!material ||
material == reinterpret_cast<QSGMaterial*>(1) /* Qt may explicitly set the material pointer to 1 in OpenVG */)
{
// Scene graph adaptation does not support shading
qmlDebug(this) << "ViewBlockingRectangle is being used under an incompatible scene graph adaptation.";
delete rectangleNode;
setFlag(QQuickItem::ItemHasContents, false);
return nullptr;
}
rectangleNode->material()->setFlag(QSGMaterial::Blending, false);
}
}
else if (observerNode)
{
oldNode = observerNode;
}
}
else if (m_renderingEnabled)
{
const auto observerNode = oldNode->childAtIndex(0);
if (m_updateRenderPosition)
{
if (!observerNode)
{
oldNode->appendChildNode(createObserverNode());
}
}
else
{
if (observerNode)
{
assert(dynamic_cast<MatrixChangeObserverNode*>(observerNode));
observerNode->setFlag(QSGNode::OwnedByParent, false); // this may not be necessary
delete observerNode;
}
}
}
const auto rect = boundingRect();
m_renderSize = rect.size();
if (m_renderingEnabled)
{
if (softwareMode)
{
softwareRenderNode->setRect(rect);
softwareRenderNode->setColor(m_color);
if (Q_UNLIKELY(m_windowChanged))
{
softwareRenderNode->setWindow(window());
m_windowChanged = false;
}
return softwareRenderNode;
}
else
{
if (rectangleNode->rect() != rect)
rectangleNode->setRect(rect);
if (rectangleNode->color() != m_color)
{
rectangleNode->setColor(m_color);
assert(rectangleNode->material());
rectangleNode->material()->setFlag(QSGMaterial::Blending, false);
}
return rectangleNode;
}
}
else
{
if (m_updateRenderPosition)
{
assert(oldNode);
return oldNode; // observer node
}
else
{
setFlag(ItemHasContents, false);
return nullptr;
}
}
}
QSizeF ViewBlockingRectangle::renderSize() const
{
return m_renderSize;
}
QPointF ViewBlockingRectangle::renderPosition() const
{
return m_renderPosition;
}
void ViewBlockingRectangle::setRenderingEnabled(bool enabled)
{
if (m_renderingEnabled == enabled)
return;
m_renderingEnabled = enabled;
if (enabled)
{
setFlag(ItemHasContents, true);
if (isVisible())
update();
}
else if (!m_updateRenderPosition)
{
setFlag(ItemHasContents, false);
}
emit renderingEnabledChanged();
}
void ViewBlockingRectangle::setUpdateRenderPosition(bool _update)
{
if (m_updateRenderPosition == _update)
return;
m_updateRenderPosition = _update;
if (_update)
{
setFlag(ItemHasContents, true);
if (isVisible())
update();
}
else if (!m_renderingEnabled)
{
setFlag(ItemHasContents, false);
}
}
bool ViewBlockingRectangle::updateRenderPosition() const
{
return m_updateRenderPosition;
}
void ViewBlockingRectangle::componentComplete()
{
QQuickItem::componentComplete();
if (isVisible())
{
m_renderSize = QSizeF(width(), height());
if (m_updateRenderPosition)
{
m_renderPosition = mapToScene(QPointF(0.0, 0.0));
}
}
}
¤ Dauer der Verarbeitung: 0.6 Sekunden
¤
*© Formatika GbR, Deutschland