129 lines
3.5 KiB
C++
129 lines
3.5 KiB
C++
#pragma once
|
|
|
|
#include "blobmaterial.hpp"
|
|
|
|
#include <qcolor.h>
|
|
#include <qlist.h>
|
|
#include <qobject.h>
|
|
#include <qqmlengine.h>
|
|
#include <qquickitem.h>
|
|
#include <qrect.h>
|
|
#include <qsgnode.h>
|
|
#include <qvector.h>
|
|
|
|
class BlobShape2;
|
|
class BlobInvertedRect2;
|
|
|
|
// One shared valuation for a whole group, drawn by one node.
|
|
//
|
|
// The shapes here are data only: they say where the material should be. The group
|
|
// draws a single quad over everything the shader can evaluate, so a pixel cannot
|
|
// be evaluated by one shape and missed by another, and no second draw can
|
|
// disagree with the first. That is what makes overlapping shapes read as one
|
|
// surface - one value, one writer, one alpha per pixel.
|
|
struct BlobGroupData {
|
|
// The shader reads rectData[80] = 16 entries, and excludeMask is a 32-bit
|
|
// mask. The shared list is capped at 16, in group order, so the cap is the
|
|
// same list for every pixel.
|
|
static constexpr int kMaxRects = 16;
|
|
|
|
QVector<BlobRectData> rects;
|
|
bool hasInverted = false;
|
|
float invertedRadius = 0;
|
|
float invertedOuter[4] = {};
|
|
float invertedInner[4] = {};
|
|
};
|
|
|
|
class BlobGroup2 : public QQuickItem {
|
|
Q_OBJECT
|
|
QML_ELEMENT
|
|
Q_PROPERTY(
|
|
qreal smoothing READ smoothing WRITE setSmoothing NOTIFY
|
|
smoothingChanged)
|
|
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
|
|
Q_PROPERTY(
|
|
bool cornerFill READ cornerFill WRITE setCornerFill NOTIFY
|
|
cornerFillChanged)
|
|
Q_PROPERTY(
|
|
QColor shadowColor READ shadowColor WRITE setShadowColor NOTIFY
|
|
shadowColorChanged)
|
|
Q_PROPERTY(
|
|
qreal shadowRadius READ shadowRadius WRITE setShadowRadius NOTIFY
|
|
shadowRadiusChanged)
|
|
|
|
friend class BlobShape2;
|
|
|
|
public:
|
|
explicit BlobGroup2(QQuickItem* parent = nullptr);
|
|
~BlobGroup2() override;
|
|
|
|
[[nodiscard]] qreal smoothing() const { return m_smoothing; }
|
|
|
|
void setSmoothing(qreal s);
|
|
|
|
[[nodiscard]] QColor color() const { return m_color; }
|
|
|
|
void setColor(const QColor& c);
|
|
|
|
[[nodiscard]] bool cornerFill() const { return m_cornerFill; }
|
|
|
|
void setCornerFill(bool e);
|
|
|
|
[[nodiscard]] QColor shadowColor() const { return m_shadowColor; }
|
|
|
|
void setShadowColor(const QColor& c);
|
|
|
|
[[nodiscard]] qreal shadowRadius() const { return m_shadowRadius; }
|
|
|
|
void setShadowRadius(qreal r);
|
|
|
|
void addShape(BlobShape2* shape);
|
|
void removeShape(BlobShape2* shape);
|
|
|
|
void setInvertedRect(BlobInvertedRect2* rect);
|
|
void clearInvertedRect(BlobInvertedRect2* rect);
|
|
|
|
[[nodiscard]] const QList<BlobShape2*>& shapes() const { return m_shapes; }
|
|
|
|
[[nodiscard]] BlobInvertedRect2* invertedRect() const {
|
|
return m_invertedRect;
|
|
}
|
|
|
|
[[nodiscard]] const BlobGroupData& data() const { return m_data; }
|
|
|
|
// One draw per group, so a change to any shape changes the value for every
|
|
// pixel. Refresh the whole group.
|
|
void markDirty();
|
|
|
|
signals:
|
|
void smoothingChanged();
|
|
void colorChanged();
|
|
void cornerFillChanged();
|
|
void shadowColorChanged();
|
|
void shadowRadiusChanged();
|
|
|
|
protected:
|
|
void updatePolish() override;
|
|
QSGNode* updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) override;
|
|
|
|
private:
|
|
void ensurePhysicsUpdated();
|
|
void ensureDataBuilt();
|
|
void fillMaterial(BlobMaterial* material);
|
|
|
|
qreal m_smoothing = 32.0;
|
|
QColor m_color{0x44, 0x88, 0xff};
|
|
bool m_cornerFill = true;
|
|
QColor m_shadowColor{0, 0, 0, 128};
|
|
qreal m_shadowRadius = 0.0;
|
|
QList<BlobShape2*> m_shapes;
|
|
BlobInvertedRect2* m_invertedRect = nullptr;
|
|
bool m_physicsUpdated = false;
|
|
bool m_dataBuilt = false;
|
|
BlobGroupData m_data;
|
|
|
|
// Scene-space union of what the shader can evaluate for any shape. The node
|
|
// covers exactly this, so gaps are not possible.
|
|
QRectF m_bounds;
|
|
};
|