Merge remote-tracking branch 'origin/feat/settings-revamp'
This commit was merged in pull request #128.
This commit is contained in:
@@ -25,6 +25,14 @@ void BlobGroup::setColor(const QColor& c) {
|
||||
markDirty();
|
||||
}
|
||||
|
||||
void BlobGroup::setCornerFill(bool e) {
|
||||
if (m_cornerFill == e)
|
||||
return;
|
||||
m_cornerFill = e;
|
||||
emit cornerFillChanged();
|
||||
markDirty();
|
||||
}
|
||||
|
||||
void BlobGroup::addShape(BlobShape* shape) {
|
||||
if (!shape || m_shapes.contains(shape)) return;
|
||||
m_shapes.append(shape);
|
||||
|
||||
@@ -9,12 +9,11 @@ class BlobShape;
|
||||
class BlobInvertedRect;
|
||||
|
||||
class BlobGroup : public QObject {
|
||||
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_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)
|
||||
|
||||
public:
|
||||
explicit BlobGroup(QObject* parent = nullptr);
|
||||
@@ -28,8 +27,14 @@ class BlobGroup : public QObject {
|
||||
|
||||
void setColor(const QColor& c);
|
||||
|
||||
void addShape(BlobShape* shape);
|
||||
void removeShape(BlobShape* shape);
|
||||
[[nodiscard]] bool cornerFill() const {
|
||||
return m_cornerFill;
|
||||
}
|
||||
|
||||
void setCornerFill(bool e);
|
||||
|
||||
void addShape(BlobShape* shape);
|
||||
void removeShape(BlobShape* shape);
|
||||
|
||||
void setInvertedRect(BlobInvertedRect* rect);
|
||||
void clearInvertedRect(BlobInvertedRect* rect);
|
||||
@@ -44,14 +49,16 @@ class BlobGroup : public QObject {
|
||||
void markShapeDirty(BlobShape* source);
|
||||
void ensurePhysicsUpdated();
|
||||
|
||||
signals:
|
||||
void smoothingChanged();
|
||||
void colorChanged();
|
||||
signals:
|
||||
void smoothingChanged();
|
||||
void colorChanged();
|
||||
void cornerFillChanged();
|
||||
|
||||
private:
|
||||
qreal m_smoothing = 32.0;
|
||||
QColor m_color{0x44, 0x88, 0xff};
|
||||
QList<BlobShape*> m_shapes;
|
||||
BlobInvertedRect* m_invertedRect = nullptr;
|
||||
bool m_physicsUpdated = false;
|
||||
private:
|
||||
qreal m_smoothing = 32.0;
|
||||
QColor m_color{ 0x44, 0x88, 0xff };
|
||||
bool m_cornerFill = true;
|
||||
QList<BlobShape*> m_shapes;
|
||||
BlobInvertedRect* m_invertedRect = nullptr;
|
||||
bool m_physicsUpdated = false;
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <qnamespace.h>
|
||||
|
||||
BlobRect::BlobRect(QQuickItem* parent) : BlobShape(parent) {}
|
||||
|
||||
@@ -31,9 +32,11 @@ void BlobRect::updatePolish() {
|
||||
QMetaObject::invokeMethod(
|
||||
this,
|
||||
[this]() {
|
||||
if (m_group) m_group->markDirty();
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
if (m_group)
|
||||
m_group->markDirty();
|
||||
},
|
||||
Qt::QueuedConnection
|
||||
);
|
||||
}
|
||||
} else {
|
||||
QMetaObject::invokeMethod(
|
||||
@@ -103,20 +106,15 @@ void BlobRect::updatePhysics() {
|
||||
|
||||
const float kStiffness = static_cast<float>(m_stiffness);
|
||||
const float kDamping = static_cast<float>(m_damping);
|
||||
const float invDamp = 1.0f / (1.0f + kDamping * dt);
|
||||
|
||||
const float accel00 =
|
||||
-kStiffness * (m_dm00 - target00) - kDamping * m_dmVel00;
|
||||
m_dmVel00 += accel00 * dt;
|
||||
m_dmVel00 = (m_dmVel00 - kStiffness * (m_dm00 - target00) * dt) * invDamp;
|
||||
m_dm00 += m_dmVel00 * dt;
|
||||
|
||||
const float accel01 =
|
||||
-kStiffness * (m_dm01 - target01) - kDamping * m_dmVel01;
|
||||
m_dmVel01 += accel01 * dt;
|
||||
m_dmVel01 = (m_dmVel01 - kStiffness * (m_dm01 - target01) * dt) * invDamp;
|
||||
m_dm01 += m_dmVel01 * dt;
|
||||
|
||||
const float accel11 =
|
||||
-kStiffness * (m_dm11 - target11) - kDamping * m_dmVel11;
|
||||
m_dmVel11 += accel11 * dt;
|
||||
m_dmVel11 = (m_dmVel11 - kStiffness * (m_dm11 - target11) * dt) * invDamp;
|
||||
m_dm11 += m_dmVel11 * dt;
|
||||
|
||||
m_deformMatrix = QMatrix4x4(
|
||||
@@ -184,6 +182,14 @@ bool BlobRect::isExcluded(const BlobShape* other) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BlobRect::isCornerExcluded(const BlobShape* other) const {
|
||||
for (const auto& ptr : m_excludeCorners) {
|
||||
if (ptr == other)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QQmlListProperty<BlobRect> BlobRect::exclude() {
|
||||
return QQmlListProperty<BlobRect>(
|
||||
this,
|
||||
@@ -196,6 +202,11 @@ QQmlListProperty<BlobRect> BlobRect::exclude() {
|
||||
&excludeRemoveLast);
|
||||
}
|
||||
|
||||
QQmlListProperty<BlobRect> BlobRect::excludeCorners() {
|
||||
return QQmlListProperty<BlobRect>(this, nullptr, &excludeCornersAppend, &excludeCornersCount, &excludeCornersAt,
|
||||
&excludeCornersClear, &excludeCornersReplace, &excludeCornersRemoveLast);
|
||||
}
|
||||
|
||||
void BlobRect::excludeAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect) {
|
||||
auto* self = static_cast<BlobRect*>(prop->object);
|
||||
self->m_exclude.append(rect);
|
||||
@@ -238,6 +249,52 @@ void BlobRect::excludeRemoveLast(QQmlListProperty<BlobRect>* prop) {
|
||||
emit self->excludeChanged();
|
||||
}
|
||||
|
||||
void BlobRect::excludeCornersAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect) {
|
||||
auto* self = static_cast<BlobRect*>(prop->object);
|
||||
self->m_excludeCorners.append(rect);
|
||||
if (self->m_group)
|
||||
self->m_group->markDirty();
|
||||
emit self->excludeCornersChanged();
|
||||
}
|
||||
|
||||
qsizetype BlobRect::excludeCornersCount(QQmlListProperty<BlobRect>* prop) {
|
||||
auto* self = static_cast<BlobRect*>(prop->object);
|
||||
return self->m_excludeCorners.size();
|
||||
}
|
||||
|
||||
BlobRect* BlobRect::excludeCornersAt(QQmlListProperty<BlobRect>* prop, qsizetype index) {
|
||||
auto* self = static_cast<BlobRect*>(prop->object);
|
||||
return self->m_excludeCorners.at(index);
|
||||
}
|
||||
|
||||
void BlobRect::excludeCornersClear(QQmlListProperty<BlobRect>* prop) {
|
||||
auto* self = static_cast<BlobRect*>(prop->object);
|
||||
if (self->m_excludeCorners.isEmpty())
|
||||
return;
|
||||
self->m_excludeCorners.clear();
|
||||
if (self->m_group)
|
||||
self->m_group->markDirty();
|
||||
emit self->excludeCornersChanged();
|
||||
}
|
||||
|
||||
void BlobRect::excludeCornersReplace(QQmlListProperty<BlobRect>* prop, qsizetype index, BlobRect* rect) {
|
||||
auto* self = static_cast<BlobRect*>(prop->object);
|
||||
self->m_excludeCorners[index] = rect;
|
||||
if (self->m_group)
|
||||
self->m_group->markDirty();
|
||||
emit self->excludeCornersChanged();
|
||||
}
|
||||
|
||||
void BlobRect::excludeCornersRemoveLast(QQmlListProperty<BlobRect>* prop) {
|
||||
auto* self = static_cast<BlobRect*>(prop->object);
|
||||
if (self->m_excludeCorners.isEmpty())
|
||||
return;
|
||||
self->m_excludeCorners.removeLast();
|
||||
if (self->m_group)
|
||||
self->m_group->markDirty();
|
||||
emit self->excludeCornersChanged();
|
||||
}
|
||||
|
||||
void BlobRect::checkAtRest(float speed) {
|
||||
constexpr float kEpsilon = 0.002f;
|
||||
const bool atRest =
|
||||
@@ -262,9 +319,11 @@ void BlobRect::checkAtRest(float speed) {
|
||||
QMetaObject::invokeMethod(
|
||||
this,
|
||||
[this]() {
|
||||
if (m_group) m_group->markDirty();
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
if (m_group)
|
||||
m_group->markDirty();
|
||||
},
|
||||
Qt::QueuedConnection
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,29 +8,18 @@
|
||||
#include <qqmllist.h>
|
||||
|
||||
class BlobRect : public BlobShape {
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
Q_PROPERTY(
|
||||
qreal stiffness READ stiffness WRITE setStiffness NOTIFY
|
||||
stiffnessChanged)
|
||||
Q_PROPERTY(qreal damping READ damping WRITE setDamping NOTIFY dampingChanged)
|
||||
Q_PROPERTY(
|
||||
qreal deformScale READ deformScale WRITE setDeformScale NOTIFY
|
||||
deformScaleChanged)
|
||||
Q_PROPERTY(
|
||||
QQmlListProperty<BlobRect> exclude READ exclude NOTIFY excludeChanged)
|
||||
Q_PROPERTY(
|
||||
qreal topLeftRadius READ topLeftRadius WRITE setTopLeftRadius NOTIFY
|
||||
topLeftRadiusChanged)
|
||||
Q_PROPERTY(
|
||||
qreal topRightRadius READ topRightRadius WRITE setTopRightRadius NOTIFY
|
||||
topRightRadiusChanged)
|
||||
Q_PROPERTY(
|
||||
qreal bottomLeftRadius READ bottomLeftRadius WRITE setBottomLeftRadius
|
||||
NOTIFY bottomLeftRadiusChanged)
|
||||
Q_PROPERTY(
|
||||
qreal bottomRightRadius READ bottomRightRadius WRITE
|
||||
setBottomRightRadius NOTIFY bottomRightRadiusChanged)
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
Q_PROPERTY(qreal stiffness READ stiffness WRITE setStiffness NOTIFY stiffnessChanged)
|
||||
Q_PROPERTY(qreal damping READ damping WRITE setDamping NOTIFY dampingChanged)
|
||||
Q_PROPERTY(qreal deformScale READ deformScale WRITE setDeformScale NOTIFY deformScaleChanged)
|
||||
Q_PROPERTY(QQmlListProperty<BlobRect> exclude READ exclude NOTIFY excludeChanged)
|
||||
Q_PROPERTY(QQmlListProperty<BlobRect> excludeCorners READ excludeCorners NOTIFY excludeCornersChanged)
|
||||
Q_PROPERTY(qreal topLeftRadius READ topLeftRadius WRITE setTopLeftRadius NOTIFY topLeftRadiusChanged)
|
||||
Q_PROPERTY(qreal topRightRadius READ topRightRadius WRITE setTopRightRadius NOTIFY topRightRadiusChanged)
|
||||
Q_PROPERTY(qreal bottomLeftRadius READ bottomLeftRadius WRITE setBottomLeftRadius NOTIFY bottomLeftRadiusChanged)
|
||||
Q_PROPERTY(
|
||||
qreal bottomRightRadius READ bottomRightRadius WRITE setBottomRightRadius NOTIFY bottomRightRadiusChanged)
|
||||
|
||||
public:
|
||||
explicit BlobRect(QQuickItem* parent = nullptr);
|
||||
@@ -63,10 +52,12 @@ class BlobRect : public BlobShape {
|
||||
}
|
||||
}
|
||||
|
||||
QQmlListProperty<BlobRect> exclude();
|
||||
QQmlListProperty<BlobRect> exclude();
|
||||
QQmlListProperty<BlobRect> excludeCorners();
|
||||
|
||||
bool isExcluded(const BlobShape* other) const override;
|
||||
void cornerRadii(float out[4]) const override;
|
||||
bool isExcluded(const BlobShape* other) const override;
|
||||
bool isCornerExcluded(const BlobShape* other) const override;
|
||||
void cornerRadii(float out[4]) const override;
|
||||
|
||||
[[nodiscard]] qreal topLeftRadius() const { return m_topLeftRadius; }
|
||||
|
||||
@@ -86,15 +77,16 @@ class BlobRect : public BlobShape {
|
||||
|
||||
void setBottomRightRadius(qreal r);
|
||||
|
||||
signals:
|
||||
void stiffnessChanged();
|
||||
void dampingChanged();
|
||||
void deformScaleChanged();
|
||||
void excludeChanged();
|
||||
void topLeftRadiusChanged();
|
||||
void topRightRadiusChanged();
|
||||
void bottomLeftRadiusChanged();
|
||||
void bottomRightRadiusChanged();
|
||||
signals:
|
||||
void stiffnessChanged();
|
||||
void dampingChanged();
|
||||
void deformScaleChanged();
|
||||
void excludeChanged();
|
||||
void excludeCornersChanged();
|
||||
void topLeftRadiusChanged();
|
||||
void topRightRadiusChanged();
|
||||
void bottomLeftRadiusChanged();
|
||||
void bottomRightRadiusChanged();
|
||||
|
||||
protected:
|
||||
void updatePolish() override;
|
||||
@@ -129,14 +121,20 @@ class BlobRect : public BlobShape {
|
||||
qreal m_bottomLeftRadius = -1;
|
||||
qreal m_bottomRightRadius = -1;
|
||||
|
||||
QList<QPointer<BlobRect>> m_exclude;
|
||||
QList<QPointer<BlobRect> > m_exclude;
|
||||
QList<QPointer<BlobRect> > m_excludeCorners;
|
||||
|
||||
static void excludeAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect);
|
||||
static qsizetype excludeCount(QQmlListProperty<BlobRect>* prop);
|
||||
static BlobRect* excludeAt(
|
||||
QQmlListProperty<BlobRect>* prop, qsizetype index);
|
||||
static void excludeClear(QQmlListProperty<BlobRect>* prop);
|
||||
static void excludeReplace(
|
||||
QQmlListProperty<BlobRect>* prop, qsizetype index, BlobRect* rect);
|
||||
static void excludeRemoveLast(QQmlListProperty<BlobRect>* prop);
|
||||
static void excludeAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect);
|
||||
static qsizetype excludeCount(QQmlListProperty<BlobRect>* prop);
|
||||
static BlobRect* excludeAt(QQmlListProperty<BlobRect>* prop, qsizetype index);
|
||||
static void excludeClear(QQmlListProperty<BlobRect>* prop);
|
||||
static void excludeReplace(QQmlListProperty<BlobRect>* prop, qsizetype index, BlobRect* rect);
|
||||
static void excludeRemoveLast(QQmlListProperty<BlobRect>* prop);
|
||||
|
||||
static void excludeCornersAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect);
|
||||
static qsizetype excludeCornersCount(QQmlListProperty<BlobRect>* prop);
|
||||
static BlobRect* excludeCornersAt(QQmlListProperty<BlobRect>* prop, qsizetype index);
|
||||
static void excludeCornersClear(QQmlListProperty<BlobRect>* prop);
|
||||
static void excludeCornersReplace(QQmlListProperty<BlobRect>* prop, qsizetype index, BlobRect* rect);
|
||||
static void excludeCornersRemoveLast(QQmlListProperty<BlobRect>* prop);
|
||||
};
|
||||
|
||||
@@ -32,7 +32,14 @@ static float cpuSmoothstep(float edge0, float edge1, float x) {
|
||||
return t * t * (3.0f - 2.0f * t);
|
||||
}
|
||||
|
||||
BlobShape::BlobShape(QQuickItem* parent) : QQuickItem(parent) {
|
||||
static float cornerFillFactor(float sd, float smoothFactor) {
|
||||
const float outside = cpuSmoothstep(0.0f, smoothFactor, sd);
|
||||
const float inside = cpuSmoothstep(0.0f, -smoothFactor, sd);
|
||||
return std::max(outside, inside);
|
||||
}
|
||||
|
||||
BlobShape::BlobShape(QQuickItem* parent)
|
||||
: QQuickItem(parent) {
|
||||
setFlag(ItemHasContents);
|
||||
}
|
||||
|
||||
@@ -81,8 +88,6 @@ void BlobShape::geometryChange(
|
||||
std::abs(m_pendingDh) > syncThreshold) {
|
||||
m_pendingDx = 0;
|
||||
m_pendingDy = 0;
|
||||
m_pendingDw = 0;
|
||||
m_pendingDh = 0;
|
||||
m_group->markShapeDirty(this);
|
||||
}
|
||||
}
|
||||
@@ -293,10 +298,12 @@ void BlobShape::updatePolish() {
|
||||
|
||||
const float smoothFactor = pad;
|
||||
constexpr float minR = 2.0f;
|
||||
const bool cornerFill = m_group->cornerFill();
|
||||
const auto rectCount = m_cachedRects.size();
|
||||
for (qsizetype i = 0; i < rectCount; ++i) {
|
||||
auto& ri = m_cachedRects[i];
|
||||
const int riExcludeMask = ri.excludeMask;
|
||||
BlobShape* const si = rectShapes[i];
|
||||
float fTr = 1.0f, fBr = 1.0f, fBl = 1.0f, fTl = 1.0f;
|
||||
|
||||
const float cTrX = ri.cx + ri.hw, cTrY = ri.cy - ri.hh;
|
||||
@@ -304,37 +311,26 @@ void BlobShape::updatePolish() {
|
||||
const float cBlX = ri.cx - ri.hw, cBlY = ri.cy + ri.hh;
|
||||
const float cTlX = ri.cx - ri.hw, cTlY = ri.cy - ri.hh;
|
||||
|
||||
for (qsizetype j = 0; j < rectCount; ++j) {
|
||||
if (j == i) continue;
|
||||
if (riExcludeMask & (1 << j)) continue;
|
||||
for (qsizetype j = 0; cornerFill && j < rectCount; ++j) {
|
||||
if (j == i)
|
||||
continue;
|
||||
if (riExcludeMask & (1 << j))
|
||||
continue;
|
||||
BlobShape* const sj = rectShapes[j];
|
||||
if (si->isCornerExcluded(sj) || sj->isCornerExcluded(si))
|
||||
continue;
|
||||
const auto& rj = m_cachedRects[j];
|
||||
fTr = std::min(
|
||||
fTr,
|
||||
cpuSmoothstep(
|
||||
0.0f,
|
||||
smoothFactor,
|
||||
cpuSdBox(cTrX, cTrY, rj.cx, rj.cy, rj.hw, rj.hh)));
|
||||
fBr = std::min(
|
||||
fBr,
|
||||
cpuSmoothstep(
|
||||
0.0f,
|
||||
smoothFactor,
|
||||
cpuSdBox(cBrX, cBrY, rj.cx, rj.cy, rj.hw, rj.hh)));
|
||||
fBl = std::min(
|
||||
fBl,
|
||||
cpuSmoothstep(
|
||||
0.0f,
|
||||
smoothFactor,
|
||||
cpuSdBox(cBlX, cBlY, rj.cx, rj.cy, rj.hw, rj.hh)));
|
||||
fTl = std::min(
|
||||
fTl,
|
||||
cpuSmoothstep(
|
||||
0.0f,
|
||||
smoothFactor,
|
||||
cpuSdBox(cTlX, cTlY, rj.cx, rj.cy, rj.hw, rj.hh)));
|
||||
const float sdTr = cpuSdBox(cTrX, cTrY, rj.cx, rj.cy, rj.hw, rj.hh);
|
||||
const float sdBr = cpuSdBox(cBrX, cBrY, rj.cx, rj.cy, rj.hw, rj.hh);
|
||||
const float sdBl = cpuSdBox(cBlX, cBlY, rj.cx, rj.cy, rj.hw, rj.hh);
|
||||
const float sdTl = cpuSdBox(cTlX, cTlY, rj.cx, rj.cy, rj.hw, rj.hh);
|
||||
fTr = std::min(fTr, cornerFillFactor(sdTr, smoothFactor));
|
||||
fBr = std::min(fBr, cornerFillFactor(sdBr, smoothFactor));
|
||||
fBl = std::min(fBl, cornerFillFactor(sdBl, smoothFactor));
|
||||
fTl = std::min(fTl, cornerFillFactor(sdTl, smoothFactor));
|
||||
}
|
||||
|
||||
if (m_cachedHasInverted) {
|
||||
if (cornerFill && m_cachedHasInverted) {
|
||||
const float icx = m_cachedInvertedInner[0];
|
||||
const float icy = m_cachedInvertedInner[1];
|
||||
const float ihw = m_cachedInvertedInner[2];
|
||||
|
||||
@@ -55,7 +55,11 @@ class BlobShape : public QQuickItem {
|
||||
|
||||
virtual bool isExcluded(const BlobShape*) const { return false; }
|
||||
|
||||
virtual void cornerRadii(float out[4]) const;
|
||||
virtual bool isCornerExcluded(const BlobShape* /*other*/) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual void cornerRadii(float out[4]) const;
|
||||
|
||||
virtual void updatePhysics() {}
|
||||
|
||||
@@ -63,24 +67,25 @@ class BlobShape : public QQuickItem {
|
||||
virtual void unregisterFromGroup();
|
||||
void updateCenteredDeformMatrix();
|
||||
|
||||
BlobGroup* m_group = nullptr;
|
||||
qreal m_radius = 0;
|
||||
QMatrix4x4 m_deformMatrix;
|
||||
QMatrix4x4 m_centeredDeformMatrix;
|
||||
BlobGroup* m_group = nullptr;
|
||||
qreal m_radius = 0;
|
||||
QMatrix4x4 m_deformMatrix; // identity by default
|
||||
QMatrix4x4 m_centeredDeformMatrix;
|
||||
|
||||
float m_cachedPaddedX = 0;
|
||||
float m_cachedPaddedY = 0;
|
||||
float m_cachedPaddedW = 0;
|
||||
float m_cachedPaddedH = 0;
|
||||
QRectF m_localPaddedRect;
|
||||
QVector<BlobRectData> m_cachedRects;
|
||||
int m_cachedMyIndex = -2;
|
||||
float m_pendingDx = 0;
|
||||
float m_pendingDy = 0;
|
||||
float m_pendingDw = 0;
|
||||
float m_pendingDh = 0;
|
||||
bool m_cachedHasInverted = false;
|
||||
float m_cachedInvertedRadius = 0;
|
||||
float m_cachedInvertedOuter[4] = {};
|
||||
float m_cachedInvertedInner[4] = {};
|
||||
// Cached data from updatePolish
|
||||
float m_cachedPaddedX = 0;
|
||||
float m_cachedPaddedY = 0;
|
||||
float m_cachedPaddedW = 0;
|
||||
float m_cachedPaddedH = 0;
|
||||
float m_pendingDw = 0;
|
||||
float m_pendingDh = 0;
|
||||
QRectF m_localPaddedRect;
|
||||
QVector<BlobRectData> m_cachedRects;
|
||||
int m_cachedMyIndex = -2;
|
||||
float m_pendingDx = 0;
|
||||
float m_pendingDy = 0;
|
||||
bool m_cachedHasInverted = false;
|
||||
float m_cachedInvertedRadius = 0;
|
||||
float m_cachedInvertedOuter[4] = {};
|
||||
float m_cachedInvertedInner[4] = {};
|
||||
};
|
||||
|
||||
+89
-43
@@ -1,35 +1,32 @@
|
||||
#include "appdb.hpp"
|
||||
|
||||
#include <qloggingcategory.h>
|
||||
#include <qsqldatabase.h>
|
||||
#include <qsqlquery.h>
|
||||
#include <quuid.h>
|
||||
|
||||
Q_LOGGING_CATEGORY(lcAppDb, "ZShell.appdb", QtInfoMsg)
|
||||
|
||||
namespace ZShell {
|
||||
|
||||
AppEntry::AppEntry(QObject* entry, unsigned int frequency, QObject* parent)
|
||||
: QObject(parent), m_entry(entry), m_frequency(frequency) {
|
||||
: QObject(parent)
|
||||
, m_entry(entry)
|
||||
, m_frequency(frequency) {
|
||||
const auto mo = m_entry->metaObject();
|
||||
// NOLINTNEXTLINE(clang-analyzer-optin.cplusplus.VirtualCall)
|
||||
const auto tmo = metaObject();
|
||||
const auto tmo = &AppEntry::staticMetaObject;
|
||||
|
||||
for (const auto& prop :
|
||||
{"name",
|
||||
"comment",
|
||||
"execString",
|
||||
"startupClass",
|
||||
"genericName",
|
||||
"categories",
|
||||
"keywords"}) {
|
||||
{ "name", "comment", "execString", "startupClass", "genericName", "categories", "keywords" }) {
|
||||
const auto metaProp = mo->property(mo->indexOfProperty(prop));
|
||||
const auto thisMetaProp = tmo->property(tmo->indexOfProperty(prop));
|
||||
QObject::connect(
|
||||
m_entry, metaProp.notifySignal(), this, thisMetaProp.notifySignal());
|
||||
QObject::connect(m_entry, metaProp.notifySignal(), this, thisMetaProp.notifySignal());
|
||||
}
|
||||
|
||||
QObject::connect(m_entry, &QObject::destroyed, this, [this]() {
|
||||
m_entry = nullptr;
|
||||
deleteLater();
|
||||
});
|
||||
m_entry = nullptr;
|
||||
deleteLater();
|
||||
});
|
||||
}
|
||||
|
||||
QObject* AppEntry::entry() const {
|
||||
@@ -121,9 +118,7 @@ AppDb::AppDb(QObject* parent)
|
||||
db.open();
|
||||
|
||||
QSqlQuery query(db);
|
||||
query.exec(
|
||||
"CREATE TABLE IF NOT EXISTS frequencies (id TEXT PRIMARY KEY, "
|
||||
"frequency INTEGER)");
|
||||
query.exec("CREATE TABLE IF NOT EXISTS frequencies (id TEXT PRIMARY KEY, frequency INTEGER)");
|
||||
}
|
||||
|
||||
QString AppDb::uuid() const {
|
||||
@@ -150,9 +145,7 @@ void AppDb::setPath(const QString& path) {
|
||||
db.open();
|
||||
|
||||
QSqlQuery query(db);
|
||||
query.exec(
|
||||
"CREATE TABLE IF NOT EXISTS frequencies (id TEXT PRIMARY KEY, "
|
||||
"frequency INTEGER)");
|
||||
query.exec("CREATE TABLE IF NOT EXISTS frequencies (id TEXT PRIMARY KEY, frequency INTEGER)");
|
||||
|
||||
updateAppFrequencies();
|
||||
}
|
||||
@@ -172,6 +165,39 @@ void AppDb::setEntries(const QObjectList& entries) {
|
||||
m_timer->start();
|
||||
}
|
||||
|
||||
QStringList AppDb::favoriteApps() const {
|
||||
return m_favoriteApps;
|
||||
}
|
||||
|
||||
void AppDb::setFavoriteApps(const QStringList& favApps) {
|
||||
if (m_favoriteApps == favApps) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_favoriteApps = favApps;
|
||||
emit favoriteAppsChanged();
|
||||
m_favoriteAppsRegex.clear();
|
||||
m_favoriteAppsRegex.reserve(m_favoriteApps.size());
|
||||
for (const QString& item : std::as_const(m_favoriteApps)) {
|
||||
const QRegularExpression re(regexifyString(item));
|
||||
if (re.isValid()) {
|
||||
m_favoriteAppsRegex << re;
|
||||
} else {
|
||||
qCWarning(lcAppDb) << "setFavoriteApps: regular expression is not valid:" << re.pattern();
|
||||
}
|
||||
}
|
||||
|
||||
emit appsChanged();
|
||||
}
|
||||
|
||||
QString AppDb::regexifyString(const QString& original) const {
|
||||
if (original.startsWith('^') && original.endsWith('$'))
|
||||
return original;
|
||||
|
||||
const QString escaped = QRegularExpression::escape(original);
|
||||
return QStringLiteral("^%1$").arg(escaped);
|
||||
}
|
||||
|
||||
QQmlListProperty<AppEntry> AppDb::apps() {
|
||||
return QQmlListProperty<AppEntry>(this, &getSortedApps());
|
||||
}
|
||||
@@ -180,40 +206,57 @@ void AppDb::incrementFrequency(const QString& id) {
|
||||
auto db = QSqlDatabase::database(m_uuid);
|
||||
QSqlQuery query(db);
|
||||
|
||||
query.prepare(
|
||||
"INSERT INTO frequencies (id, frequency) "
|
||||
"VALUES (:id, 1) "
|
||||
"ON CONFLICT (id) DO UPDATE SET frequency = frequency + 1");
|
||||
query.prepare("INSERT INTO frequencies (id, frequency) "
|
||||
"VALUES (:id, 1) "
|
||||
"ON CONFLICT (id) DO UPDATE SET frequency = frequency + 1");
|
||||
query.bindValue(":id", id);
|
||||
query.exec();
|
||||
|
||||
auto* app = m_apps.value(id);
|
||||
if (app) {
|
||||
const auto before = getSortedApps();
|
||||
|
||||
app->incrementFrequency();
|
||||
|
||||
if (before != getSortedApps()) {
|
||||
getSortedApps();
|
||||
if (before != m_sortedApps) {
|
||||
emit appsChanged();
|
||||
}
|
||||
} else {
|
||||
qWarning() << "AppDb::incrementFrequency: could not find app with id"
|
||||
<< id;
|
||||
qCWarning(lcAppDb) << "incrementFrequency: could not find app with id" << id;
|
||||
}
|
||||
}
|
||||
|
||||
QList<AppEntry*>& AppDb::getSortedApps() const {
|
||||
m_sortedApps = m_apps.values();
|
||||
std::sort(
|
||||
m_sortedApps.begin(), m_sortedApps.end(), [](AppEntry* a, AppEntry* b) {
|
||||
if (a->frequency() != b->frequency()) {
|
||||
|
||||
// Pre-compute favorite status to avoid repeated regex matching during sort
|
||||
QSet<QString> favSet;
|
||||
favSet.reserve(m_sortedApps.size());
|
||||
for (const auto* app : std::as_const(m_sortedApps)) {
|
||||
if (isFavorite(app))
|
||||
favSet.insert(app->id());
|
||||
}
|
||||
|
||||
std::sort(m_sortedApps.begin(), m_sortedApps.end(), [&favSet](AppEntry* a, AppEntry* b) {
|
||||
const bool aIsFav = favSet.contains(a->id());
|
||||
const bool bIsFav = favSet.contains(b->id());
|
||||
if (aIsFav != bIsFav)
|
||||
return aIsFav;
|
||||
if (a->frequency() != b->frequency())
|
||||
return a->frequency() > b->frequency();
|
||||
}
|
||||
return a->name().localeAwareCompare(b->name()) < 0;
|
||||
});
|
||||
return m_sortedApps;
|
||||
}
|
||||
|
||||
bool AppDb::isFavorite(const AppEntry* app) const {
|
||||
for (const QRegularExpression& re : m_favoriteAppsRegex) {
|
||||
if (re.match(app->id()).hasMatch()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
quint32 AppDb::getFrequency(const QString& id) const {
|
||||
auto db = QSqlDatabase::database(m_uuid);
|
||||
QSqlQuery query(db);
|
||||
@@ -235,7 +278,8 @@ void AppDb::updateAppFrequencies() {
|
||||
app->setFrequency(getFrequency(app->id()));
|
||||
}
|
||||
|
||||
if (before != getSortedApps()) {
|
||||
getSortedApps();
|
||||
if (before != m_sortedApps) {
|
||||
emit appsChanged();
|
||||
}
|
||||
}
|
||||
@@ -249,10 +293,10 @@ void AppDb::updateApps() {
|
||||
dirty = true;
|
||||
auto* const newEntry = new AppEntry(entry, getFrequency(id), this);
|
||||
QObject::connect(newEntry, &QObject::destroyed, this, [id, this]() {
|
||||
if (m_apps.remove(id)) {
|
||||
emit appsChanged();
|
||||
}
|
||||
});
|
||||
if (m_apps.remove(id)) {
|
||||
emit appsChanged();
|
||||
}
|
||||
});
|
||||
m_apps.insert(id, newEntry);
|
||||
}
|
||||
}
|
||||
@@ -262,11 +306,13 @@ void AppDb::updateApps() {
|
||||
newIds.insert(entry->property("id").toString());
|
||||
}
|
||||
|
||||
for (auto it = m_apps.keyBegin(); it != m_apps.keyEnd(); ++it) {
|
||||
const auto& id = *it;
|
||||
if (!newIds.contains(id)) {
|
||||
for (auto it = m_apps.begin(); it != m_apps.end();) {
|
||||
if (!newIds.contains(it.key())) {
|
||||
dirty = true;
|
||||
m_apps.take(id)->deleteLater();
|
||||
it.value()->deleteLater();
|
||||
it = m_apps.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+28
-21
@@ -4,6 +4,7 @@
|
||||
#include <qobject.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qqmllist.h>
|
||||
#include <qregularexpression.h>
|
||||
#include <qtimer.h>
|
||||
|
||||
namespace ZShell {
|
||||
@@ -64,13 +65,11 @@ class AppDb : public QObject {
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
Q_PROPERTY(QString uuid READ uuid CONSTANT)
|
||||
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged REQUIRED)
|
||||
Q_PROPERTY(
|
||||
QObjectList entries READ entries WRITE setEntries NOTIFY entriesChanged
|
||||
REQUIRED)
|
||||
Q_PROPERTY(
|
||||
QQmlListProperty<ZShell::AppEntry> apps READ apps NOTIFY appsChanged)
|
||||
Q_PROPERTY(QString uuid READ uuid CONSTANT)
|
||||
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged REQUIRED)
|
||||
Q_PROPERTY(QObjectList entries READ entries WRITE setEntries NOTIFY entriesChanged REQUIRED)
|
||||
Q_PROPERTY(QStringList favoriteApps READ favoriteApps WRITE setFavoriteApps NOTIFY favoriteAppsChanged REQUIRED)
|
||||
Q_PROPERTY(QQmlListProperty<ZShell::AppEntry> apps READ apps NOTIFY appsChanged)
|
||||
|
||||
public:
|
||||
explicit AppDb(QObject* parent = nullptr);
|
||||
@@ -83,28 +82,36 @@ class AppDb : public QObject {
|
||||
[[nodiscard]] QObjectList entries() const;
|
||||
void setEntries(const QObjectList& entries);
|
||||
|
||||
[[nodiscard]] QQmlListProperty<AppEntry> apps();
|
||||
[[nodiscard]] QStringList favoriteApps() const;
|
||||
void setFavoriteApps(const QStringList& favApps);
|
||||
|
||||
[[nodiscard]] QQmlListProperty<AppEntry> apps();
|
||||
|
||||
Q_INVOKABLE void incrementFrequency(const QString& id);
|
||||
|
||||
signals:
|
||||
void pathChanged();
|
||||
void entriesChanged();
|
||||
void appsChanged();
|
||||
signals:
|
||||
void pathChanged();
|
||||
void entriesChanged();
|
||||
void favoriteAppsChanged();
|
||||
void appsChanged();
|
||||
|
||||
private:
|
||||
QTimer* m_timer;
|
||||
|
||||
const QString m_uuid;
|
||||
QString m_path;
|
||||
QObjectList m_entries;
|
||||
QHash<QString, AppEntry*> m_apps;
|
||||
mutable QList<AppEntry*> m_sortedApps;
|
||||
const QString m_uuid;
|
||||
QString m_path;
|
||||
QObjectList m_entries;
|
||||
QStringList m_favoriteApps;
|
||||
QList<QRegularExpression> m_favoriteAppsRegex;
|
||||
QHash<QString, AppEntry*> m_apps;
|
||||
mutable QList<AppEntry*> m_sortedApps;
|
||||
|
||||
QList<AppEntry*>& getSortedApps() const;
|
||||
quint32 getFrequency(const QString& id) const;
|
||||
void updateAppFrequencies();
|
||||
void updateApps();
|
||||
QString regexifyString(const QString& original) const;
|
||||
QList<AppEntry*>& getSortedApps() const;
|
||||
bool isFavorite(const AppEntry* app) const;
|
||||
quint32 getFrequency(const QString& id) const;
|
||||
void updateAppFrequencies();
|
||||
void updateApps();
|
||||
};
|
||||
|
||||
} // namespace ZShell
|
||||
|
||||
@@ -12,15 +12,18 @@ class ZUtils : public QObject {
|
||||
QML_ELEMENT
|
||||
QML_SINGLETON
|
||||
|
||||
public:
|
||||
// clang-format off
|
||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path);
|
||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect);
|
||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved);
|
||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed);
|
||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved);
|
||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved, QJSValue onFailed);
|
||||
// clang-format on
|
||||
Q_PROPERTY(QString version READ version CONSTANT)
|
||||
Q_PROPERTY(QString qtVersion READ qtVersion CONSTANT)
|
||||
|
||||
public:
|
||||
// clang-format off
|
||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path);
|
||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect);
|
||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved);
|
||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed);
|
||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved);
|
||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved, QJSValue onFailed);
|
||||
// clang-format on
|
||||
|
||||
Q_INVOKABLE static bool copyFile(
|
||||
const QUrl& source, const QUrl& target, bool overwrite = true);
|
||||
|
||||
Reference in New Issue
Block a user